diff options
author | Antonin Kral <a.kral@bobek.cz> | 2010-01-31 08:32:52 +0100 |
---|---|---|
committer | Antonin Kral <a.kral@bobek.cz> | 2010-01-31 08:32:52 +0100 |
commit | 4eefaf421bfeddf040d96a3dafb12e09673423d7 (patch) | |
tree | cb2e5ccc7f98158894f977ff131949da36673591 | |
download | mongodb-4eefaf421bfeddf040d96a3dafb12e09673423d7.tar.gz |
Imported Upstream version 1.3.1
656 files changed, 131140 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5f07eca --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.vcproj -crlf -diff -merge +*.pbxproj -crlf -diff -merge + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3256d02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +.jsdbshell +.dbshell +.sconsign.dblite +.sconf_temp + +*~ +*.o +*.aps +*.tar.gz +*.suo +*.ncb +*.idb +*.obj +*/*.obj +*.opt +*.pch +*.jsh +*.jsall +*.pyc +*.log +*.exe +*.exp +*.lib +*.idb +*.pdb +*.manifest +*.gch +*# +.#* +shell/mongo.cpp +shell/mongo-server.cpp + +db/Debug +db/oplog* +db/.gdb* +db/makefile.local +config.log +settings.py +buildinfo.cpp +tags + +#temp dirs +dump +log +logs +docs/html +docs/latex +32bit +scratch + +# binaries +mongo +mongod +mongogrid +mongos + +mongodump +mongorestore + +mongofiles +mongoexport + +mongoimport +mongosniff +mongobridge + +*.tgz +*.zip +*.tar.gz + +mongodb-* + +#libs +libmongoclient.* +libmongotestfiles.* + +# examples +firstExample +secondExample +whereExample + +#tests +test +authTest +perftest +clientTest + +#debian +build-stamp +configure-stamp +debian/mongodb +debian/mongodb.* + +#osx +.DS_Store + +# QtCreator +*.config +*.creator +*.creator.user +*.files +*.includes + diff --git a/GNU-AGPL-3.0.txt b/GNU-AGPL-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/GNU-AGPL-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + 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 +them 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. @@ -0,0 +1,32 @@ +MongoDB README + +DOCUMENTATION + + http://www.mongodb.org/ + +COMPONENTS + + mongod - The database process. + mongos - Sharding controller. + mongo - The database shell (uses interactive javascript). + +BUILDING + + see docs/building.md + + +RUNNING + + ./mongod + + runs the database. Use + + ./mongod --help + + to see command line options. + +NOTES + + Mongo uses memory mapped files. If built as a 32 bit executable, you will + not be able to work with large (multi-gigabyte) databases. However, 32 bit + builds work fine with small development databases. diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..5233083 --- /dev/null +++ b/SConstruct @@ -0,0 +1,1539 @@ +# -*- mode: python; -*- +# build file for 10gen db +# this request scons +# you can get from http://www.scons.org +# then just type scons + +# some common tasks +# build 64-bit mac and pushing to s3 +# scons --64 s3dist +# scons --distname=0.8 s3dist +# all s3 pushes require settings.py and simples3 + +import os +import sys +import imp +import types +import re +import shutil +import urllib +import urllib2 +import buildscripts +import buildscripts.bb + +buildscripts.bb.checkOk() + +# --- options ---- +AddOption('--prefix', + dest='prefix', + type='string', + nargs=1, + action='store', + metavar='DIR', + help='installation prefix') + +AddOption('--distname', + dest='distname', + type='string', + nargs=1, + action='store', + metavar='DIR', + help='dist name (0.8.0)') + + +AddOption( "--64", + dest="force64", + type="string", + nargs=0, + action="store", + help="whether to force 64 bit" ) + + +AddOption( "--32", + dest="force32", + type="string", + nargs=0, + action="store", + help="whether to force 32 bit" ) + + +AddOption( "--mm", + dest="mm", + type="string", + nargs=0, + action="store", + help="use main memory instead of memory mapped files" ) + + +AddOption( "--release", + dest="release", + type="string", + nargs=0, + action="store", + help="relase build") + + +AddOption( "--static", + dest="static", + type="string", + nargs=0, + action="store", + help="fully static build") + + +AddOption('--java', + dest='javaHome', + type='string', + default="/opt/java/", + nargs=1, + action='store', + metavar='DIR', + help='java home') + +AddOption('--nojni', + dest='nojni', + type="string", + nargs=0, + action="store", + help="turn off jni support" ) + + +AddOption('--usesm', + dest='usesm', + type="string", + nargs=0, + action="store", + help="use spider monkey for javascript" ) + +AddOption('--usev8', + dest='usev8', + type="string", + nargs=0, + action="store", + help="use v8 for javascript" ) + +AddOption('--usejvm', + dest='usejvm', + type="string", + nargs=0, + action="store", + help="use java for javascript" ) + +AddOption('--asio', + dest='asio', + type="string", + nargs=0, + action="store", + help="Use Asynchronous IO (NOT READY YET)" ) + +AddOption( "--d", + dest="debugBuild", + type="string", + nargs=0, + action="store", + help="debug build no optimization, etc..." ) + +AddOption( "--dd", + dest="debugBuildAndLogging", + type="string", + nargs=0, + action="store", + help="debug build no optimization, additional debug logging, etc..." ) + +AddOption( "--recstore", + dest="recstore", + type="string", + nargs=0, + action="store", + help="use new recstore" ) + +AddOption( "--noshell", + dest="noshell", + type="string", + nargs=0, + action="store", + help="don't build shell" ) + +AddOption( "--extrapath", + dest="extrapath", + type="string", + nargs=1, + action="store", + help="comma seperated list of add'l paths (--extrapath /opt/foo/,/foo) static linking" ) + +AddOption( "--extrapathdyn", + dest="extrapathdyn", + type="string", + nargs=1, + action="store", + help="comma seperated list of add'l paths (--extrapath /opt/foo/,/foo) dynamic linking" ) + + +AddOption( "--extralib", + dest="extralib", + type="string", + nargs=1, + action="store", + help="comma seperated list of libraries (--extralib js_static,readline" ) + +AddOption( "--cxx", + dest="cxx", + type="string", + nargs=1, + action="store", + help="compiler to use" ) + + +AddOption( "--boost-compiler", + dest="boostCompiler", + type="string", + nargs=1, + action="store", + help="compiler used for boost (gcc41)" ) + +AddOption( "--boost-version", + dest="boostVersion", + type="string", + nargs=1, + action="store", + help="boost version for linking(1_38)" ) + +# +# to use CPUPROFILE=/tmp/profile +# to view pprof -gv mongod /tmp/profile +# +AddOption( "--pg", + dest="profile", + type="string", + nargs=0, + action="store" ) + +# --- environment setup --- + +def removeIfInList( lst , thing ): + if thing in lst: + lst.remove( thing ) + +def printLocalInfo(): + import sys, SCons + print( "scons version: " + SCons.__version__ ) + print( "python version: " + " ".join( [ `i` for i in sys.version_info ] ) ) + +printLocalInfo() + +boostLibs = [ "thread" , "filesystem" , "program_options" ] + +onlyServer = len( COMMAND_LINE_TARGETS ) == 0 or ( len( COMMAND_LINE_TARGETS ) == 1 and str( COMMAND_LINE_TARGETS[0] ) in [ "mongod" , "mongos" , "test" ] ) +nix = False +useJavaHome = False +linux = False +linux64 = False +darwin = False +windows = False +freebsd = False +solaris = False +force64 = not GetOption( "force64" ) is None +if not force64 and os.getcwd().endswith( "mongo-64" ): + force64 = True + print( "*** assuming you want a 64-bit build b/c of directory *** " ) +msarch = None +if force64: + msarch = "amd64" + +force32 = not GetOption( "force32" ) is None +release = not GetOption( "release" ) is None +static = not GetOption( "static" ) is None + +debugBuild = ( not GetOption( "debugBuild" ) is None ) or ( not GetOption( "debugBuildAndLogging" ) is None ) +debugLogging = not GetOption( "debugBuildAndLogging" ) is None +noshell = not GetOption( "noshell" ) is None +nojni = not GetOption( "nojni" ) is None + +usesm = not GetOption( "usesm" ) is None +usev8 = not GetOption( "usev8" ) is None +usejvm = not GetOption( "usejvm" ) is None + +asio = not GetOption( "asio" ) is None + +env = Environment( MSVS_ARCH=msarch , tools = ["default", "gch"], toolpath = '.' ) +if GetOption( "cxx" ) is not None: + env["CC"] = GetOption( "cxx" ) + env["CXX"] = GetOption( "cxx" ) +env["LIBPATH"] = [] + +if GetOption( "recstore" ) != None: + env.Append( CPPDEFINES=[ "_RECSTORE" ] ) +env.Append( CPPDEFINES=[ "_SCONS" ] ) +env.Append( CPPPATH=[ "." ] ) + + + +boostCompiler = GetOption( "boostCompiler" ) +if boostCompiler is None: + boostCompiler = "" +else: + boostCompiler = "-" + boostCompiler + +boostVersion = GetOption( "boostVersion" ) +if boostVersion is None: + boostVersion = "" +else: + boostVersion = "-" + boostVersion + +if ( usesm and usejvm ): + print( "can't say usesm and usejvm at the same time" ) + Exit(1) + +if ( not ( usesm or usejvm or usev8 ) ): + usesm = True + +extraLibPlaces = [] + +def addExtraLibs( s ): + for x in s.split(","): + env.Append( CPPPATH=[ x + "/include" ] ) + env.Append( LIBPATH=[ x + "/lib" ] ) + env.Append( LIBPATH=[ x + "/lib64" ] ) + extraLibPlaces.append( x + "/lib" ) + +if GetOption( "extrapath" ) is not None: + addExtraLibs( GetOption( "extrapath" ) ) + release = True + +if GetOption( "extrapathdyn" ) is not None: + addExtraLibs( GetOption( "extrapathdyn" ) ) + +if GetOption( "extralib" ) is not None: + for x in GetOption( "extralib" ).split( "," ): + env.Append( LIBS=[ x ] ) + +# ------ SOURCE FILE SETUP ----------- + +commonFiles = Split( "stdafx.cpp buildinfo.cpp db/jsobj.cpp db/json.cpp db/commands.cpp db/lasterror.cpp db/nonce.cpp db/queryutil.cpp shell/mongo.cpp" ) +commonFiles += [ "util/background.cpp" , "util/mmap.cpp" , "util/sock.cpp" , "util/util.cpp" , "util/top.cpp" , "util/message.cpp" , + "util/assert_util.cpp" , "util/httpclient.cpp" , "util/md5main.cpp" , "util/base64.cpp", "util/debug_util.cpp", + "util/thread_pool.cpp" ] +commonFiles += Glob( "util/*.c" ) +commonFiles += Split( "client/connpool.cpp client/dbclient.cpp client/model.cpp client/parallel.cpp client/syncclusterconnection.cpp" ) +commonFiles += [ "scripting/engine.cpp" ] + +#mmap stuff + +if GetOption( "mm" ) != None: + commonFiles += [ "util/mmap_mm.cpp" ] +elif os.sys.platform == "win32": + commonFiles += [ "util/mmap_win.cpp" ] +else: + commonFiles += [ "util/mmap_posix.cpp" ] + +if os.path.exists( "util/processinfo_" + os.sys.platform + ".cpp" ): + commonFiles += [ "util/processinfo_" + os.sys.platform + ".cpp" ] +else: + commonFiles += [ "util/processinfo_none.cpp" ] + +coreDbFiles = [] +coreServerFiles = [ "util/message_server_port.cpp" , "util/message_server_asio.cpp" ] + +serverOnlyFiles = Split( "db/query.cpp db/update.cpp db/introspect.cpp db/btree.cpp db/clientcursor.cpp db/tests.cpp db/repl.cpp db/btreecursor.cpp db/cloner.cpp db/namespace.cpp db/matcher.cpp db/dbeval.cpp db/dbwebserver.cpp db/dbhelpers.cpp db/instance.cpp db/dbstats.cpp db/database.cpp db/pdfile.cpp db/index.cpp db/cursor.cpp db/security_commands.cpp db/client.cpp db/security.cpp util/miniwebserver.cpp db/storage.cpp db/reccache.cpp db/queryoptimizer.cpp db/extsort.cpp db/mr.cpp s/d_util.cpp" ) + +serverOnlyFiles += Glob( "db/dbcommands*.cpp" ) + +if usesm: + commonFiles += [ "scripting/engine_spidermonkey.cpp" ] + nojni = True +elif usev8: + commonFiles += [ Glob( "scripting/*v8*.cpp" ) ] + nojni = True +elif not nojni: + commonFiles += [ "scripting/engine_java.cpp" ] +else: + commonFiles += [ "scripting/engine_none.cpp" ] + nojni = True + +coreShardFiles = [] +shardServerFiles = coreShardFiles + Glob( "s/strategy*.cpp" ) + [ "s/commands_admin.cpp" , "s/commands_public.cpp" , "s/request.cpp" , "s/cursors.cpp" , "s/server.cpp" , "s/chunk.cpp" , "s/shardkey.cpp" , "s/config.cpp" , "s/s_only.cpp" ] +serverOnlyFiles += coreShardFiles + [ "s/d_logic.cpp" ] + +serverOnlyFiles += [ "db/module.cpp" ] + Glob( "db/modules/*.cpp" ) + +modules = [] + +for x in os.listdir( "db/modules/" ): + if x.find( "." ) >= 0: + continue + print( "adding module: " + x ) + modRoot = "db/modules/" + x + "/" + serverOnlyFiles += Glob( modRoot + "src/*.cpp" ) + modBuildFile = modRoot + "build.py" + if os.path.exists( modBuildFile ): + modules += [ imp.load_module( "module_" + x , open( modBuildFile , "r" ) , modBuildFile , ( ".py" , "r" , imp.PY_SOURCE ) ) ] + +allClientFiles = commonFiles + coreDbFiles + [ "client/clientOnly.cpp" , "client/gridfs.cpp" , "s/d_util.cpp" ]; + +allCXXFiles = allClientFiles + coreShardFiles + shardServerFiles + serverOnlyFiles; + +# ---- other build setup ----- + +platform = os.sys.platform +if "uname" in dir(os): + processor = os.uname()[4] +else: + processor = "i386" + +if force32: + processor = "i386" +if force64: + processor = "x86_64" + +DEFAULT_INSTALl_DIR = "/usr/local" +installDir = DEFAULT_INSTALl_DIR +nixLibPrefix = "lib" + +distName = GetOption( "distname" ) +dontReplacePackage = False + +javaHome = GetOption( "javaHome" ) +javaVersion = "i386"; +javaLibs = [] + +distBuild = len( COMMAND_LINE_TARGETS ) == 1 and ( str( COMMAND_LINE_TARGETS[0] ) == "s3dist" or str( COMMAND_LINE_TARGETS[0] ) == "dist" ) +if distBuild: + release = True + +if GetOption( "prefix" ): + installDir = GetOption( "prefix" ) + +def findVersion( root , choices ): + for c in choices: + if ( os.path.exists( root + c ) ): + return root + c + raise "can't find a version of [" + root + "] choices: " + choices + +def choosePathExist( choices , default=None): + for c in choices: + if c != None and os.path.exists( c ): + return c + return default + +if "darwin" == os.sys.platform: + darwin = True + platform = "osx" # prettier than darwin + + if usejvm: + env.Append( CPPPATH=[ "-I/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Headers/" ] ) + + if not nojni: + env.Append( FRAMEWORKS=["JavaVM"] ) + + if env["CXX"] is None: + if os.path.exists( "/usr/bin/g++-4.2" ): + env["CXX"] = "g++-4.2" + + nix = True + + if force64: + env.Append( CPPPATH=["/usr/64/include"] ) + env.Append( LIBPATH=["/usr/64/lib"] ) + if installDir == DEFAULT_INSTALl_DIR and not distBuild: + installDir = "/usr/64/" + else: + env.Append( CPPPATH=[ "/sw/include" , "/opt/local/include"] ) + env.Append( LIBPATH=["/sw/lib/", "/opt/local/lib"] ) + +elif "linux2" == os.sys.platform: + linux = True + useJavaHome = True + javaOS = "linux" + platform = "linux" + + javaHome = choosePathExist( [ javaHome , "/usr/lib/jvm/java/" , os.environ.get( "JAVA_HOME" ) ] , "/usr/lib/jvm/java/" ) + + if os.uname()[4] == "x86_64" and not force32: + linux64 = True + javaVersion = "amd64" + nixLibPrefix = "lib64" + env.Append( LIBPATH=["/usr/lib64" , "/lib64" ] ) + env.Append( LIBS=["pthread"] ) + + force64 = False + + if force32: + env.Append( LIBPATH=["/usr/lib32"] ) + + nix = True + + if static: + env.Append( LINKFLAGS=" -static " ) + +elif "sunos5" == os.sys.platform: + nix = True + solaris = True + useJavaHome = True + javaHome = "/usr/lib/jvm/java-6-sun/" + javaOS = "solaris" + env.Append( CPPDEFINES=[ "__linux__" , "__sunos__" ] ) + env.Append( LIBS=["socket","resolv"] ) + +elif os.sys.platform.startswith( "freebsd" ): + nix = True + freebsd = True + env.Append( CPPPATH=[ "/usr/local/include" ] ) + env.Append( LIBPATH=[ "/usr/local/lib" ] ) + env.Append( CPPDEFINES=[ "__freebsd__" ] ) + +elif "win32" == os.sys.platform: + windows = True + if force64: + release = True + + for pathdir in env['ENV']['PATH'].split(os.pathsep): + if os.path.exists(os.path.join(pathdir, 'cl.exe')): + break + else: + #use current environment + env['ENV'] = dict(os.environ) + + def find_boost(): + for bv in reversed( range(33,50) ): + for extra in ('', '_0', '_1'): + boostDir = "C:/Program Files/Boost/boost_1_" + str(bv) + extra + if os.path.exists( boostDir ): + return boostDir + return None + + boostDir = find_boost() + if boostDir is None: + print( "can't find boost" ) + Exit(1) + + serverOnlyFiles += [ "util/ntservice.cpp" ] + + boostLibs = [] + + if usesm: + env.Append( CPPPATH=[ "js/src/" ] ) + env.Append(CPPPATH=["../js/src/"]) + env.Append(LIBPATH=["../js/src"]) + env.Append( CPPDEFINES=[ "OLDJS" ] ) + else: + javaHome = findVersion( "C:/Program Files/java/" , + [ "jdk" , "jdk1.6.0_10" ] ) + env.Append( CPPPATH=[ javaHome + "/include" , javaHome + "/include/win32" ] ) + env.Append( LIBPATH=[ javaHome + "/Lib" ] ) + javaLibs += [ "jvm" ]; + + winSDKHome = findVersion( "C:/Program Files/Microsoft SDKs/Windows/" , + [ "v6.0" , "v6.0a" , "v6.1" ] ) + + env.Append( CPPPATH=[ boostDir , "pcre-7.4" , winSDKHome + "/Include" ] ) + + env.Append( CPPFLAGS=" /EHsc /W3 " ) + env.Append( CPPFLAGS=" /wd4355 /wd4800 " ) #some warnings we don't like + env.Append( CPPDEFINES=["WIN32","_CONSOLE","_CRT_SECURE_NO_WARNINGS","HAVE_CONFIG_H","PCRE_STATIC","_UNICODE","UNICODE","SUPPORT_UCP","SUPPORT_UTF8,PSAPI_VERSION=1" ] ) + + #env.Append( CPPFLAGS=' /Yu"stdafx.h" ' ) # this would be for pre-compiled headers, could play with it later + + if release: + env.Append( CPPDEFINES=[ "NDEBUG" ] ) + env.Append( CPPFLAGS= " /O2 /Oi /FD /MT /Gy /nologo /Zi /TP /errorReport:prompt /Gm " ) + #env.Append( CPPFLAGS= " /GL " ) # TODO: this has caused some linking problems + else: + env.Append( CPPDEFINES=[ "_DEBUG" ] ) + env.Append( CPPFLAGS=" /Od /Gm /RTC1 /MDd /ZI " ) + env.Append( CPPFLAGS=' /Fd"mongod.pdb" ' ) + + env.Append( LIBPATH=[ boostDir + "/Lib" ] ) + if force64: + env.Append( LIBPATH=[ winSDKHome + "/Lib/x64" ] ) + env.Append( LINKFLAGS=" /NODEFAULTLIB:MSVCPRT /NODEFAULTLIB:MSVCRT " ) + else: + env.Append( LIBPATH=[ winSDKHome + "/Lib" ] ) + + + def pcreFilter(x): + name = x.name + if x.name.endswith( "dftables.c" ): + return False + if x.name.endswith( "pcredemo.c" ): + return False + if x.name.endswith( "pcretest.c" ): + return False + if x.name.endswith( "unittest.cc" ): + return False + if x.name.endswith( "pcregrep.c" ): + return False + return True + + pcreFiles = [] + pcreFiles += filter( pcreFilter , Glob( "pcre-7.4/*.c" ) ) + pcreFiles += filter( pcreFilter , Glob( "pcre-7.4/*.cc" ) ) + commonFiles += pcreFiles + allClientFiles += pcreFiles + + winLibString = "ws2_32.lib kernel32.lib advapi32.lib Psapi.lib" + + if force64: + winLibString += " LIBCMT LIBCPMT " + else: + winLibString += " user32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib " + winLibString += " odbc32.lib odbccp32.lib uuid.lib " + + env.Append( LIBS=Split(winLibString) ) + + if force64: + env.Append( CPPDEFINES=["_AMD64_=1"] ) + else: + env.Append( CPPDEFINES=["_X86_=1"] ) + + env.Append( CPPPATH=["../winpcap/Include"] ) + env.Append( LIBPATH=["../winpcap/Lib"] ) + +else: + print( "No special config for [" + os.sys.platform + "] which probably means it won't work" ) + +if not nojni and useJavaHome: + env.Append( CPPPATH=[ javaHome + "include" , javaHome + "include/" + javaOS ] ) + env.Append( LIBPATH=[ javaHome + "jre/lib/" + javaVersion + "/server" , javaHome + "jre/lib/" + javaVersion ] ) + + if not nojni: + javaLibs += [ "java" , "jvm" ] + + env.Append( LINKFLAGS="-Xlinker -rpath -Xlinker " + javaHome + "jre/lib/" + javaVersion + "/server" ) + env.Append( LINKFLAGS="-Xlinker -rpath -Xlinker " + javaHome + "jre/lib/" + javaVersion ) + +if nix: + env.Append( CPPFLAGS="-fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch" ) + env.Append( CXXFLAGS=" -Wnon-virtual-dtor " ) + env.Append( LINKFLAGS=" -fPIC -pthread -rdynamic" ) + env.Append( LIBS=[] ) + + if debugBuild: + env.Append( CPPFLAGS=" -O0 -fstack-protector -fstack-check" ); + else: + env.Append( CPPFLAGS=" -O3" ) + + if debugLogging: + env.Append( CPPFLAGS=" -D_DEBUG" ); + + if force64: + env.Append( CFLAGS="-m64" ) + env.Append( CXXFLAGS="-m64" ) + env.Append( LINKFLAGS="-m64" ) + + if force32: + env.Append( CFLAGS="-m32" ) + env.Append( CXXFLAGS="-m32" ) + env.Append( LINKFLAGS="-m32" ) + + if GetOption( "profile" ) is not None: + env.Append( LIBS=[ "profiler" ] ) + + # pre-compiled headers + if False and 'Gch' in dir( env ): + print( "using precompiled headers" ) + env['Gch'] = env.Gch( [ "stdafx.h" ] )[0] + #Depends( "stdafx.o" , "stdafx.h.gch" ) + #SideEffect( "dummyGCHSideEffect" , "stdafx.h.gch" ) + +if usev8: + env.Append( CPPPATH=["../v8/include/"] ) + env.Append( LIBPATH=["../v8/"] ) + + +if "uname" in dir(os): + hacks = buildscripts.findHacks( os.uname() ) + if hacks is not None: + hacks.insert( env , { "linux64" : linux64 } ) + +try: + umask = os.umask(022) +except OSError: + pass + +# --- check system --- + +def getGitBranch(): + if not os.path.exists( ".git" ): + return None + + version = open( ".git/HEAD" ,'r' ).read().strip() + if not version.startswith( "ref: " ): + return version + version = version.split( "/" ) + version = version[len(version)-1] + return version + +def getGitBranchString( prefix="" , postfix="" ): + t = re.compile( '[/\\\]' ).split( os.getcwd() ) + if len(t) > 2 and t[len(t)-1] == "mongo": + t = re.compile( ".*_([vV]\d+\.\d+)$" ).match( t[len(t)-2] ) + if t is not None: + return prefix + t.group(1).lower() + postfix + + b = getGitBranch() + if b == None or b == "master": + return "" + return prefix + b + postfix + +def getGitVersion(): + if not os.path.exists( ".git" ): + return "nogitversion" + + version = open( ".git/HEAD" ,'r' ).read().strip() + if not version.startswith( "ref: " ): + return version + version = version[5:] + f = ".git/" + version + if not os.path.exists( f ): + return version + return open( f , 'r' ).read().strip() + +def getSysInfo(): + if windows: + return "windows " + str( sys.getwindowsversion() ) + else: + return " ".join( os.uname() ) + +def add_exe(target): + if windows: + return target + ".exe" + return target + +def setupBuildInfoFile( outFile ): + version = getGitVersion() + sysInfo = getSysInfo() + contents = '\n'.join([ + '#include "stdafx.h"', + '#include <iostream>', + '#include <boost/version.hpp>', + 'namespace mongo { const char * gitVersion(){ return "' + version + '"; } }', + 'namespace mongo { const char * sysInfo(){ return "' + sysInfo + ' BOOST_LIB_VERSION=" BOOST_LIB_VERSION ; } }', + ]) + + contents += '\n'; + + if os.path.exists( outFile ) and open( outFile ).read().strip() == contents.strip(): + return + + contents += '\n'; + + out = open( outFile , 'w' ) + out.write( contents ) + out.close() + +setupBuildInfoFile( "buildinfo.cpp" ) + +def bigLibString( myenv ): + s = str( myenv["LIBS"] ) + if 'SLIBS' in myenv._dict: + s += str( myenv["SLIBS"] ) + return s + + +def doConfigure( myenv , needJava=True , needPcre=True , shell=False ): + conf = Configure(myenv) + myenv["LINKFLAGS_CLEAN"] = list( myenv["LINKFLAGS"] ) + myenv["LIBS_CLEAN"] = list( myenv["LIBS"] ) + + if 'CheckCXX' in dir( conf ): + if not conf.CheckCXX(): + print( "c++ compiler not installed!" ) + Exit(1) + + if nix and not shell: + if not conf.CheckLib( "stdc++" ): + print( "can't find stdc++ library which is needed" ); + Exit(1) + + def myCheckLib( poss , failIfNotFound=False , java=False , staticOnly=False): + + if type( poss ) != types.ListType : + poss = [poss] + + allPlaces = []; + allPlaces += extraLibPlaces + if nix and release: + allPlaces += myenv["LIBPATH"] + if not force64: + allPlaces += [ "/usr/lib" , "/usr/local/lib" ] + + for p in poss: + for loc in allPlaces: + fullPath = loc + "/lib" + p + ".a" + if os.path.exists( fullPath ): + myenv['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)} $SLIBS' + myenv.Append( SLIBS=" " + fullPath + " " ) + return True + + + if release and not java and not windows and failIfNotFound: + print( "ERROR: can't find static version of: " + str( poss ) + " in: " + str( allPlaces ) ) + Exit(1) + + res = not staticOnly and conf.CheckLib( poss ) + if res: + return True + + if failIfNotFound: + print( "can't find library " + str( poss ) + " in " + str( myenv["LIBPATH"] ) ) + Exit(1) + + return False + + if needPcre and not conf.CheckCXXHeader( 'pcrecpp.h' ): + print( "can't find pcre" ) + Exit(1) + + if not conf.CheckCXXHeader( "boost/filesystem/operations.hpp" ): + print( "can't find boost headers" ) + if shell: + print( "\tshell might not compile" ) + else: + Exit(1) + + if asio: + if conf.CheckCXXHeader( "boost/asio.hpp" ): + myenv.Append( CPPDEFINES=[ "USE_ASIO" ] ) + else: + print( "WARNING: old version of boost - you should consider upgrading" ) + + # this will add it iff it exists and works + myCheckLib( [ "boost_system" + boostCompiler + "-mt" + boostVersion , + "boost_system" + boostCompiler + boostVersion ] ) + + for b in boostLibs: + l = "boost_" + b + myCheckLib( [ l + boostCompiler + "-mt" + boostVersion , + l + boostCompiler + boostVersion ] , + release or not shell) + + if not conf.CheckCXXHeader( "execinfo.h" ): + myenv.Append( CPPDEFINES=[ "NOEXECINFO" ] ) + + if needJava: + for j in javaLibs: + myCheckLib( j , True , True ) + + if nix and needPcre: + myCheckLib( "pcrecpp" , True ) + myCheckLib( "pcre" , True ) + + myenv["_HAVEPCAP"] = myCheckLib( ["pcap", "wpcap"] ) + removeIfInList( myenv["LIBS"] , "pcap" ) + removeIfInList( myenv["LIBS"] , "wpcap" ) + + for m in modules: + m.configure( conf , myenv ) + + # this is outside of usesm block so don't have to rebuild for java + if windows: + myenv.Append( CPPDEFINES=[ "XP_WIN" ] ) + else: + myenv.Append( CPPDEFINES=[ "XP_UNIX" ] ) + + if solaris: + conf.CheckLib( "nsl" ) + + if usesm: + + myCheckLib( [ "mozjs" , "js", "js_static" ] , True ) + mozHeader = "js" + if bigLibString(myenv).find( "mozjs" ) >= 0: + mozHeader = "mozjs" + + if not conf.CheckHeader( mozHeader + "/jsapi.h" ): + if conf.CheckHeader( "jsapi.h" ): + myenv.Append( CPPDEFINES=[ "OLDJS" ] ) + else: + print( "no spider monkey headers!" ) + Exit(1) + + if usev8: + if debugBuild: + myCheckLib( [ "v8_g" , "v8" ] , True ) + else: + myCheckLib( "v8" , True ) + + if shell: + haveReadLine = False + if darwin: + myenv.Append( CPPDEFINES=[ "USE_READLINE" ] ) + if force64: + myCheckLib( "readline" , True ) + myCheckLib( "ncurses" , True ) + else: + myenv.Append( LINKFLAGS=" /usr/lib/libreadline.dylib " ) + elif myCheckLib( "readline" , release and nix , staticOnly=release ): + myenv.Append( CPPDEFINES=[ "USE_READLINE" ] ) + myCheckLib( "ncurses" , staticOnly=release ) + myCheckLib( "tinfo" , staticOnly=release ) + else: + print( "warning: no readline, shell will be a bit ugly" ) + + if linux: + myCheckLib( "rt" , True ) + + # requires ports devel/libexecinfo to be installed + if freebsd: + myCheckLib( "execinfo", True ) + env.Append( LIBS=[ "execinfo" ] ) + + return conf.Finish() + +env = doConfigure( env ) + +# --- js concat --- + +def concatjs(target, source, env): + + outFile = str( target[0] ) + + fullSource = "" + + first = True + + for s in source: + f = open( str(s) , 'r' ) + for l in f: + l = l.split("//")[0].strip() + if len ( l ) == 0: + continue + + if l == "}": + fullSource += "}" + continue + + if first: + first = False + else: + fullSource += "\n" + + fullSource += l + + fullSource += "\n" + + fullSource = re.compile( r'/\*\*.*?\*/' , re.M | re.S ).sub( "" , fullSource ) + + out = open( outFile , 'w' ) + out.write( fullSource ) + + return None + +jsBuilder = Builder(action = concatjs, + suffix = '.jsall', + src_suffix = '.js') + +env.Append( BUILDERS={'JSConcat' : jsBuilder}) + +# --- jsh --- + +def jsToH(target, source, env): + + outFile = str( target[0] ) + if len( source ) != 1: + raise Exception( "wrong" ) + + h = "const char * jsconcatcode" + outFile.split( "mongo" )[-1].replace( "-" , "_").split( ".cpp")[0] + " = \n" + + for l in open( str(source[0]) , 'r' ): + l = l.strip() + l = l.split( "//" )[0] + l = l.replace( '\\' , "\\\\" ) + l = l.replace( '"' , "\\\"" ) + + + h += '"' + l + "\\n\"\n " + + h += ";\n\n" + + out = open( outFile , 'w' ) + out.write( h ) + + return None + +jshBuilder = Builder(action = jsToH, + suffix = '.cpp', + src_suffix = '.jsall') + +env.Append( BUILDERS={'JSHeader' : jshBuilder}) + + +# --- targets ---- + +clientEnv = env.Clone(); +clientEnv.Append( CPPPATH=["../"] ) +clientEnv.Prepend( LIBS=[ "mongoclient"] ) +clientEnv.Prepend( LIBPATH=["."] ) +l = clientEnv[ "LIBS" ] +removeIfInList( l , "pcre" ) +removeIfInList( l , "pcrecpp" ) + +testEnv = env.Clone() +testEnv.Append( CPPPATH=["../"] ) +testEnv.Prepend( LIBS=[ "mongotestfiles" ] ) +testEnv.Prepend( LIBPATH=["."] ) + + +# ----- TARGETS ------ + +def checkErrorCodes(): + import buildscripts.errorcodes as x + if x.checkErrorCodes() == False: + print( "next id to use:" + str( x.getNextCode() ) ) + Exit(-1) + +checkErrorCodes() + +# main db target +mongod = env.Program( "mongod" , commonFiles + coreDbFiles + serverOnlyFiles + [ "db/db.cpp" ] ) +Default( mongod ) + +# tools +allToolFiles = commonFiles + coreDbFiles + serverOnlyFiles + [ "client/gridfs.cpp", "tools/tool.cpp" ] +env.Program( "mongodump" , allToolFiles + [ "tools/dump.cpp" ] ) +env.Program( "mongorestore" , allToolFiles + [ "tools/restore.cpp" ] ) + +env.Program( "mongoexport" , allToolFiles + [ "tools/export.cpp" ] ) +env.Program( "mongoimport" , allToolFiles + [ "tools/import.cpp" ] ) + +env.Program( "mongofiles" , allToolFiles + [ "tools/files.cpp" ] ) + +env.Program( "mongobridge" , allToolFiles + [ "tools/bridge.cpp" ] ) + +# mongos +mongos = env.Program( "mongos" , commonFiles + coreDbFiles + coreServerFiles + shardServerFiles ) + +# c++ library +clientLibName = str( env.Library( "mongoclient" , allClientFiles )[0] ) +env.Library( "mongotestfiles" , commonFiles + coreDbFiles + serverOnlyFiles + ["client/gridfs.cpp"]) + +clientTests = [] + +# examples +clientTests += [ clientEnv.Program( "firstExample" , [ "client/examples/first.cpp" ] ) ] +clientTests += [ clientEnv.Program( "secondExample" , [ "client/examples/second.cpp" ] ) ] +clientTests += [ clientEnv.Program( "whereExample" , [ "client/examples/whereExample.cpp" ] ) ] +clientTests += [ clientEnv.Program( "authTest" , [ "client/examples/authTest.cpp" ] ) ] + +# testing +test = testEnv.Program( "test" , Glob( "dbtests/*.cpp" ) ) +perftest = testEnv.Program( "perftest", [ "dbtests/framework.cpp" , "dbtests/perf/perftest.cpp" ] ) +clientTests += [ clientEnv.Program( "clientTest" , [ "client/examples/clientTest.cpp" ] ) ] + +# --- sniffer --- +mongosniff_built = False +if darwin or clientEnv["_HAVEPCAP"]: + mongosniff_built = True + sniffEnv = clientEnv.Clone() + if not windows: + sniffEnv.Append( LIBS=[ "pcap" ] ) + else: + sniffEnv.Append( LIBS=[ "wpcap" ] ) + sniffEnv.Program( "mongosniff" , "tools/sniffer.cpp" ) + +# --- shell --- + +env.JSConcat( "shell/mongo.jsall" , ["shell/utils.js","shell/db.js","shell/mongo.js","shell/mr.js","shell/query.js","shell/collection.js"] ) +env.JSHeader( "shell/mongo.jsall" ) + +env.JSConcat( "shell/mongo-server.jsall" , [ "shell/servers.js"] ) +env.JSHeader( "shell/mongo-server.jsall" ) + + +shellEnv = env.Clone(); + +if release and ( ( darwin and force64 ) or linux64 ): + shellEnv["LINKFLAGS"] = env["LINKFLAGS_CLEAN"] + shellEnv["LIBS"] = env["LIBS_CLEAN"] + shellEnv["SLIBS"] = "" + +if noshell: + print( "not building shell" ) +elif not onlyServer: + weird = force64 and not windows and not solaris + + if weird: + shellEnv["CFLAGS"].remove("-m64") + shellEnv["CXXFLAGS"].remove("-m64") + shellEnv["LINKFLAGS"].remove("-m64") + shellEnv["CPPPATH"].remove( "/usr/64/include" ) + shellEnv["LIBPATH"].remove( "/usr/64/lib" ) + shellEnv.Append( CPPPATH=[ "/sw/include" , "/opt/local/include"] ) + shellEnv.Append( LIBPATH=[ "/sw/lib/", "/opt/local/lib" , "/usr/lib" ] ) + + l = shellEnv["LIBS"] + if linux64: + removeIfInList( l , "java" ) + removeIfInList( l , "jvm" ) + + removeIfInList( l , "pcre" ) + removeIfInList( l , "pcrecpp" ) + + if windows: + shellEnv.Append( LIBS=["winmm.lib"] ) + + coreShellFiles = [ "shell/dbshell.cpp" , "shell/utils.cpp" , "shell/mongo-server.cpp" ] + + if weird: + shell32BitFiles = coreShellFiles + for f in allClientFiles: + shell32BitFiles.append( "32bit/" + str( f ) ) + shellEnv.VariantDir( "32bit" , "." ) + else: + shellEnv.Prepend( LIBPATH=[ "." ] ) + + shellEnv = doConfigure( shellEnv , needPcre=False , needJava=False , shell=True ) + + if weird: + mongo = shellEnv.Program( "mongo" , shell32BitFiles ) + else: + shellEnv.Prepend( LIBS=[ "mongoclient"] ) + mongo = shellEnv.Program( "mongo" , coreShellFiles ) + + if weird: + Depends( "32bit/shell/mongo.cpp" , "shell/mongo.cpp" ) + Depends( "32bit/shell/mongo-server.cpp" , "shell/mongo-server.cpp" ) + + +# ---- RUNNING TESTS ---- + +testEnv.Alias( "dummySmokeSideEffect", [], [] ) + +def addSmoketest( name, deps, actions ): + if type( actions ) == type( list() ): + actions = [ testSetup ] + actions + else: + actions = [ testSetup, actions ] + testEnv.Alias( name, deps, actions ) + testEnv.AlwaysBuild( name ) + # Prevent smoke tests from running in parallel + testEnv.SideEffect( "dummySmokeSideEffect", name ) + +def ensureDir( name ): + d = os.path.dirname( name ) + if not os.path.exists( d ): + print( "Creating dir: " + name ); + os.makedirs( d ) + if not os.path.exists( d ): + print( "Failed to create dir: " + name ); + Exit( 1 ) + +def ensureTestDirs(): + ensureDir( "/tmp/unittest/" ) + ensureDir( "/data/" ) + ensureDir( "/data/db/" ) + +def testSetup( env , target , source ): + ensureTestDirs() + +if len( COMMAND_LINE_TARGETS ) == 1 and str( COMMAND_LINE_TARGETS[0] ) == "test": + ensureDir( "/tmp/unittest/" ); + +addSmoketest( "smoke", [ add_exe( "test" ) ] , [ test[ 0 ].abspath ] ) +addSmoketest( "smokePerf", [ "perftest" ] , [ perftest[ 0 ].abspath ] ) + +clientExec = [ x[0].abspath for x in clientTests ] +def runClientTests( env, target, source ): + global clientExec + global mongodForTestsPort + import subprocess + for i in clientExec: + if subprocess.call( [ i, "--port", mongodForTestsPort ] ) != 0: + return True + if subprocess.Popen( [ mongod[0].abspath, "msg", "ping", mongodForTestsPort ], stdout=subprocess.PIPE ).communicate()[ 0 ].count( "****ok" ) == 0: + return True + if subprocess.call( [ mongod[0].abspath, "msg", "ping", mongodForTestsPort ] ) != 0: + return True + return False +addSmoketest( "smokeClient" , clientExec, runClientTests ) +addSmoketest( "mongosTest" , [ mongos[0].abspath ] , [ mongos[0].abspath + " --test" ] ) + +def jsSpec( suffix ): + import os.path + args = [ os.path.dirname( mongo[0].abspath ), "jstests" ] + suffix + return apply( os.path.join, args ) + +def jsDirTestSpec( dir ): + return mongo[0].abspath + " --nodb " + jsSpec( [ dir ] ) + +def runShellTest( env, target, source ): + global mongodForTestsPort + import subprocess + target = str( target[0] ) + if target == "smokeJs": + spec = [ jsSpec( [ "_runner.js" ] ) ] + elif target == "smokeQuota": + g = Glob( jsSpec( [ "quota" ] ) ) + spec = [ x.abspath for x in g ] + elif target == "smokeJsPerf": + g = Glob( jsSpec( [ "perf" ] ) ) + spec = [ x.abspath for x in g ] + elif target == "smokeJsSlow": + spec = [x.abspath for x in Glob(jsSpec(["slow/*"]))] + else: + print( "invalid target for runShellTest()" ) + Exit( 1 ) + return subprocess.call( [ mongo[0].abspath, "--port", mongodForTestsPort ] + spec ) + +# These tests require the mongo shell +if not onlyServer and not noshell: + addSmoketest( "smokeJs", [add_exe("mongo")], runShellTest ) + addSmoketest( "smokeClone", [ "mongo", "mongod" ], [ jsDirTestSpec( "clone" ) ] ) + addSmoketest( "smokeRepl", [ "mongo", "mongod", "mongobridge" ], [ jsDirTestSpec( "repl" ) ] ) + addSmoketest( "smokeDisk", [ add_exe( "mongo" ), add_exe( "mongod" ) ], [ jsDirTestSpec( "disk" ) ] ) + addSmoketest( "smokeSharding", [ "mongo", "mongod", "mongos" ], [ jsDirTestSpec( "sharding" ) ] ) + addSmoketest( "smokeJsPerf", [ "mongo" ], runShellTest ) + addSmoketest("smokeJsSlow", [add_exe("mongo")], runShellTest) + addSmoketest( "smokeQuota", [ "mongo" ], runShellTest ) + addSmoketest( "smokeTool", [ add_exe( "mongo" ) ], [ jsDirTestSpec( "tool" ) ] ) + +mongodForTests = None +mongodForTestsPort = "27017" + +def startMongodForTests( env, target, source ): + global mongodForTests + global mongodForTestsPort + global mongod + if mongodForTests: + return + mongodForTestsPort = "40000" + import os + ensureTestDirs() + dirName = "/data/db/sconsTests/" + ensureDir( dirName ) + from subprocess import Popen + mongodForTests = Popen( [ mongod[0].abspath, "--port", mongodForTestsPort, "--dbpath", dirName, "--nohttpinterface" ] ) + # Wait for mongod to start + import time + time.sleep( 5 ) + if mongodForTests.poll() is not None: + print( "Failed to start mongod" ) + mongodForTests = None + Exit( 1 ) + +def stopMongodForTests(): + global mongodForTests + if not mongodForTests: + return + if mongodForTests.poll() is not None: + print( "Failed to start mongod" ) + mongodForTests = None + Exit( 1 ) + try: + # This function not available in Python 2.5 + mongodForTests.terminate() + except AttributeError: + if windows: + import win32process + win32process.TerminateProcess(mongodForTests._handle, -1) + else: + from os import kill + kill( mongodForTests.pid, 15 ) + mongodForTests.wait() + +testEnv.Alias( "startMongod", [add_exe("mongod")], [startMongodForTests] ); +testEnv.AlwaysBuild( "startMongod" ); +testEnv.SideEffect( "dummySmokeSideEffect", "startMongod" ) + +def addMongodReqTargets( env, target, source ): + mongodReqTargets = [ "smokeClient", "smokeJs", "smokeQuota" ] + for target in mongodReqTargets: + testEnv.Depends( target, "startMongod" ) + testEnv.Depends( "smokeAll", target ) + +testEnv.Alias( "addMongodReqTargets", [], [addMongodReqTargets] ) +testEnv.AlwaysBuild( "addMongodReqTargets" ) + +testEnv.Alias( "smokeAll", [ "smoke", "mongosTest", "smokeClone", "smokeRepl", "addMongodReqTargets", "smokeDisk", "smokeSharding", "smokeTool" ] ) +testEnv.AlwaysBuild( "smokeAll" ) + +def addMongodReqNoJsTargets( env, target, source ): + mongodReqTargets = [ "smokeClient" ] + for target in mongodReqTargets: + testEnv.Depends( target, "startMongod" ) + testEnv.Depends( "smokeAllNoJs", target ) + +testEnv.Alias( "addMongodReqNoJsTargets", [], [addMongodReqNoJsTargets] ) +testEnv.AlwaysBuild( "addMongodReqNoJsTargets" ) + +testEnv.Alias( "smokeAllNoJs", [ "smoke", "mongosTest", "addMongodReqNoJsTargets" ] ) +testEnv.AlwaysBuild( "smokeAllNoJs" ) + +import atexit +atexit.register( stopMongodForTests ) + +def recordPerformance( env, target, source ): + from buildscripts import benchmark_tools + global perftest + import subprocess, re + p = subprocess.Popen( [ perftest[0].abspath ], stdout=subprocess.PIPE ) + b = p.communicate()[ 0 ] + print( "perftest results:" ); + print( b ); + if p.returncode != 0: + return True + entries = re.findall( "{.*?}", b ) + import sys + for e in entries: + matches = re.match( "{'(.*?)': (.*?)}", e ) + name = matches.group( 1 ) + val = float( matches.group( 2 ) ) + sub = { "benchmark": { "project": "http://github.com/mongodb/mongo", "description": "" }, "trial": {} } + sub[ "benchmark" ][ "name" ] = name + sub[ "benchmark" ][ "tags" ] = [ "c++", re.match( "(.*)__", name ).group( 1 ) ] + sub[ "trial" ][ "server_hash" ] = getGitVersion() + sub[ "trial" ][ "client_hash" ] = "" + sub[ "trial" ][ "result" ] = val + try: + print(benchmark_tools.post_data(sub)) + except: + print( "exception posting perf results" ) + print( sys.exc_info() ) + return False + +addSmoketest( "recordPerf", [ "perftest" ] , [ recordPerformance ] ) + +def run_shell_tests(env, target, source): + from buildscripts import test_shell + test_shell.mongo_path = windows and "mongo.exe" or "mongo" + test_shell.run_tests() + +env.Alias("test_shell", [], [run_shell_tests]) +env.AlwaysBuild("test_shell") + +# ---- INSTALL ------- + +def getSystemInstallName(): + n = platform + "-" + processor + if static: + n += "-static" + if nix and os.uname()[2].startswith( "8." ): + n += "-tiger" + + try: + import settings + if "distmod" in dir( settings ): + n = n + "-" + str( settings.distmod ) + except: + pass + + return n + +def getCodeVersion(): + fullSource = open( "stdafx.cpp" , "r" ).read() + allMatches = re.findall( r"versionString.. = \"(.*?)\"" , fullSource ); + if len(allMatches) != 1: + print( "can't find version # in code" ) + return None + return allMatches[0] + +def getDistName( sofar ): + global distName + global dontReplacePackage + + if distName is not None: + return distName + + if str( COMMAND_LINE_TARGETS[0] ) == "s3dist": + version = getCodeVersion() + if not version.endswith( "+" ) and not version.endswith("-"): + print( "got real code version, doing release build for: " + version ) + dontReplacePackage = True + distName = version + return version + + + return getGitBranchString( "" , "-" ) + today.strftime( "%Y-%m-%d" ) + + +if distBuild: + from datetime import date + today = date.today() + installDir = "mongodb-" + getSystemInstallName() + "-" + installDir += getDistName( installDir ) + print "going to make dist: " + installDir + +# binaries + +def checkGlibc(target,source,env): + import subprocess + stringProcess = subprocess.Popen( [ "strings" , str( target[0] ) ] , stdout=subprocess.PIPE ) + stringResult = stringProcess.communicate()[0] + if stringResult.count( "GLIBC_2.4" ) > 0: + print( "************* " + str( target[0] ) + " has GLIBC_2.4 dependencies!" ) + Exit(-3) + +allBinaries = [] + +def installBinary( e , name ): + global allBinaries + + if windows: + e.Alias( name , name + ".exe" ) + name += ".exe" + + inst = e.Install( installDir + "/bin" , name ) + + fullInstallName = installDir + "/bin/" + name + + allBinaries += [ name ] + if solaris or linux: + e.AddPostAction( inst, e.Action( 'strip ' + fullInstallName ) ) + + if linux and len( COMMAND_LINE_TARGETS ) == 1 and str( COMMAND_LINE_TARGETS[0] ) == "s3dist": + e.AddPostAction( inst , checkGlibc ) + + if nix: + e.AddPostAction( inst , e.Action( 'chmod 755 ' + fullInstallName ) ) + +installBinary( env , "mongodump" ) +installBinary( env , "mongorestore" ) + +installBinary( env , "mongoexport" ) +installBinary( env , "mongoimport" ) + +installBinary( env , "mongofiles" ) + +if mongosniff_built: + installBinary(env, "mongosniff") + +installBinary( env , "mongod" ) +installBinary( env , "mongos" ) + +if not noshell: + installBinary( env , "mongo" ) + +env.Alias( "all" , allBinaries ) + + +# NOTE: In some cases scons gets confused between installation targets and build +# dependencies. Here, we use InstallAs instead of Install to prevent such confusion +# on a case-by-case basis. + +#headers +for id in [ "", "util/", "db/" , "client/" ]: + env.Install( installDir + "/include/mongo/" + id , Glob( id + "*.h" ) ) + +#lib +env.Install( installDir + "/" + nixLibPrefix, clientLibName ) +if usejvm: + env.Install( installDir + "/" + nixLibPrefix + "/mongo/jars" , Glob( "jars/*" ) ) + +#textfiles +if distBuild or release: + #don't want to install these /usr/local/ for example + env.Install( installDir , "distsrc/README" ) + env.Install( installDir , "distsrc/THIRD-PARTY-NOTICES" ) + env.Install( installDir , "distsrc/GNU-AGPL-3.0" ) + +#final alias +env.Alias( "install" , installDir ) + +# aliases +if windows: + env.Alias( "mongoclient" , "mongoclient.lib" ) +else: + env.Alias( "mongoclient" , "libmongoclient.a" ) + + +# ---- CONVENIENCE ---- + +def tabs( env, target, source ): + from subprocess import Popen, PIPE + from re import search, match + diff = Popen( [ "git", "diff", "-U0", "origin", "master" ], stdout=PIPE ).communicate()[ 0 ] + sourceFile = False + for line in diff.split( "\n" ): + if match( "diff --git", line ): + sourceFile = not not search( "\.(h|hpp|c|cpp)\s*$", line ) + if sourceFile and match( "\+ *\t", line ): + return True + return False +env.Alias( "checkSource", [], [ tabs ] ) +env.AlwaysBuild( "checkSource" ) + +def gitPush( env, target, source ): + import subprocess + return subprocess.call( [ "git", "push" ] ) +env.Alias( "push", [ ".", "smoke", "checkSource" ], gitPush ) +env.AlwaysBuild( "push" ) + + +# ---- deploying --- + +def s3push( localName , remoteName=None , remotePrefix=None , fixName=True , platformDir=True ): + + if remotePrefix is None: + if distName is None: + remotePrefix = getGitBranchString( "-" ) + "-latest" + else: + remotePrefix = "-" + distName + + sys.path.append( "." ) + sys.path.append( ".." ) + sys.path.append( "../../" ) + + import simples3 + import settings + + s = simples3.S3Bucket( settings.bucket , settings.id , settings.key ) + + if remoteName is None: + remoteName = localName + + if fixName: + (root,dot,suffix) = localName.rpartition( "." ) + name = remoteName + "-" + getSystemInstallName() + name += remotePrefix + if dot == "." : + name += "." + suffix + name = name.lower() + else: + name = remoteName + + if platformDir: + name = platform + "/" + name + + print( "uploading " + localName + " to http://s3.amazonaws.com/" + s.name + "/" + name ) + if dontReplacePackage: + for ( key , modify , etag , size ) in s.listdir( prefix=name ): + print( "error: already a file with that name, not uploading" ) + Exit(2) + s.put( name , open( localName , "rb" ).read() , acl="public-read" ); + print( " done uploading!" ) + +def s3shellpush( env , target , source ): + s3push( "mongo" , "mongo-shell" ) + +env.Alias( "s3shell" , [ "mongo" ] , [ s3shellpush ] ) +env.AlwaysBuild( "s3shell" ) + +def s3dist( env , target , source ): + s3push( distFile , "mongodb" ) + +env.Append( TARFLAGS=" -z " ) +if windows: + distFile = installDir + ".zip" + env.Zip( distFile , installDir ) +else: + distFile = installDir + ".tgz" + env.Tar( distFile , installDir ) + +env.Alias( "dist" , distFile ) +env.Alias( "s3dist" , [ "install" , distFile ] , [ s3dist ] ) +env.AlwaysBuild( "s3dist" ) + +def clean_old_dist_builds(env, target, source): + prefix = "mongodb-%s-%s" % (platform, processor) + filenames = sorted(os.listdir(".")) + filenames = [x for x in filenames if x.startswith(prefix)] + to_keep = [x for x in filenames if x.endswith(".tgz") or x.endswith(".zip")][-2:] + for filename in [x for x in filenames if x not in to_keep]: + print "removing %s" % filename + try: + shutil.rmtree(filename) + except: + os.remove(filename) + +env.Alias("dist_clean", [], [clean_old_dist_builds]) +env.AlwaysBuild("dist_clean") diff --git a/buildscripts/__init__.py b/buildscripts/__init__.py new file mode 100644 index 0000000..7f1b703 --- /dev/null +++ b/buildscripts/__init__.py @@ -0,0 +1,10 @@ + +import hacks_ubuntu +import os; + +def findHacks( un ): + if un[0] == 'Linux' and (os.path.exists("/etc/debian_version") or + os.path.exists("/etc/arch-release") or + un[3].find("Ubuntu") >= 0): + return hacks_ubuntu + return None diff --git a/buildscripts/bb.py b/buildscripts/bb.py new file mode 100644 index 0000000..1e87828 --- /dev/null +++ b/buildscripts/bb.py @@ -0,0 +1,23 @@ +# bb tools + +import os +import re + +def checkOk(): + dir = os.getcwd() + m = re.compile( ".*/.*_V(\d+\.\d+)/mongo" ).findall( dir ) + if len(m) == 0: + return + if len(m) > 1: + raise Exception( "unexpected: " + str(m) ) + + m = "v" + m[0] + print( m ) + print( "excpted version [" + m + "]" ) + + from subprocess import Popen, PIPE + diff = Popen( [ "git", "diff", "origin/v1.2" ], stdout=PIPE ).communicate()[ 0 ] + if len(diff) > 0: + print( diff ) + raise Exception( "build bot broken?" ) + diff --git a/buildscripts/benchmark_tools.py b/buildscripts/benchmark_tools.py new file mode 100644 index 0000000..54a86b0 --- /dev/null +++ b/buildscripts/benchmark_tools.py @@ -0,0 +1,62 @@ +import os +import urllib +import urllib2 +import sys + +try: + import json +except: + import simplejson as json # need simplejson for python < 2.6 + +sys.path.append( "." ) +sys.path.append( ".." ) +sys.path.append( "../../" ) +sys.path.append( "../../../" ) + + +import settings + +def machine_info(extra_info=""): + """Get a dict representing the "machine" section of a benchmark result. + + ie: + { + "os_name": "OS X", + "os_version": "10.5", + "processor": "2.4 GHz Intel Core 2 Duo", + "memory": "3 GB 667 MHz DDR2 SDRAM", + "extra_info": "Python 2.6" + } + + Must have a settings.py file on sys.path that defines "processor" and "memory" + variables. + """ + machine = {} + (machine["os_name"], _, machine["os_version"], _, _) = os.uname() + machine["processor"] = settings.processor + machine["memory"] = settings.memory + machine["extra_info"] = extra_info + return machine + +def post_data(data, machine_extra_info="", post_url="http://mongo-db.appspot.com/benchmark"): + """Post a benchmark data point. + + data should be a Python dict that looks like: + { + "benchmark": { + "project": "http://github.com/mongodb/mongo-python-driver", + "name": "insert test", + "description": "test inserting 10000 documents with the C extension enabled", + "tags": ["insert", "python"] + }, + "trial": { + "server_hash": "4f5a8d52f47507a70b6c625dfb5dbfc87ba5656a", + "client_hash": "8bf2ad3d397cbde745fd92ad41c5b13976fac2b5", + "result": 67.5, + "extra_info": "some logs or something" + } + } + """ + data["machine"] = machine_info(machine_extra_info) + urllib2.urlopen(post_url, urllib.urlencode({"payload": json.dumps(data)})) + return data diff --git a/buildscripts/errorcodes.py b/buildscripts/errorcodes.py new file mode 100644 index 0000000..7a7e017 --- /dev/null +++ b/buildscripts/errorcodes.py @@ -0,0 +1,85 @@ +#!/usr/bin/python + +import os +import sys +import re + +def getAllSourceFiles( arr=None , prefix="." ): + if arr is None: + arr = [] + + for x in os.listdir( prefix ): + if x.startswith( "." ) or x.startswith( "pcre-" ) or x.startswith( "32bit" ) or x.startswith( "mongodb-" ): + continue + full = prefix + "/" + x + if os.path.isdir( full ): + getAllSourceFiles( arr , full ) + else: + if full.endswith( ".cpp" ) or full.endswith( ".h" ) or full.endswith( ".c" ): + arr.append( full ) + + return arr + +assertNames = [ "uassert" , "massert" ] + +def assignErrorCodes(): + cur = 10000 + for root in assertNames: + for x in getAllSourceFiles(): + print( x ) + didAnything = False + fixed = "" + for line in open( x ): + s = line.partition( root + "(" ) + if s[1] == "" or line.startswith( "#define " + root): + fixed += line + continue + fixed += s[0] + root + "( " + str( cur ) + " , " + s[2] + cur = cur + 1 + didAnything = True + if didAnything: + out = open( x , 'w' ) + out.write( fixed ) + out.close() + + +def readErrorCodes( callback ): + ps = [ re.compile( "([um]asser(t|ted)) *\( *(\d+)" ) , + re.compile( "(User|Msg)Exceptio(n)\( *(\d+)" ) + ] + for x in getAllSourceFiles(): + lineNum = 1 + for line in open( x ): + for p in ps: + for m in p.findall( line ): + callback( x , lineNum , line , m[2] ) + lineNum = lineNum + 1 + + +def getNextCode(): + highest = [0] + def check( fileName , lineNum , line , code ): + code = int( code ) + if code > highest[0]: + highest[0] = code + readErrorCodes( check ) + return highest[0] + 1 + +def checkErrorCodes(): + seen = {} + errors = [] + def checkDups( fileName , lineNum , line , code ): + if code in seen: + print( "DUPLICATE IDS" ) + print( "%s:%d:%s %s" % ( fileName , lineNum , line.strip() , code ) ) + print( "%s:%d:%s %s" % seen[code] ) + errors.append( seen[code] ) + seen[code] = ( fileName , lineNum , line , code ) + readErrorCodes( checkDups ) + return len( errors ) == 0 + +if __name__ == "__main__": + ok = checkErrorCodes() + print( "ok:" + str( ok ) ) + print( "next: " + str( getNextCode() ) ) + diff --git a/buildscripts/hacks_ubuntu.py b/buildscripts/hacks_ubuntu.py new file mode 100644 index 0000000..67c5d78 --- /dev/null +++ b/buildscripts/hacks_ubuntu.py @@ -0,0 +1,47 @@ + +import os + +def insert( env , options ): + + if not foundxulrunner( env , options ): + if os.path.exists( "usr/include/mozjs/" ): + env.Append( CPPDEFINES=[ "MOZJS" ] ) + + +def foundxulrunner( env , options ): + best = None + + for x in os.listdir( "/usr/include" ): + if x.find( "xulrunner" ) != 0: + continue + if x == "xulrunner": + best = x + break + best = x + + + if best is None: + print( "warning: using ubuntu without xulrunner-dev. we reccomend installing it" ) + return False + + incroot = "/usr/include/" + best + "/" + libroot = "/usr/lib" + if options["linux64"] and os.path.exists("/usr/lib64"): + libroot += "64"; + libroot += "/" + best + + + if not os.path.exists( libroot ): + print( "warning: found xulrunner include but not lib for: " + best ) + return False + + env.Prepend( LIBPATH=[ libroot ] ) + env.Prepend( RPATH=[ libroot ] ) + + env.Prepend( CPPPATH=[ incroot + "stable/" , + incroot + "unstable/" ] ) + + env.Append( CPPDEFINES=[ "XULRUNNER" , "OLDJS" ] ) + if best.find( "1.9.0" ) >= 0: + env.Append( CPPDEFINES=[ "XULRUNNER190" ] ) + return True diff --git a/buildscripts/sourcepush.py b/buildscripts/sourcepush.py new file mode 100644 index 0000000..e389afb --- /dev/null +++ b/buildscripts/sourcepush.py @@ -0,0 +1,70 @@ + +import os +import sys + +sys.path.append( "." ) +sys.path.append( ".." ) +sys.path.append( "../../" ) +sys.path.append( "../../../" ) + +import simples3 +import settings +import subprocess + +# this pushes all source balls as tgz and zip + +def run_git( args ): + cmd = "git " + args + cmd = cmd.split( " " ) + x = subprocess.Popen( ( "git " + args ).split( " " ) , stdout=subprocess.PIPE).communicate() + return x[0] + +def push_tag( bucket , tag , extension , gzip=False ): + localName = "mongodb-src-" + tag + "." + extension + remoteName = "src/" + localName + if gzip: + remoteName += ".gz" + for ( key , modify , etag , size ) in bucket.listdir( prefix=remoteName ): + print( "found old: " + key + " uploaded on: " + str( modify ) ) + return + + if os.path.exists( localName ): + os.remove( localName ) + + print( "need to do: " + remoteName ) + + cmd = "archive --format %s --output %s --prefix mongodb-src-%s/ %s" % ( extension , localName , tag , tag ) + run_git( cmd ) + + print( "\t" + cmd ) + + if not os.path.exists( localName ) or os.path.getsize(localName) == 0 : + raise( Exception( "creating archive failed: " + cmd ) ) + + if gzip: + newLocalName = localName + ".gz" + if ( os.path.exists( newLocalName ) ): + os.remove( newLocalName ) + subprocess.call( [ "gzip" , localName ] ) + localName = newLocalName + + if not os.path.exists( localName ) or os.path.getsize(localName) == 0 : + raise( Exception( "gzipping failed" ) ) + + bucket.put( remoteName , open( localName , "rb" ).read() , acl="public-read" ) + print( "\t uploaded to: http://s3.amazonaws.com/%s/%s" % ( bucket.name , remoteName ) ) + + os.remove( localName ) + + +def push_all(): + tags = run_git("tag -l").strip().split( "\n" ) + + bucket = simples3.S3Bucket( settings.bucket , settings.id , settings.key ) + + for tag in tags: + push_tag( bucket , tag , "tar" , True ) + push_tag( bucket , tag , "zip" ) + +if __name__ == "__main__": + push_all() diff --git a/buildscripts/test_shell.py b/buildscripts/test_shell.py new file mode 100644 index 0000000..eb6a032 --- /dev/null +++ b/buildscripts/test_shell.py @@ -0,0 +1,239 @@ +# Copyright 2009 10gen, Inc. +# +# This file is part of MongoDB. +# +# MongoDB is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# MongoDB 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with MongoDB. If not, see <http://www.gnu.org/licenses/>. + +"""Tests for the MongoDB shell. + +Right now these mostly just test that the shell handles command line arguments +appropriately. +""" + +import unittest +import sys +import subprocess +import os + +"""Exit codes for MongoDB.""" +BADOPTS = 2 +NOCONNECT = 255 + +"""Path to the mongo shell executable to be tested.""" +mongo_path = None + +class TestShell(unittest.TestCase): + + def open_mongo(self, args=[]): + """Get a subprocess.Popen instance of the shell with the given args. + """ + return subprocess.Popen([mongo_path] + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr = subprocess.PIPE) + + def setUp(self): + assert mongo_path + + def test_help(self): + mongo_h = self.open_mongo(["-h"]) + mongo_help = self.open_mongo(["--help"]) + + out = mongo_h.communicate() + self.assertEqual(out, mongo_help.communicate()) + self.assert_("usage:" in out[0]) + + self.assertEqual(0, mongo_h.returncode) + self.assertEqual(0, mongo_help.returncode) + + def test_nodb(self): + mongo = self.open_mongo([]) + mongo_nodb = self.open_mongo(["--nodb"]) + + out = mongo_nodb.communicate() + self.assert_("MongoDB shell version" in out[0]) + self.assert_("bye" in out[0]) + self.assert_("couldn't connect" not in out[0]) + self.assertEqual(0, mongo_nodb.returncode) + + out = mongo.communicate() + self.assert_("MongoDB shell version" in out[0]) + self.assert_("bye" not in out[0]) + self.assert_("couldn't connect" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + def test_eval(self): + mongo = self.open_mongo(["--nodb", "--eval", "print('hello world');"]) + out = mongo.communicate() + self.assert_("hello world" in out[0]) + self.assert_("bye" not in out[0]) + self.assertEqual(0, mongo.returncode) + + mongo = self.open_mongo(["--eval"]) + out = mongo.communicate() + self.assert_("required parameter is missing" in out[0]) + self.assertEqual(BADOPTS, mongo.returncode) + + def test_shell(self): + mongo = self.open_mongo(["--nodb", "--shell", "--eval", "print('hello world');"]) + out = mongo.communicate() + self.assert_("hello world" in out[0]) + self.assert_("bye" in out[0]) # the shell started and immediately exited because stdin was empty + self.assertEqual(0, mongo.returncode) + + def test_host_port(self): + mongo = self.open_mongo([]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--host", "localhost"]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: localhost/test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--port", "27018"]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: 127.0.0.1:27018" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--host", "localhost", "--port", "27018"]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: localhost:27018/test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--host"]) + out = mongo.communicate() + self.assert_("required parameter is missing" in out[0]) + self.assertEqual(BADOPTS, mongo.returncode) + + mongo = self.open_mongo(["--port"]) + out = mongo.communicate() + self.assert_("required parameter is missing" in out[0]) + self.assertEqual(BADOPTS, mongo.returncode) + + def test_positionals(self): + dirname = os.path.dirname(__file__) + test_js = os.path.join(dirname, "testdata/test.js") + test_txt = os.path.join(dirname, "testdata/test.txt") + test = os.path.join(dirname, "testdata/test") + non_exist_js = os.path.join(dirname, "testdata/nonexist.js") + non_exist_txt = os.path.join(dirname, "testdata/nonexist.txt") + + mongo = self.open_mongo(["--nodb", test_js]) + out = mongo.communicate() + self.assert_("hello world" in out[0]) + self.assert_("bye" not in out[0]) + self.assertEqual(0, mongo.returncode) + + mongo = self.open_mongo(["--nodb", test_txt]) + out = mongo.communicate() + self.assert_("foobar" in out[0]) + self.assert_("bye" not in out[0]) + self.assertEqual(0, mongo.returncode) + + mongo = self.open_mongo([test_js, test, test_txt]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo([test_txt, test, test_js]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo([test, test_js, test_txt]) + out = mongo.communicate() + self.assert_("url: " + test in out[0]) + self.assert_("connecting to: " + test in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo([non_exist_js, test, test_txt]) + out = mongo.communicate() + self.assert_("url: test" in out[0]) + self.assert_("connecting to: test" in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo([non_exist_txt, test_js, test_txt]) + out = mongo.communicate() + self.assert_("url: " + non_exist_txt in out[0]) + self.assert_("connecting to: " + non_exist_txt in out[0]) + self.assertEqual(NOCONNECT, mongo.returncode) + + def test_multiple_files(self): + dirname = os.path.dirname(__file__) + test_js = os.path.join(dirname, "testdata/test.js") + test_txt = os.path.join(dirname, "testdata/test.txt") + + mongo = self.open_mongo(["--nodb", test_js, test_txt]) + out = mongo.communicate() + self.assert_("hello world" in out[0]) + self.assert_("foobar" in out[0]) + self.assert_("bye" not in out[0]) + self.assertEqual(0, mongo.returncode) + + mongo = self.open_mongo(["--shell", "--nodb", test_js, test_txt]) + out = mongo.communicate() + self.assert_("hello world" in out[0]) + self.assert_("foobar" in out[0]) + self.assert_("bye" in out[0]) + self.assertEqual(0, mongo.returncode) + + # just testing that they don't blow up + def test_username_and_password(self): + mongo = self.open_mongo(["--username", "mike"]) + out = mongo.communicate() + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["-u", "mike"]) + out = mongo.communicate() + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--password", "mike"]) + out = mongo.communicate() + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["-p", "mike"]) + out = mongo.communicate() + self.assertEqual(NOCONNECT, mongo.returncode) + + mongo = self.open_mongo(["--username"]) + out = mongo.communicate() + self.assert_("required parameter is missing" in out[0]) + self.assertEqual(BADOPTS, mongo.returncode) + + mongo = self.open_mongo(["--password"]) + out = mongo.communicate() + self.assert_("required parameter is missing" in out[0]) + self.assertEqual(BADOPTS, mongo.returncode) + + +def run_tests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestShell) + unittest.TextTestRunner(verbosity=1).run(suite) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print "must give the path to shell executable to be tested" + sys.exit() + + mongo_path = sys.argv[1] + run_tests() diff --git a/buildscripts/testdata/test.js b/buildscripts/testdata/test.js new file mode 100644 index 0000000..b058960 --- /dev/null +++ b/buildscripts/testdata/test.js @@ -0,0 +1 @@ +print("hello world"); diff --git a/buildscripts/testdata/test.txt b/buildscripts/testdata/test.txt new file mode 100644 index 0000000..b1c09ce --- /dev/null +++ b/buildscripts/testdata/test.txt @@ -0,0 +1 @@ +print("foobar"); diff --git a/client/clientOnly.cpp b/client/clientOnly.cpp new file mode 100644 index 0000000..f9fc570 --- /dev/null +++ b/client/clientOnly.cpp @@ -0,0 +1,57 @@ +// clientOnly.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "../client/dbclient.h" +#include "../db/dbhelpers.h" +#include "../db/cmdline.h" + +namespace mongo { + + CmdLine cmdLine; + + const char * curNs = "in client mode"; + + bool dbexitCalled = false; + + void dbexit( ExitCode returnCode, const char *whyMsg ) { + dbexitCalled = true; + out() << "dbexit called" << endl; + if ( whyMsg ) + out() << " b/c " << whyMsg << endl; + out() << "exiting" << endl; + ::exit( returnCode ); + } + + bool inShutdown(){ + return dbexitCalled; + } + + string getDbContext() { + return "in client only mode"; + } + + bool haveLocalShardingInfo( const string& ns ){ + return false; + } +/* + auto_ptr<CursorIterator> Helpers::find( const char *ns , BSONObj query , bool requireIndex ){ + uassert( 10000 , "Helpers::find can't be used in client" , 0 ); + return auto_ptr<CursorIterator>(0); + } +*/ +} diff --git a/client/connpool.cpp b/client/connpool.cpp new file mode 100644 index 0000000..b332bae --- /dev/null +++ b/client/connpool.cpp @@ -0,0 +1,122 @@ +/* connpool.cpp +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// _ todo: reconnect? + +#include "stdafx.h" +#include "connpool.h" +#include "../db/commands.h" + +namespace mongo { + + DBConnectionPool pool; + + DBClientBase* DBConnectionPool::get(const string& host) { + boostlock L(poolMutex); + + PoolForHost *&p = pools[host]; + if ( p == 0 ) + p = new PoolForHost(); + if ( p->pool.empty() ) { + string errmsg; + DBClientBase *c; + if( host.find(',') == string::npos ) { + DBClientConnection *cc = new DBClientConnection(true); + log(2) << "creating new connection for pool to:" << host << endl; + if ( !cc->connect(host.c_str(), errmsg) ) { + delete cc; + uassert( 11002 , (string)"dbconnectionpool: connect failed " + host , false); + return 0; + } + c = cc; + onCreate( c ); + } + else { + DBClientPaired *p = new DBClientPaired(); + if( !p->connect(host) ) { + delete p; + uassert( 11003 , (string)"dbconnectionpool: connect failed [2] " + host , false); + return 0; + } + c = p; + } + return c; + } + DBClientBase *c = p->pool.top(); + p->pool.pop(); + onHandedOut( c ); + return c; + } + + void DBConnectionPool::flush(){ + boostlock L(poolMutex); + for ( map<string,PoolForHost*>::iterator i = pools.begin(); i != pools.end(); i++ ){ + PoolForHost* p = i->second; + + vector<DBClientBase*> all; + while ( ! p->pool.empty() ){ + DBClientBase * c = p->pool.top(); + p->pool.pop(); + all.push_back( c ); + bool res; + c->isMaster( res ); + } + + for ( vector<DBClientBase*>::iterator i=all.begin(); i != all.end(); i++ ){ + p->pool.push( *i ); + } + } + } + + void DBConnectionPool::addHook( DBConnectionHook * hook ){ + _hooks.push_back( hook ); + } + + void DBConnectionPool::onCreate( DBClientBase * conn ){ + if ( _hooks.size() == 0 ) + return; + + for ( list<DBConnectionHook*>::iterator i = _hooks.begin(); i != _hooks.end(); i++ ){ + (*i)->onCreate( conn ); + } + } + + void DBConnectionPool::onHandedOut( DBClientBase * conn ){ + if ( _hooks.size() == 0 ) + return; + + for ( list<DBConnectionHook*>::iterator i = _hooks.begin(); i != _hooks.end(); i++ ){ + (*i)->onHandedOut( conn ); + } + } + + class PoolFlushCmd : public Command { + public: + PoolFlushCmd() : Command( "connpoolsync" ){} + virtual bool run(const char*, mongo::BSONObj&, std::string&, mongo::BSONObjBuilder& result, bool){ + pool.flush(); + result << "ok" << 1; + return true; + } + virtual bool slaveOk(){ + return true; + } + + } poolFlushCmd; + +} // namespace mongo diff --git a/client/connpool.h b/client/connpool.h new file mode 100644 index 0000000..34ed498 --- /dev/null +++ b/client/connpool.h @@ -0,0 +1,135 @@ +/** @file connpool.h */ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <stack> +#include "dbclient.h" + +namespace mongo { + + struct PoolForHost { + std::stack<DBClientBase*> pool; + }; + + class DBConnectionHook { + public: + virtual ~DBConnectionHook(){} + + virtual void onCreate( DBClientBase * conn ){} + virtual void onHandedOut( DBClientBase * conn ){} + + }; + + /** Database connection pool. + + Generally, use ScopedDbConnection and do not call these directly. + + This class, so far, is suitable for use with unauthenticated connections. + Support for authenticated connections requires some adjustements: please + request... + + Usage: + + { + ScopedDbConnection c("myserver"); + c.conn()... + } + */ + class DBConnectionPool { + boost::mutex poolMutex; + map<string,PoolForHost*> pools; // servername -> pool + list<DBConnectionHook*> _hooks; + + void onCreate( DBClientBase * conn ); + void onHandedOut( DBClientBase * conn ); + public: + void flush(); + DBClientBase *get(const string& host); + void release(const string& host, DBClientBase *c) { + if ( c->isFailed() ) + return; + boostlock L(poolMutex); + pools[host]->pool.push(c); + } + void addHook( DBConnectionHook * hook ); + }; + + extern DBConnectionPool pool; + + /** Use to get a connection from the pool. On exceptions things + clean up nicely. + */ + class ScopedDbConnection { + const string host; + DBClientBase *_conn; + public: + /** get the associated connection object */ + DBClientBase* operator->(){ + uassert( 11004 , "did you call done already" , _conn ); + return _conn; + } + + /** get the associated connection object */ + DBClientBase& conn() { + uassert( 11005 , "did you call done already" , _conn ); + return *_conn; + } + + /** throws UserException if can't connect */ + ScopedDbConnection(const string& _host) : + host(_host), _conn( pool.get(_host) ) { + //cout << " for: " << _host << " got conn: " << _conn << endl; + } + + /** Force closure of the connection. You should call this if you leave it in + a bad state. Destructor will do this too, but it is verbose. + */ + void kill() { + delete _conn; + _conn = 0; + } + + /** Call this when you are done with the connection. + + If you do not call done() before this object goes out of scope, + we can't be sure we fully read all expected data of a reply on the socket. so + we don't try to reuse the connection in that situation. + */ + void done() { + if ( ! _conn ) + return; + + /* we could do this, but instead of assume one is using autoreconnect mode on the connection + if ( _conn->isFailed() ) + kill(); + else + */ + pool.release(host, _conn); + _conn = 0; + } + + ~ScopedDbConnection() { + if ( _conn && ! _conn->isFailed() ) { + /* see done() comments above for why we log this line */ + log() << "~ScopedDBConnection: _conn != null" << endl; + kill(); + } + } + }; + +} // namespace mongo diff --git a/client/dbclient.cpp b/client/dbclient.cpp new file mode 100644 index 0000000..165981d --- /dev/null +++ b/client/dbclient.cpp @@ -0,0 +1,981 @@ +// dbclient.cpp - connect to a Mongo database as a database, from C++ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "../db/pdfile.h" +#include "dbclient.h" +#include "../util/builder.h" +#include "../db/jsobj.h" +#include "../db/json.h" +#include "../db/instance.h" +#include "../util/md5.hpp" +#include "../db/dbmessage.h" +#include "../db/cmdline.h" + +namespace mongo { + + Query& Query::where(const string &jscode, BSONObj scope) { + /* use where() before sort() and hint() and explain(), else this will assert. */ + assert( !obj.hasField("query") ); + BSONObjBuilder b; + b.appendElements(obj); + b.appendWhere(jscode, scope); + obj = b.obj(); + return *this; + } + + void Query::makeComplex() { + if ( obj.hasElement( "query" ) ) + return; + BSONObjBuilder b; + b.append( "query", obj ); + obj = b.obj(); + } + + Query& Query::sort(const BSONObj& s) { + appendComplex( "orderby", s ); + return *this; + } + + Query& Query::hint(BSONObj keyPattern) { + appendComplex( "$hint", keyPattern ); + return *this; + } + + Query& Query::explain() { + appendComplex( "$explain", true ); + return *this; + } + + Query& Query::snapshot() { + appendComplex( "$snapshot", true ); + return *this; + } + + Query& Query::minKey( const BSONObj &val ) { + appendComplex( "$min", val ); + return *this; + } + + Query& Query::maxKey( const BSONObj &val ) { + appendComplex( "$max", val ); + return *this; + } + + bool Query::isComplex() const{ + return obj.hasElement( "query" ); + } + + BSONObj Query::getFilter() const { + if ( ! isComplex() ) + return obj; + return obj.getObjectField( "query" ); + } + BSONObj Query::getSort() const { + if ( ! isComplex() ) + return BSONObj(); + return obj.getObjectField( "orderby" ); + } + BSONObj Query::getHint() const { + if ( ! isComplex() ) + return BSONObj(); + return obj.getObjectField( "$hint" ); + } + bool Query::isExplain() const { + return isComplex() && obj.getBoolField( "$explain" ); + } + + string Query::toString() const{ + return obj.toString(); + } + + /* --- dbclientcommands --- */ + + inline bool DBClientWithCommands::isOk(const BSONObj& o) { + return o.getIntField("ok") == 1; + } + + inline bool DBClientWithCommands::runCommand(const string &dbname, const BSONObj& cmd, BSONObj &info, int options) { + string ns = dbname + ".$cmd"; + info = findOne(ns, cmd, 0 , options); + return isOk(info); + } + + /* note - we build a bson obj here -- for something that is super common like getlasterror you + should have that object prebuilt as that would be faster. + */ + bool DBClientWithCommands::simpleCommand(const string &dbname, BSONObj *info, const string &command) { + BSONObj o; + if ( info == 0 ) + info = &o; + BSONObjBuilder b; + b.append(command, 1); + return runCommand(dbname, b.done(), *info); + } + + unsigned long long DBClientWithCommands::count(const string &_ns, const BSONObj& query, int options) { + NamespaceString ns(_ns); + BSONObj cmd = BSON( "count" << ns.coll << "query" << query ); + BSONObj res; + if( !runCommand(ns.db.c_str(), cmd, res, options) ) + uasserted(11010,string("count fails:") + res.toString()); + return res.getIntField("n"); + } + + BSONObj getlasterrorcmdobj = fromjson("{getlasterror:1}"); + + BSONObj DBClientWithCommands::getLastErrorDetailed() { + BSONObj info; + runCommand("admin", getlasterrorcmdobj, info); + return info; + } + + string DBClientWithCommands::getLastError() { + BSONObj info = getLastErrorDetailed(); + BSONElement e = info["err"]; + if( e.eoo() ) return ""; + if( e.type() == Object ) return e.toString(); + return e.str(); + } + + BSONObj getpreverrorcmdobj = fromjson("{getpreverror:1}"); + + BSONObj DBClientWithCommands::getPrevError() { + BSONObj info; + runCommand("admin", getpreverrorcmdobj, info); + return info; + } + + BSONObj getnoncecmdobj = fromjson("{getnonce:1}"); + + string DBClientWithCommands::createPasswordDigest( const string & username , const string & clearTextPassword ){ + md5digest d; + { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t *) username.data(), username.length()); + md5_append(&st, (const md5_byte_t *) ":mongo:", 7 ); + md5_append(&st, (const md5_byte_t *) clearTextPassword.data(), clearTextPassword.length()); + md5_finish(&st, d); + } + return digestToString( d ); + } + + bool DBClientWithCommands::auth(const string &dbname, const string &username, const string &password_text, string& errmsg, bool digestPassword) { + //cout << "TEMP AUTH " << toString() << dbname << ' ' << username << ' ' << password_text << ' ' << digestPassword << endl; + + string password = password_text; + if( digestPassword ) + password = createPasswordDigest( username , password_text ); + + BSONObj info; + string nonce; + if( !runCommand(dbname, getnoncecmdobj, info) ) { + errmsg = "getnonce fails - connection problem?"; + return false; + } + { + BSONElement e = info.getField("nonce"); + assert( e.type() == String ); + nonce = e.valuestr(); + } + + BSONObj authCmd; + BSONObjBuilder b; + { + + b << "authenticate" << 1 << "nonce" << nonce << "user" << username; + md5digest d; + { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t *) nonce.c_str(), nonce.size() ); + md5_append(&st, (const md5_byte_t *) username.data(), username.length()); + md5_append(&st, (const md5_byte_t *) password.c_str(), password.size() ); + md5_finish(&st, d); + } + b << "key" << digestToString( d ); + authCmd = b.done(); + } + + if( runCommand(dbname, authCmd, info) ) + return true; + + errmsg = info.toString(); + return false; + } + + BSONObj ismastercmdobj = fromjson("{\"ismaster\":1}"); + + bool DBClientWithCommands::isMaster(bool& isMaster, BSONObj *info) { + BSONObj o; + if ( info == 0 ) info = &o; + bool ok = runCommand("admin", ismastercmdobj, *info); + isMaster = (info->getIntField("ismaster") == 1); + return ok; + } + + bool DBClientWithCommands::createCollection(const string &ns, unsigned size, bool capped, int max, BSONObj *info) { + BSONObj o; + if ( info == 0 ) info = &o; + BSONObjBuilder b; + b.append("create", ns); + if ( size ) b.append("size", size); + if ( capped ) b.append("capped", true); + if ( max ) b.append("max", max); + string db = nsToDatabase(ns.c_str()); + return runCommand(db.c_str(), b.done(), *info); + } + + bool DBClientWithCommands::copyDatabase(const string &fromdb, const string &todb, const string &fromhost, BSONObj *info) { + BSONObj o; + if ( info == 0 ) info = &o; + BSONObjBuilder b; + b.append("copydb", 1); + b.append("fromhost", fromhost); + b.append("fromdb", fromdb); + b.append("todb", todb); + return runCommand("admin", b.done(), *info); + } + + bool DBClientWithCommands::setDbProfilingLevel(const string &dbname, ProfilingLevel level, BSONObj *info ) { + BSONObj o; + if ( info == 0 ) info = &o; + + if ( level ) { + // Create system.profile collection. If it already exists this does nothing. + // TODO: move this into the db instead of here so that all + // drivers don't have to do this. + string ns = dbname + ".system.profile"; + createCollection(ns.c_str(), 1024 * 1024, true, 0, info); + } + + BSONObjBuilder b; + b.append("profile", (int) level); + return runCommand(dbname, b.done(), *info); + } + + BSONObj getprofilingcmdobj = fromjson("{\"profile\":-1}"); + + bool DBClientWithCommands::getDbProfilingLevel(const string &dbname, ProfilingLevel& level, BSONObj *info) { + BSONObj o; + if ( info == 0 ) info = &o; + if ( runCommand(dbname, getprofilingcmdobj, *info) ) { + level = (ProfilingLevel) info->getIntField("was"); + return true; + } + return false; + } + + BSONObj DBClientWithCommands::mapreduce(const string &ns, const string &jsmapf, const string &jsreducef, BSONObj query, const string& outputcolname) { + BSONObjBuilder b; + b.append("mapreduce", nsGetCollection(ns)); + b.appendCode("map", jsmapf.c_str()); + b.appendCode("reduce", jsreducef.c_str()); + if( !query.isEmpty() ) + b.append("query", query); + if( !outputcolname.empty() ) + b.append("out", outputcolname); + BSONObj info; + runCommand(nsGetDB(ns), b.done(), info); + return info; + } + + bool DBClientWithCommands::eval(const string &dbname, const string &jscode, BSONObj& info, BSONElement& retValue, BSONObj *args) { + BSONObjBuilder b; + b.appendCode("$eval", jscode.c_str()); + if ( args ) + b.appendArray("args", *args); + bool ok = runCommand(dbname, b.done(), info); + if ( ok ) + retValue = info.getField("retval"); + return ok; + } + + bool DBClientWithCommands::eval(const string &dbname, const string &jscode) { + BSONObj info; + BSONElement retValue; + return eval(dbname, jscode, info, retValue); + } + + list<string> DBClientWithCommands::getDatabaseNames(){ + BSONObj info; + uassert( 10005 , "listdatabases failed" , runCommand( "admin" , BSON( "listDatabases" << 1 ) , info ) ); + uassert( 10006 , "listDatabases.databases not array" , info["databases"].type() == Array ); + + list<string> names; + + BSONObjIterator i( info["databases"].embeddedObjectUserCheck() ); + while ( i.more() ){ + names.push_back( i.next().embeddedObjectUserCheck()["name"].valuestr() ); + } + + return names; + } + + list<string> DBClientWithCommands::getCollectionNames( const string& db ){ + list<string> names; + + string ns = db + ".system.namespaces"; + auto_ptr<DBClientCursor> c = query( ns.c_str() , BSONObj() ); + while ( c->more() ){ + string name = c->next()["name"].valuestr(); + if ( name.find( "$" ) != string::npos ) + continue; + names.push_back( name ); + } + return names; + } + + bool DBClientWithCommands::exists( const string& ns ){ + list<string> names; + + string db = nsGetDB( ns ) + ".system.namespaces"; + BSONObj q = BSON( "name" << ns ); + return count( db.c_str() , q ); + } + + + void testSort() { + DBClientConnection c; + string err; + if ( !c.connect("localhost", err) ) { + out() << "can't connect to server " << err << endl; + return; + } + + cout << "findOne returns:" << endl; + cout << c.findOne("test.foo", QUERY( "x" << 3 ) ).toString() << endl; + cout << c.findOne("test.foo", QUERY( "x" << 3 ).sort("name") ).toString() << endl; + + } + + /* TODO: unit tests should run this? */ + void testDbEval() { + DBClientConnection c; + string err; + if ( !c.connect("localhost", err) ) { + out() << "can't connect to server " << err << endl; + return; + } + + if( !c.auth("dwight", "u", "p", err) ) { + out() << "can't authenticate " << err << endl; + return; + } + + BSONObj info; + BSONElement retValue; + BSONObjBuilder b; + b.append("0", 99); + BSONObj args = b.done(); + bool ok = c.eval("dwight", "function() { return args[0]; }", info, retValue, &args); + out() << "eval ok=" << ok << endl; + out() << "retvalue=" << retValue.toString() << endl; + out() << "info=" << info.toString() << endl; + + out() << endl; + + int x = 3; + assert( c.eval("dwight", "function() { return 3; }", x) ); + + out() << "***\n"; + + BSONObj foo = fromjson("{\"x\":7}"); + out() << foo.toString() << endl; + int res=0; + ok = c.eval("dwight", "function(parm1) { return parm1.x; }", foo, res); + out() << ok << " retval:" << res << endl; + } + + void testPaired(); + + /* --- dbclientconnection --- */ + + bool DBClientConnection::auth(const string &dbname, const string &username, const string &password_text, string& errmsg, bool digestPassword) { + string password = password_text; + if( digestPassword ) + password = createPasswordDigest( username , password_text ); + + if( autoReconnect ) { + /* note we remember the auth info before we attempt to auth -- if the connection is broken, we will + then have it for the next autoreconnect attempt. + */ + pair<string,string> p = pair<string,string>(username, password); + authCache[dbname] = p; + } + + return DBClientBase::auth(dbname, username, password.c_str(), errmsg, false); + } + + BSONObj DBClientInterface::findOne(const string &ns, Query query, const BSONObj *fieldsToReturn, int queryOptions) { + auto_ptr<DBClientCursor> c = + this->query(ns, query, 1, 0, fieldsToReturn, queryOptions); + + massert( 10276 , "DBClientBase::findOne: transport error", c.get() ); + + if ( !c->more() ) + return BSONObj(); + + return c->next().copy(); + } + + bool DBClientConnection::connect(const string &_serverAddress, string& errmsg) { + serverAddress = _serverAddress; + + string ip; + int port; + size_t idx = serverAddress.find( ":" ); + if ( idx != string::npos ) { + port = strtol( serverAddress.substr( idx + 1 ).c_str(), 0, 10 ); + ip = serverAddress.substr( 0 , idx ); + ip = hostbyname(ip.c_str()); + } else { + port = CmdLine::DefaultDBPort; + ip = hostbyname( serverAddress.c_str() ); + } + massert( 10277 , "Unable to parse hostname", !ip.empty() ); + + // we keep around SockAddr for connection life -- maybe MessagingPort + // requires that? + server = auto_ptr<SockAddr>(new SockAddr(ip.c_str(), port)); + p = auto_ptr<MessagingPort>(new MessagingPort()); + + if ( !p->connect(*server) ) { + stringstream ss; + ss << "couldn't connect to server " << serverAddress << " " << ip << ":" << port; + errmsg = ss.str(); + failed = true; + return false; + } + return true; + } + + void DBClientConnection::_checkConnection() { + if ( !failed ) + return; + if ( lastReconnectTry && time(0)-lastReconnectTry < 2 ) + return; + if ( !autoReconnect ) + return; + + lastReconnectTry = time(0); + log() << "trying reconnect to " << serverAddress << endl; + string errmsg; + string tmp = serverAddress; + failed = false; + if ( !connect(tmp.c_str(), errmsg) ) { + log() << "reconnect " << serverAddress << " failed " << errmsg << endl; + return; + } + + log() << "reconnect " << serverAddress << " ok" << endl; + for( map< string, pair<string,string> >::iterator i = authCache.begin(); i != authCache.end(); i++ ) { + const char *dbname = i->first.c_str(); + const char *username = i->second.first.c_str(); + const char *password = i->second.second.c_str(); + if( !DBClientBase::auth(dbname, username, password, errmsg, false) ) + log() << "reconnect: auth failed db:" << dbname << " user:" << username << ' ' << errmsg << '\n'; + } + } + + auto_ptr<DBClientCursor> DBClientBase::query(const string &ns, Query query, int nToReturn, + int nToSkip, const BSONObj *fieldsToReturn, int queryOptions) { + auto_ptr<DBClientCursor> c( new DBClientCursor( this, + ns, query.obj, nToReturn, nToSkip, + fieldsToReturn, queryOptions ) ); + if ( c->init() ) + return c; + return auto_ptr< DBClientCursor >( 0 ); + } + + auto_ptr<DBClientCursor> DBClientBase::getMore( const string &ns, long long cursorId, int nToReturn, int options ) { + auto_ptr<DBClientCursor> c( new DBClientCursor( this, ns, cursorId, nToReturn, options ) ); + if ( c->init() ) + return c; + return auto_ptr< DBClientCursor >( 0 ); + } + + void DBClientBase::insert( const string & ns , BSONObj obj ) { + Message toSend; + + BufBuilder b; + int opts = 0; + b.append( opts ); + b.append( ns ); + obj.appendSelfToBufBuilder( b ); + + toSend.setData( dbInsert , b.buf() , b.len() ); + + say( toSend ); + } + + void DBClientBase::insert( const string & ns , const vector< BSONObj > &v ) { + Message toSend; + + BufBuilder b; + int opts = 0; + b.append( opts ); + b.append( ns ); + for( vector< BSONObj >::const_iterator i = v.begin(); i != v.end(); ++i ) + i->appendSelfToBufBuilder( b ); + + toSend.setData( dbInsert, b.buf(), b.len() ); + + say( toSend ); + } + + void DBClientBase::remove( const string & ns , Query obj , bool justOne ) { + Message toSend; + + BufBuilder b; + int opts = 0; + b.append( opts ); + b.append( ns ); + + int flags = 0; + if ( justOne ) + flags |= 1; + b.append( flags ); + + obj.obj.appendSelfToBufBuilder( b ); + + toSend.setData( dbDelete , b.buf() , b.len() ); + + say( toSend ); + } + + void DBClientBase::update( const string & ns , Query query , BSONObj obj , bool upsert , bool multi ) { + + BufBuilder b; + b.append( (int)0 ); // reserverd + b.append( ns ); + + int flags = 0; + if ( upsert ) flags |= UpdateOption_Upsert; + if ( multi ) flags |= UpdateOption_Multi; + b.append( flags ); + + query.obj.appendSelfToBufBuilder( b ); + obj.appendSelfToBufBuilder( b ); + + Message toSend; + toSend.setData( dbUpdate , b.buf() , b.len() ); + + say( toSend ); + } + + auto_ptr<DBClientCursor> DBClientWithCommands::getIndexes( const string &ns ){ + return query( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , BSON( "ns" << ns ) ); + } + + void DBClientWithCommands::dropIndex( const string& ns , BSONObj keys ){ + dropIndex( ns , genIndexName( keys ) ); + } + + + void DBClientWithCommands::dropIndex( const string& ns , const string& indexName ){ + BSONObj info; + if ( ! runCommand( nsToDatabase( ns.c_str() ) , + BSON( "deleteIndexes" << NamespaceString( ns ).coll << "index" << indexName ) , + info ) ){ + log() << "dropIndex failed: " << info << endl; + uassert( 10007 , "dropIndex failed" , 0 ); + } + resetIndexCache(); + } + + void DBClientWithCommands::dropIndexes( const string& ns ){ + BSONObj info; + uassert( 10008 , "dropIndexes failed" , runCommand( nsToDatabase( ns.c_str() ) , + BSON( "deleteIndexes" << NamespaceString( ns ).coll << "index" << "*") , + info ) ); + resetIndexCache(); + } + + void DBClientWithCommands::reIndex( const string& ns ){ + list<BSONObj> all; + auto_ptr<DBClientCursor> i = getIndexes( ns ); + while ( i->more() ){ + all.push_back( i->next().getOwned() ); + } + + dropIndexes( ns ); + + for ( list<BSONObj>::iterator i=all.begin(); i!=all.end(); i++ ){ + BSONObj o = *i; + insert( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , o ); + } + + } + + + string DBClientWithCommands::genIndexName( const BSONObj& keys ){ + stringstream ss; + + bool first = 1; + for ( BSONObjIterator i(keys); i.more(); ) { + BSONElement f = i.next(); + + if ( first ) + first = 0; + else + ss << "_"; + + ss << f.fieldName() << "_"; + if( f.isNumber() ) + ss << f.numberInt(); + } + return ss.str(); + } + + bool DBClientWithCommands::ensureIndex( const string &ns , BSONObj keys , bool unique, const string & name ) { + BSONObjBuilder toSave; + toSave.append( "ns" , ns ); + toSave.append( "key" , keys ); + + string cacheKey(ns); + cacheKey += "--"; + + if ( name != "" ) { + toSave.append( "name" , name ); + cacheKey += name; + } + else { + string nn = genIndexName( keys ); + toSave.append( "name" , nn ); + cacheKey += nn; + } + + if ( unique ) + toSave.appendBool( "unique", unique ); + + if ( _seenIndexes.count( cacheKey ) ) + return 0; + _seenIndexes.insert( cacheKey ); + + insert( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , toSave.obj() ); + return 1; + } + + void DBClientWithCommands::resetIndexCache() { + _seenIndexes.clear(); + } + + /* -- DBClientCursor ---------------------------------------------- */ + + void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, const BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { + CHECK_OBJECT( query , "assembleRequest query" ); + // see query.h for the protocol we are using here. + BufBuilder b; + int opts = queryOptions; + b.append(opts); + b.append(ns.c_str()); + b.append(nToSkip); + b.append(nToReturn); + query.appendSelfToBufBuilder(b); + if ( fieldsToReturn ) + fieldsToReturn->appendSelfToBufBuilder(b); + toSend.setData(dbQuery, b.buf(), b.len()); + } + + void DBClientConnection::say( Message &toSend ) { + checkConnection(); + try { + port().say( toSend ); + } catch( SocketException & ) { + failed = true; + throw; + } + } + + void DBClientConnection::sayPiggyBack( Message &toSend ) { + port().piggyBack( toSend ); + } + + bool DBClientConnection::call( Message &toSend, Message &response, bool assertOk ) { + /* todo: this is very ugly messagingport::call returns an error code AND can throw + an exception. we should make it return void and just throw an exception anytime + it fails + */ + try { + if ( !port().call(toSend, response) ) { + failed = true; + if ( assertOk ) + massert( 10278 , "dbclient error communicating with server", false); + return false; + } + } + catch( SocketException & ) { + failed = true; + throw; + } + return true; + } + + void DBClientConnection::checkResponse( const char *data, int nReturned ) { + /* check for errors. the only one we really care about at + this stage is "not master" */ + if ( clientPaired && nReturned ) { + BSONObj o(data); + BSONElement e = o.firstElement(); + if ( strcmp(e.fieldName(), "$err") == 0 && + e.type() == String && strncmp(e.valuestr(), "not master", 10) == 0 ) { + clientPaired->isntMaster(); + } + } + } + + bool DBClientCursor::init() { + Message toSend; + if ( !cursorId ) { + assembleRequest( ns, query, nToReturn, nToSkip, fieldsToReturn, opts, toSend ); + } else { + BufBuilder b; + b.append( opts ); + b.append( ns.c_str() ); + b.append( nToReturn ); + b.append( cursorId ); + toSend.setData( dbGetMore, b.buf(), b.len() ); + } + if ( !connector->call( toSend, *m, false ) ) + return false; + dataReceived(); + return true; + } + + void DBClientCursor::requestMore() { + assert( cursorId && pos == nReturned ); + + BufBuilder b; + b.append(opts); + b.append(ns.c_str()); + b.append(nToReturn); + b.append(cursorId); + + Message toSend; + toSend.setData(dbGetMore, b.buf(), b.len()); + auto_ptr<Message> response(new Message()); + connector->call( toSend, *response ); + + m = response; + dataReceived(); + } + + void DBClientCursor::dataReceived() { + QueryResult *qr = (QueryResult *) m->data; + resultFlags = qr->resultFlags(); + if ( qr->resultFlags() & QueryResult::ResultFlag_CursorNotFound ) { + // cursor id no longer valid at the server. + assert( qr->cursorId == 0 ); + cursorId = 0; // 0 indicates no longer valid (dead) + // TODO: should we throw a UserException here??? + } + if ( cursorId == 0 || ! ( opts & QueryOption_CursorTailable ) ) { + // only set initially: we don't want to kill it on end of data + // if it's a tailable cursor + cursorId = qr->cursorId; + } + nReturned = qr->nReturned; + pos = 0; + data = qr->data(); + + connector->checkResponse( data, nReturned ); + /* this assert would fire the way we currently work: + assert( nReturned || cursorId == 0 ); + */ + } + + /** If true, safe to call next(). Requests more from server if necessary. */ + bool DBClientCursor::more() { + if ( pos < nReturned ) + return true; + + if ( cursorId == 0 ) + return false; + + requestMore(); + return pos < nReturned; + } + + BSONObj DBClientCursor::next() { + assert( more() ); + pos++; + BSONObj o(data); + data += o.objsize(); + return o; + } + + DBClientCursor::~DBClientCursor() { + if ( cursorId && _ownCursor ) { + BufBuilder b; + b.append( (int)0 ); // reserved + b.append( (int)1 ); // number + b.append( cursorId ); + + Message m; + m.setData( dbKillCursors , b.buf() , b.len() ); + + connector->sayPiggyBack( m ); + } + + } + + /* --- class dbclientpaired --- */ + + string DBClientPaired::toString() { + stringstream ss; + ss << "state: " << master << '\n'; + ss << "left: " << left.toStringLong() << '\n'; + ss << "right: " << right.toStringLong() << '\n'; + return ss.str(); + } + +#pragma warning(disable: 4355) + DBClientPaired::DBClientPaired() : + left(true, this), right(true, this) + { + master = NotSetL; + } +#pragma warning(default: 4355) + + /* find which server, the left or right, is currently master mode */ + void DBClientPaired::_checkMaster() { + for ( int retry = 0; retry < 2; retry++ ) { + int x = master; + for ( int pass = 0; pass < 2; pass++ ) { + DBClientConnection& c = x == 0 ? left : right; + try { + bool im; + BSONObj o; + c.isMaster(im, &o); + if ( retry ) + log() << "checkmaster: " << c.toString() << ' ' << o.toString() << '\n'; + if ( im ) { + master = (State) (x + 2); + return; + } + } + catch (AssertionException&) { + if ( retry ) + log() << "checkmaster: caught exception " << c.toString() << '\n'; + } + x = x^1; + } + sleepsecs(1); + } + + uassert( 10009 , "checkmaster: no master found", false); + } + + inline DBClientConnection& DBClientPaired::checkMaster() { + if ( master > NotSetR ) { + // a master is selected. let's just make sure connection didn't die + DBClientConnection& c = master == Left ? left : right; + if ( !c.isFailed() ) + return c; + // after a failure, on the next checkMaster, start with the other + // server -- presumably it took over. (not critical which we check first, + // just will make the failover slightly faster if we guess right) + master = master == Left ? NotSetR : NotSetL; + } + + _checkMaster(); + assert( master > NotSetR ); + return master == Left ? left : right; + } + + DBClientConnection& DBClientPaired::slaveConn(){ + DBClientConnection& m = checkMaster(); + assert( ! m.isFailed() ); + return master == Left ? right : left; + } + + bool DBClientPaired::connect(const string &serverHostname1, const string &serverHostname2) { + string errmsg; + bool l = left.connect(serverHostname1, errmsg); + bool r = right.connect(serverHostname2, errmsg); + master = l ? NotSetL : NotSetR; + if ( !l && !r ) // it would be ok to fall through, but checkMaster will then try an immediate reconnect which is slow + return false; + try { + checkMaster(); + } + catch (AssertionException&) { + return false; + } + return true; + } + + bool DBClientPaired::connect(string hostpairstring) { + size_t comma = hostpairstring.find( "," ); + uassert( 10010 , "bad hostpairstring", comma != string::npos); + return connect( hostpairstring.substr( 0 , comma ) , hostpairstring.substr( comma + 1 ) ); + } + + bool DBClientPaired::auth(const string &dbname, const string &username, const string &pwd, string& errmsg) { + DBClientConnection& m = checkMaster(); + if( !m.auth(dbname, username, pwd, errmsg) ) + return false; + /* we try to authentiate with the other half of the pair -- even if down, that way the authInfo is cached. */ + string e; + try { + if( &m == &left ) + right.auth(dbname, username, pwd, e); + else + left.auth(dbname, username, pwd, e); + } + catch( AssertionException&) { + } + return true; + } + + auto_ptr<DBClientCursor> DBClientPaired::query(const string &a, Query b, int c, int d, + const BSONObj *e, int f) + { + return checkMaster().query(a,b,c,d,e,f); + } + + BSONObj DBClientPaired::findOne(const string &a, Query b, const BSONObj *c, int d) { + return checkMaster().findOne(a,b,c,d); + } + + void testPaired() { + DBClientPaired p; + log() << "connect returns " << p.connect("localhost:27017", "localhost:27018") << endl; + + //DBClientConnection p(true); + string errmsg; + // log() << "connect " << p.connect("localhost", errmsg) << endl; + log() << "auth " << p.auth("dwight", "u", "p", errmsg) << endl; + + while( 1 ) { + sleepsecs(3); + try { + log() << "findone returns " << p.findOne("dwight.foo", BSONObj()).toString() << endl; + sleepsecs(3); + BSONObj info; + bool im; + log() << "ismaster returns " << p.isMaster(im,&info) << " info: " << info.toString() << endl; + } + catch(...) { + cout << "caught exception" << endl; + } + } + } + +} // namespace mongo diff --git a/client/dbclient.h b/client/dbclient.h new file mode 100644 index 0000000..e3f1675 --- /dev/null +++ b/client/dbclient.h @@ -0,0 +1,894 @@ +/** @file dbclient.h - connect to a Mongo database as a database, from C++ */ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include "../util/message.h" +#include "../db/jsobj.h" +#include "../db/json.h" + +namespace mongo { + + /** the query field 'options' can have these bits set: */ + enum QueryOptions { + /** Tailable means cursor is not closed when the last data is retrieved. rather, the cursor marks + the final object's position. you can resume using the cursor later, from where it was located, + if more data were received. Set on dbQuery and dbGetMore. + + like any "latent cursor", the cursor may become invalid at some point -- for example if that + final object it references were deleted. Thus, you should be prepared to requery if you get back + ResultFlag_CursorNotFound. + */ + QueryOption_CursorTailable = 1 << 1, + + /** allow query of replica slave. normally these return an error except for namespace "local". + */ + QueryOption_SlaveOk = 1 << 2, + + // findingStart mode is used to find the first operation of interest when + // we are scanning through a repl log. For efficiency in the common case, + // where the first operation of interest is closer to the tail than the head, + // we start from the tail of the log and work backwards until we find the + // first operation of interest. Then we scan forward from that first operation, + // actually returning results to the client. During the findingStart phase, + // we release the db mutex occasionally to avoid blocking the db process for + // an extended period of time. + QueryOption_OplogReplay = 1 << 3, + + /** The server normally times out idle cursors after an inactivy period to prevent excess memory use + Set this option to prevent that. + */ + QueryOption_NoCursorTimeout = 1 << 4, + + /** Use with QueryOption_CursorTailable. If we are at the end of the data, block for a while rather + than returning no data. After a timeout period, we do return as normal. + */ + QueryOption_AwaitData = 1 << 5 + + }; + + enum UpdateOptions { + /** Upsert - that is, insert the item if no matching item is found. */ + UpdateOption_Upsert = 1 << 0, + + /** Update multiple documents (if multiple documents match query expression). + (Default is update a single document and stop.) */ + UpdateOption_Multi = 1 << 1 + }; + + class BSONObj; + + /** Represents a Mongo query expression. Typically one uses the QUERY(...) macro to construct a Query object. + Examples: + QUERY( "age" << 33 << "school" << "UCLA" ).sort("name") + QUERY( "age" << GT << 30 << LT << 50 ) + */ + class Query { + public: + BSONObj obj; + Query() : obj(BSONObj()) { } + Query(const BSONObj& b) : obj(b) { } + Query(const string &json) : + obj(fromjson(json)) { } + Query(const char * json) : + obj(fromjson(json)) { } + + /** Add a sort (ORDER BY) criteria to the query expression. + @param sortPattern the sort order template. For example to order by name ascending, time descending: + { name : 1, ts : -1 } + i.e. + BSON( "name" << 1 << "ts" << -1 ) + or + fromjson(" name : 1, ts : -1 ") + */ + Query& sort(const BSONObj& sortPattern); + + /** Add a sort (ORDER BY) criteria to the query expression. + This version of sort() assumes you want to sort on a single field. + @param asc = 1 for ascending order + asc = -1 for descending order + */ + Query& sort(const string &field, int asc = 1) { sort( BSON( field << asc ) ); return *this; } + + /** Provide a hint to the query. + @param keyPattern Key pattern for the index to use. + Example: + hint("{ts:1}") + */ + Query& hint(BSONObj keyPattern); + Query& hint(const string &jsonKeyPatt) { return hint(fromjson(jsonKeyPatt)); } + + /** Provide min and/or max index limits for the query. + min <= x < max + */ + Query& minKey(const BSONObj &val); + /** + max is exclusive + */ + Query& maxKey(const BSONObj &val); + + /** Return explain information about execution of this query instead of the actual query results. + Normally it is easier to use the mongo shell to run db.find(...).explain(). + */ + Query& explain(); + + /** Use snapshot mode for the query. Snapshot mode assures no duplicates are returned, or objects missed, which were + present at both the start and end of the query's execution (if an object is new during the query, or deleted during + the query, it may or may not be returned, even with snapshot mode). + + Note that short query responses (less than 1MB) are always effectively snapshotted. + + Currently, snapshot mode may not be used with sorting or explicit hints. + */ + Query& snapshot(); + + /** Queries to the Mongo database support a $where parameter option which contains + a javascript function that is evaluated to see whether objects being queried match + its criteria. Use this helper to append such a function to a query object. + Your query may also contain other traditional Mongo query terms. + + @param jscode The javascript function to evaluate against each potential object + match. The function must return true for matched objects. Use the this + variable to inspect the current object. + @param scope SavedContext for the javascript object. List in a BSON object any + variables you would like defined when the jscode executes. One can think + of these as "bind variables". + + Examples: + conn.findOne("test.coll", Query("{a:3}").where("this.b == 2 || this.c == 3")); + Query badBalance = Query().where("this.debits - this.credits < 0"); + */ + Query& where(const string &jscode, BSONObj scope); + Query& where(const string &jscode) { return where(jscode, BSONObj()); } + + /** + * if this query has an orderby, hint, or some other field + */ + bool isComplex() const; + + BSONObj getFilter() const; + BSONObj getSort() const; + BSONObj getHint() const; + bool isExplain() const; + + string toString() const; + operator string() const { return toString(); } + private: + void makeComplex(); + template< class T > + void appendComplex( const char *fieldName, const T& val ) { + makeComplex(); + BSONObjBuilder b; + b.appendElements(obj); + b.append(fieldName, val); + obj = b.obj(); + } + }; + +/** Typically one uses the QUERY(...) macro to construct a Query object. + Example: QUERY( "age" << 33 << "school" << "UCLA" ) +*/ +#define QUERY(x) Query( BSON(x) ) + + /** + interface that handles communication with the db + */ + class DBConnector { + public: + virtual ~DBConnector() {} + virtual bool call( Message &toSend, Message &response, bool assertOk=true ) = 0; + virtual void say( Message &toSend ) = 0; + virtual void sayPiggyBack( Message &toSend ) = 0; + virtual void checkResponse( const string &data, int nReturned ) {} + }; + + /** Queries return a cursor object */ + class DBClientCursor : boost::noncopyable { + friend class DBClientBase; + bool init(); + public: + /** If true, safe to call next(). Requests more from server if necessary. */ + bool more(); + + /** next + @return next object in the result cursor. + on an error at the remote server, you will get back: + { $err: <string> } + if you do not want to handle that yourself, call nextSafe(). + */ + BSONObj next(); + + /** throws AssertionException if get back { $err : ... } */ + BSONObj nextSafe() { + BSONObj o = next(); + BSONElement e = o.firstElement(); + assert( strcmp(e.fieldName(), "$err") != 0 ); + return o; + } + + /** + iterate the rest of the cursor and return the number if items + */ + int itcount(){ + int c = 0; + while ( more() ){ + next(); + c++; + } + return c; + } + + /** cursor no longer valid -- use with tailable cursors. + note you should only rely on this once more() returns false; + 'dead' may be preset yet some data still queued and locally + available from the dbclientcursor. + */ + bool isDead() const { + return cursorId == 0; + } + + bool tailable() const { + return (opts & QueryOption_CursorTailable) != 0; + } + + bool hasResultFlag( int flag ){ + return (resultFlags & flag) != 0; + } + public: + DBClientCursor( DBConnector *_connector, const string &_ns, BSONObj _query, int _nToReturn, + int _nToSkip, const BSONObj *_fieldsToReturn, int queryOptions ) : + connector(_connector), + ns(_ns), + query(_query), + nToReturn(_nToReturn), + nToSkip(_nToSkip), + fieldsToReturn(_fieldsToReturn), + opts(queryOptions), + m(new Message()), + cursorId(), + nReturned(), + pos(), + data(), + _ownCursor( true ) { + } + + DBClientCursor( DBConnector *_connector, const string &_ns, long long _cursorId, int _nToReturn, int options ) : + connector(_connector), + ns(_ns), + nToReturn( _nToReturn ), + opts( options ), + m(new Message()), + cursorId( _cursorId ), + nReturned(), + pos(), + data(), + _ownCursor( true ) { + } + + virtual ~DBClientCursor(); + + long long getCursorId() const { return cursorId; } + + /** by default we "own" the cursor and will send the server a KillCursor + message when ~DBClientCursor() is called. This function overrides that. + */ + void decouple() { _ownCursor = false; } + + private: + DBConnector *connector; + string ns; + BSONObj query; + int nToReturn; + int nToSkip; + const BSONObj *fieldsToReturn; + int opts; + auto_ptr<Message> m; + + int resultFlags; + long long cursorId; + int nReturned; + int pos; + const char *data; + void dataReceived(); + void requestMore(); + bool _ownCursor; // see decouple() + }; + + /** + The interface that any db connection should implement + */ + class DBClientInterface : boost::noncopyable { + public: + virtual auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn = 0, int nToSkip = 0, + const BSONObj *fieldsToReturn = 0, int queryOptions = 0) = 0; + + virtual auto_ptr<DBClientCursor> getMore( const string &ns, long long cursorId, int nToReturn = 0, int options = 0 ) = 0; + + virtual void insert( const string &ns, BSONObj obj ) = 0; + + virtual void insert( const string &ns, const vector< BSONObj >& v ) = 0; + + virtual void remove( const string &ns , Query query, bool justOne = 0 ) = 0; + + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ) = 0; + + virtual ~DBClientInterface() { } + + /** + @return a single object that matches the query. if none do, then the object is empty + @throws AssertionException + */ + virtual BSONObj findOne(const string &ns, Query query, const BSONObj *fieldsToReturn = 0, int queryOptions = 0); + + + }; + + /** + DB "commands" + Basically just invocations of connection.$cmd.findOne({...}); + */ + class DBClientWithCommands : public DBClientInterface { + bool isOk(const BSONObj&); + set<string> _seenIndexes; + public: + + /** helper function. run a simple command where the command expression is simply + { command : 1 } + @param info -- where to put result object. may be null if caller doesn't need that info + @param command -- command name + @return true if the command returned "ok". + */ + bool simpleCommand(const string &dbname, BSONObj *info, const string &command); + + /** Run a database command. Database commands are represented as BSON objects. Common database + commands have prebuilt helper functions -- see below. If a helper is not available you can + directly call runCommand. + + @param dbname database name. Use "admin" for global administrative commands. + @param cmd the command object to execute. For example, { ismaster : 1 } + @param info the result object the database returns. Typically has { ok : ..., errmsg : ... } fields + set. + @return true if the command returned "ok". + */ + bool runCommand(const string &dbname, const BSONObj& cmd, BSONObj &info, int options=0); + + /** Authorize access to a particular database. + Authentication is separate for each database on the server -- you may authenticate for any + number of databases on a single connection. + The "admin" database is special and once authenticated provides access to all databases on the + server. + @param digestPassword if password is plain text, set this to true. otherwise assumed to be pre-digested + @return true if successful + */ + virtual bool auth(const string &dbname, const string &username, const string &pwd, string& errmsg, bool digestPassword = true); + + /** count number of objects in collection ns that match the query criteria specified + throws UserAssertion if database returns an error + */ + unsigned long long count(const string &ns, const BSONObj& query = BSONObj(), int options=0 ); + + string createPasswordDigest( const string &username , const string &clearTextPassword ); + + /** returns true in isMaster parm if this db is the current master + of a replica pair. + + pass in info for more details e.g.: + { "ismaster" : 1.0 , "msg" : "not paired" , "ok" : 1.0 } + + returns true if command invoked successfully. + */ + virtual bool isMaster(bool& isMaster, BSONObj *info=0); + + /** + Create a new collection in the database. Normally, collection creation is automatic. You would + use this function if you wish to specify special options on creation. + + If the collection already exists, no action occurs. + + ns: fully qualified collection name + size: desired initial extent size for the collection. + Must be <= 1000000000 for normal collections. + For fixed size (capped) collections, this size is the total/max size of the + collection. + capped: if true, this is a fixed size collection (where old data rolls out). + max: maximum number of objects if capped (optional). + + returns true if successful. + */ + bool createCollection(const string &ns, unsigned size = 0, bool capped = false, int max = 0, BSONObj *info = 0); + + /** Get error result from the last operation on this connection. + @return error message text, or empty string if no error. + */ + string getLastError(); + /** Get error result from the last operation on this connection. + @return full error object. + */ + BSONObj getLastErrorDetailed(); + + /** Return the last error which has occurred, even if not the very last operation. + + @return { err : <error message>, nPrev : <how_many_ops_back_occurred>, ok : 1 } + + result.err will be null if no error has occurred. + */ + BSONObj getPrevError(); + + /** Reset the previous error state for this connection (accessed via getLastError and + getPrevError). Useful when performing several operations at once and then checking + for an error after attempting all operations. + */ + bool resetError() { return simpleCommand("admin", 0, "reseterror"); } + + /** Delete the specified collection. */ + virtual bool dropCollection( const string &ns ){ + string db = nsGetDB( ns ); + string coll = nsGetCollection( ns ); + uassert( 10011 , "no collection name", coll.size() ); + + BSONObj info; + + bool res = runCommand( db.c_str() , BSON( "drop" << coll ) , info ); + resetIndexCache(); + return res; + } + + /** Perform a repair and compaction of the specified database. May take a long time to run. Disk space + must be available equal to the size of the database while repairing. + */ + bool repairDatabase(const string &dbname, BSONObj *info = 0) { + return simpleCommand(dbname, info, "repairDatabase"); + } + + /** Copy database from one server or name to another server or name. + + Generally, you should dropDatabase() first as otherwise the copied information will MERGE + into whatever data is already present in this database. + + For security reasons this function only works when you are authorized to access the "admin" db. However, + if you have access to said db, you can copy any database from one place to another. + TODO: this needs enhancement to be more flexible in terms of security. + + This method provides a way to "rename" a database by copying it to a new db name and + location. The copy is "repaired" and compacted. + + fromdb database name from which to copy. + todb database name to copy to. + fromhost hostname of the database (and optionally, ":port") from which to + copy the data. copies from self if "". + + returns true if successful + */ + bool copyDatabase(const string &fromdb, const string &todb, const string &fromhost = "", BSONObj *info = 0); + + /** The Mongo database provides built-in performance profiling capabilities. Uset setDbProfilingLevel() + to enable. Profiling information is then written to the system.profiling collection, which one can + then query. + */ + enum ProfilingLevel { + ProfileOff = 0, + ProfileSlow = 1, // log very slow (>100ms) operations + ProfileAll = 2 + }; + bool setDbProfilingLevel(const string &dbname, ProfilingLevel level, BSONObj *info = 0); + bool getDbProfilingLevel(const string &dbname, ProfilingLevel& level, BSONObj *info = 0); + + /** Run a map/reduce job on the server. + + See http://www.mongodb.org/display/DOCS/MapReduce + + ns namespace (db+collection name) of input data + jsmapf javascript map function code + jsreducef javascript reduce function code. + query optional query filter for the input + output optional permanent output collection name. if not specified server will + generate a temporary collection and return its name. + + returns a result object which contains: + { result : <collection_name>,
+ numObjects : <number_of_objects_scanned>,
+ timeMillis : <job_time>,
+ ok : <1_if_ok>,
+ [, err : <errmsg_if_error>]
+ } + + For example one might call: + result.getField("ok").trueValue() + on the result to check if ok. + */ + BSONObj mapreduce(const string &ns, const string &jsmapf, const string &jsreducef, BSONObj query = BSONObj(), const string& output = ""); + + /** Run javascript code on the database server. + dbname database SavedContext in which the code runs. The javascript variable 'db' will be assigned + to this database when the function is invoked. + jscode source code for a javascript function. + info the command object which contains any information on the invocation result including + the return value and other information. If an error occurs running the jscode, error + information will be in info. (try "out() << info.toString()") + retValue return value from the jscode function. + args args to pass to the jscode function. when invoked, the 'args' variable will be defined + for use by the jscode. + + returns true if runs ok. + + See testDbEval() in dbclient.cpp for an example of usage. + */ + bool eval(const string &dbname, const string &jscode, BSONObj& info, BSONElement& retValue, BSONObj *args = 0); + + /** + + */ + bool validate( const string &ns , bool scandata=true ){ + BSONObj cmd = BSON( "validate" << nsGetCollection( ns ) << "scandata" << scandata ); + BSONObj info; + return runCommand( nsGetDB( ns ).c_str() , cmd , info ); + } + + /* The following helpers are simply more convenient forms of eval() for certain common cases */ + + /* invocation with no return value of interest -- with or without one simple parameter */ + bool eval(const string &dbname, const string &jscode); + template< class T > + bool eval(const string &dbname, const string &jscode, T parm1) { + BSONObj info; + BSONElement retValue; + BSONObjBuilder b; + b.append("0", parm1); + BSONObj args = b.done(); + return eval(dbname, jscode, info, retValue, &args); + } + + /** eval invocation with one parm to server and one numeric field (either int or double) returned */ + template< class T, class NumType > + bool eval(const string &dbname, const string &jscode, T parm1, NumType& ret) { + BSONObj info; + BSONElement retValue; + BSONObjBuilder b; + b.append("0", parm1); + BSONObj args = b.done(); + if ( !eval(dbname, jscode, info, retValue, &args) ) + return false; + ret = (NumType) retValue.number(); + return true; + } + + /** + get a list of all the current databases + */ + list<string> getDatabaseNames(); + + /** + get a list of all the current collections in db + */ + list<string> getCollectionNames( const string& db ); + + bool exists( const string& ns ); + + + /** Create an index if it does not already exist. + ensureIndex calls are remembered so it is safe/fast to call this function many + times in your code. + @param ns collection to be indexed + @param keys the "key pattern" for the index. e.g., { name : 1 } + @param unique if true, indicates that key uniqueness should be enforced for this index + @param name if not isn't specified, it will be created from the keys (recommended) + @return whether or not sent message to db. + should be true on first call, false on subsequent unless resetIndexCache was called + */ + virtual bool ensureIndex( const string &ns , BSONObj keys , bool unique = false, const string &name = "" ); + + /** + clears the index cache, so the subsequent call to ensureIndex for any index will go to the server + */ + virtual void resetIndexCache(); + + virtual auto_ptr<DBClientCursor> getIndexes( const string &ns ); + + virtual void dropIndex( const string& ns , BSONObj keys ); + virtual void dropIndex( const string& ns , const string& indexName ); + + /** + drops all indexes for the collection + */ + virtual void dropIndexes( const string& ns ); + + virtual void reIndex( const string& ns ); + + string genIndexName( const BSONObj& keys ); + + /** Erase / drop an entire database */ + virtual bool dropDatabase(const string &dbname, BSONObj *info = 0) { + bool ret = simpleCommand(dbname, info, "dropDatabase"); + resetIndexCache(); + return ret; + } + + virtual string toString() = 0; + + /** @return the database name portion of an ns string */ + string nsGetDB( const string &ns ){ + string::size_type pos = ns.find( "." ); + if ( pos == string::npos ) + return ns; + + return ns.substr( 0 , pos ); + } + + /** @return the collection name portion of an ns string */ + string nsGetCollection( const string &ns ){ + string::size_type pos = ns.find( "." ); + if ( pos == string::npos ) + return ""; + + return ns.substr( pos + 1 ); + } + + }; + + /** + abstract class that implements the core db operations + */ + class DBClientBase : public DBClientWithCommands, public DBConnector { + public: + /** send a query to the database. + ns: namespace to query, format is <dbname>.<collectname>[.<collectname>]* + query: query to perform on the collection. this is a BSONObj (binary JSON) + You may format as + { query: { ... }, orderby: { ... } } + to specify a sort order. + nToReturn: n to return. 0 = unlimited + nToSkip: start with the nth item + fieldsToReturn: + optional template of which fields to select. if unspecified, returns all fields + queryOptions: see options enum at top of this file + + @return cursor. 0 if error (connection failure) + @throws AssertionException + */ + virtual auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn = 0, int nToSkip = 0, + const BSONObj *fieldsToReturn = 0, int queryOptions = 0); + + /** @param cursorId id of cursor to retrieve + @return an handle to a previously allocated cursor + @throws AssertionException + */ + virtual auto_ptr<DBClientCursor> getMore( const string &ns, long long cursorId, int nToReturn = 0, int options = 0 ); + + /** + insert an object into the database + */ + virtual void insert( const string &ns , BSONObj obj ); + + /** + insert a vector of objects into the database + */ + virtual void insert( const string &ns, const vector< BSONObj >& v ); + + /** + remove matching objects from the database + @param justOne if this true, then once a single match is found will stop + */ + virtual void remove( const string &ns , Query q , bool justOne = 0 ); + + /** + updates objects matching query + */ + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ); + + virtual string getServerAddress() const = 0; + + virtual bool isFailed() const = 0; + + }; + + class DBClientPaired; + + class ConnectException : public UserException { + public: + ConnectException(string msg) : UserException(9000,msg) { } + }; + + /** + A basic connection to the database. + This is the main entry point for talking to a simple Mongo setup + */ + class DBClientConnection : public DBClientBase { + DBClientPaired *clientPaired; + auto_ptr<MessagingPort> p; + auto_ptr<SockAddr> server; + bool failed; // true if some sort of fatal error has ever happened + bool autoReconnect; + time_t lastReconnectTry; + string serverAddress; // remember for reconnects + void _checkConnection(); + void checkConnection() { if( failed ) _checkConnection(); } + map< string, pair<string,string> > authCache; + public: + + /** + @param _autoReconnect if true, automatically reconnect on a connection failure + @param cp used by DBClientPaired. You do not need to specify this parameter + */ + DBClientConnection(bool _autoReconnect=false,DBClientPaired* cp=0) : + clientPaired(cp), failed(false), autoReconnect(_autoReconnect), lastReconnectTry(0) { } + + /** Connect to a Mongo database server. + + If autoReconnect is true, you can try to use the DBClientConnection even when + false was returned -- it will try to connect again. + + @param serverHostname host to connect to. can include port number ( 127.0.0.1 , 127.0.0.1:5555 ) + @param errmsg any relevant error message will appended to the string + @return false if fails to connect. + */ + virtual bool connect(const string &serverHostname, string& errmsg); + + /** Connect to a Mongo database server. Exception throwing version. + Throws a UserException if cannot connect. + + If autoReconnect is true, you can try to use the DBClientConnection even when + false was returned -- it will try to connect again. + + @param serverHostname host to connect to. can include port number ( 127.0.0.1 , 127.0.0.1:5555 ) + */ + void connect(string serverHostname) { + string errmsg; + if( !connect(serverHostname.c_str(), errmsg) ) + throw ConnectException(string("can't connect ") + errmsg); + } + + virtual bool auth(const string &dbname, const string &username, const string &pwd, string& errmsg, bool digestPassword = true); + + virtual auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn = 0, int nToSkip = 0, + const BSONObj *fieldsToReturn = 0, int queryOptions = 0) { + checkConnection(); + return DBClientBase::query( ns, query, nToReturn, nToSkip, fieldsToReturn, queryOptions ); + } + + /** + @return true if this connection is currently in a failed state. When autoreconnect is on, + a connection will transition back to an ok state after reconnecting. + */ + bool isFailed() const { + return failed; + } + + MessagingPort& port() { + return *p.get(); + } + + string toStringLong() const { + stringstream ss; + ss << serverAddress; + if ( failed ) ss << " failed"; + return ss.str(); + } + + /** Returns the address of the server */ + string toString() { + return serverAddress; + } + + string getServerAddress() const { + return serverAddress; + } + + protected: + virtual bool call( Message &toSend, Message &response, bool assertOk = true ); + virtual void say( Message &toSend ); + virtual void sayPiggyBack( Message &toSend ); + virtual void checkResponse( const char *data, int nReturned ); + }; + + /** Use this class to connect to a replica pair of servers. The class will manage + checking for which server in a replica pair is master, and do failover automatically. + + On a failover situation, expect at least one operation to return an error (throw + an exception) before the failover is complete. Operations are not retried. + */ + class DBClientPaired : public DBClientBase { + DBClientConnection left,right; + enum State { + NotSetL=0, + NotSetR=1, + Left, Right + } master; + + void _checkMaster(); + DBClientConnection& checkMaster(); + + public: + /** Call connect() after constructing. autoReconnect is always on for DBClientPaired connections. */ + DBClientPaired(); + + /** Returns false is neither member of the pair were reachable, or neither is + master, although, + when false returned, you can still try to use this connection object, it will + try reconnects. + */ + bool connect(const string &serverHostname1, const string &serverHostname2); + + /** Connect to a server pair using a host pair string of the form + hostname[:port],hostname[:port] + */ + bool connect(string hostpairstring); + + /** Authorize. Authorizes both sides of the pair as needed. + */ + bool auth(const string &dbname, const string &username, const string &pwd, string& errmsg); + + /** throws userassertion "no master found" */ + virtual + auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn = 0, int nToSkip = 0, + const BSONObj *fieldsToReturn = 0, int queryOptions = 0); + + /** throws userassertion "no master found" */ + virtual + BSONObj findOne(const string &ns, Query query, const BSONObj *fieldsToReturn = 0, int queryOptions = 0); + + /** insert */ + virtual void insert( const string &ns , BSONObj obj ) { + checkMaster().insert(ns, obj); + } + + /** insert multiple objects. Note that single object insert is asynchronous, so this version + is only nominally faster and not worth a special effort to try to use. */ + virtual void insert( const string &ns, const vector< BSONObj >& v ) { + checkMaster().insert(ns, v); + } + + /** remove */ + virtual void remove( const string &ns , Query obj , bool justOne = 0 ) { + checkMaster().remove(ns, obj, justOne); + } + + /** update */ + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ) { + return checkMaster().update(ns, query, obj, upsert,multi); + } + + string toString(); + + /* this is the callback from our underlying connections to notify us that we got a "not master" error. + */ + void isntMaster() { + master = ( ( master == Left ) ? NotSetR : NotSetL ); + } + + string getServerAddress() const { + return left.getServerAddress() + "," + right.getServerAddress(); + } + + DBClientConnection& slaveConn(); + + /* TODO - not yet implemented. mongos may need these. */ + virtual bool call( Message &toSend, Message &response, bool assertOk=true ) { assert(false); return false; } + virtual void say( Message &toSend ) { assert(false); } + virtual void sayPiggyBack( Message &toSend ) { assert(false); } + virtual void checkResponse( const char *data, int nReturned ) { assert(false); } + + bool isFailed() const { + // TODO: this really should check isFailed on current master as well + return master > NotSetR; + } + }; + + + DBClientBase * createDirectClient(); + +} // namespace mongo diff --git a/client/examples/authTest.cpp b/client/examples/authTest.cpp new file mode 100644 index 0000000..77ce12d --- /dev/null +++ b/client/examples/authTest.cpp @@ -0,0 +1,53 @@ +// authTest.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> + +#include "client/dbclient.h" + +using namespace mongo; + +int main( int argc, const char **argv ) { + + const char *port = "27017"; + if ( argc != 1 ) { + if ( argc != 3 ) + throw -12; + port = argv[ 2 ]; + } + + DBClientConnection conn; + string errmsg; + if ( ! conn.connect( string( "127.0.0.1:" ) + port , errmsg ) ) { + cout << "couldn't connect : " << errmsg << endl; + throw -11; + } + + { // clean up old data from any previous tests + conn.remove( "test.system.users" , BSONObj() ); + } + + conn.insert( "test.system.users" , BSON( "user" << "eliot" << "pwd" << conn.createPasswordDigest( "eliot" , "bar" ) ) ); + + errmsg.clear(); + bool ok = conn.auth( "test" , "eliot" , "bar" , errmsg ); + if ( ! ok ) + cout << errmsg << endl; + assert( ok ); + + assert( ! conn.auth( "test" , "eliot" , "bars" , errmsg ) ); +} diff --git a/client/examples/clientTest.cpp b/client/examples/clientTest.cpp new file mode 100644 index 0000000..bbb82f6 --- /dev/null +++ b/client/examples/clientTest.cpp @@ -0,0 +1,214 @@ +// clientTest.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * a simple test for the c++ driver + */ + +#include <iostream> + +#include "client/dbclient.h" + +using namespace std; +using namespace mongo; + +int main( int argc, const char **argv ) { + + const char *port = "27017"; + if ( argc != 1 ) { + if ( argc != 3 ) + throw -12; + port = argv[ 2 ]; + } + + DBClientConnection conn; + string errmsg; + if ( ! conn.connect( string( "127.0.0.1:" ) + port , errmsg ) ) { + cout << "couldn't connect : " << errmsg << endl; + throw -11; + } + + const char * ns = "test.test1"; + + conn.dropCollection(ns); + + // clean up old data from any previous tests + conn.remove( ns, BSONObj() ); + assert( conn.findOne( ns , BSONObj() ).isEmpty() ); + + // test insert + conn.insert( ns ,BSON( "name" << "eliot" << "num" << 1 ) ); + assert( ! conn.findOne( ns , BSONObj() ).isEmpty() ); + + // test remove + conn.remove( ns, BSONObj() ); + assert( conn.findOne( ns , BSONObj() ).isEmpty() ); + + + // insert, findOne testing + conn.insert( ns , BSON( "name" << "eliot" << "num" << 1 ) ); + { + BSONObj res = conn.findOne( ns , BSONObj() ); + assert( strstr( res.getStringField( "name" ) , "eliot" ) ); + assert( ! strstr( res.getStringField( "name2" ) , "eliot" ) ); + assert( 1 == res.getIntField( "num" ) ); + } + + + // cursor + conn.insert( ns ,BSON( "name" << "sara" << "num" << 2 ) ); + { + auto_ptr<DBClientCursor> cursor = conn.query( ns , BSONObj() ); + int count = 0; + while ( cursor->more() ) { + count++; + BSONObj obj = cursor->next(); + } + assert( count == 2 ); + } + + { + auto_ptr<DBClientCursor> cursor = conn.query( ns , BSON( "num" << 1 ) ); + int count = 0; + while ( cursor->more() ) { + count++; + BSONObj obj = cursor->next(); + } + assert( count == 1 ); + } + + { + auto_ptr<DBClientCursor> cursor = conn.query( ns , BSON( "num" << 3 ) ); + int count = 0; + while ( cursor->more() ) { + count++; + BSONObj obj = cursor->next(); + } + assert( count == 0 ); + } + + // update + { + BSONObj res = conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot" ).obj() ); + assert( ! strstr( res.getStringField( "name2" ) , "eliot" ) ); + + BSONObj after = BSONObjBuilder().appendElements( res ).append( "name2" , "h" ).obj(); + + conn.update( ns , BSONObjBuilder().append( "name" , "eliot2" ).obj() , after ); + res = conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot" ).obj() ); + assert( ! strstr( res.getStringField( "name2" ) , "eliot" ) ); + assert( conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot2" ).obj() ).isEmpty() ); + + conn.update( ns , BSONObjBuilder().append( "name" , "eliot" ).obj() , after ); + res = conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot" ).obj() ); + assert( strstr( res.getStringField( "name" ) , "eliot" ) ); + assert( strstr( res.getStringField( "name2" ) , "h" ) ); + assert( conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot2" ).obj() ).isEmpty() ); + + // upsert + conn.update( ns , BSONObjBuilder().append( "name" , "eliot2" ).obj() , after , 1 ); + assert( ! conn.findOne( ns , BSONObjBuilder().append( "name" , "eliot" ).obj() ).isEmpty() ); + + } + + { // ensure index + assert( conn.ensureIndex( ns , BSON( "name" << 1 ) ) ); + assert( ! conn.ensureIndex( ns , BSON( "name" << 1 ) ) ); + } + + { // hint related tests + assert( conn.findOne(ns, "{}")["name"].str() == "sara" ); + + assert( conn.findOne(ns, "{ name : 'eliot' }")["name"].str() == "eliot" ); + assert( conn.getLastError() == "" ); + + // nonexistent index test + assert( conn.findOne(ns, Query("{name:\"eliot\"}").hint("{foo:1}")).hasElement("$err") ); + assert( conn.getLastError() == "bad hint" ); + conn.resetError(); + assert( conn.getLastError() == "" ); + + //existing index + assert( conn.findOne(ns, Query("{name:'eliot'}").hint("{name:1}")).hasElement("name") ); + + // run validate + assert( conn.validate( ns ) ); + } + + { // timestamp test + + const char * tsns = "test.tstest1"; + conn.dropCollection( tsns ); + + { + mongo::BSONObjBuilder b; + b.appendTimestamp( "ts" ); + conn.insert( tsns , b.obj() ); + } + + mongo::BSONObj out = conn.findOne( tsns , mongo::BSONObj() ); + Date_t oldTime = out["ts"].timestampTime(); + unsigned int oldInc = out["ts"].timestampInc(); + + { + mongo::BSONObjBuilder b1; + b1.append( out["_id"] ); + + mongo::BSONObjBuilder b2; + b2.append( out["_id"] ); + b2.appendTimestamp( "ts" ); + + conn.update( tsns , b1.obj() , b2.obj() ); + } + + BSONObj found = conn.findOne( tsns , mongo::BSONObj() ); + assert( ( oldTime < found["ts"].timestampTime() ) || + ( oldInc + 1 == found["ts"].timestampInc() ) ); + + } + + { // check that killcursors doesn't affect last error + assert( conn.getLastError().empty() ); + + BufBuilder b; + b.append( (int)0 ); // reserved + b.append( (int)-1 ); // invalid # of cursors triggers exception + b.append( (int)-1 ); // bogus cursor id + + Message m; + m.setData( dbKillCursors, b.buf(), b.len() ); + + // say() is protected in DBClientConnection, so get superclass + static_cast< DBConnector* >( &conn )->say( m ); + + assert( conn.getLastError().empty() ); + } + + { + list<string> l = conn.getDatabaseNames(); + for ( list<string>::iterator i = l.begin(); i != l.end(); i++ ){ + cout << "db name : " << *i << endl; + } + + l = conn.getCollectionNames( "test" ); + for ( list<string>::iterator i = l.begin(); i != l.end(); i++ ){ + cout << "coll name : " << *i << endl; + } + } + + cout << "client test finished!" << endl; +} diff --git a/client/examples/first.cpp b/client/examples/first.cpp new file mode 100644 index 0000000..f3b654f --- /dev/null +++ b/client/examples/first.cpp @@ -0,0 +1,85 @@ +// first.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * this is a good first example of how to use mongo from c++ + */ + +#include <iostream> + +#include "client/dbclient.h" + +using namespace std; + +void insert( mongo::DBClientConnection & conn , const char * name , int num ) { + mongo::BSONObjBuilder obj; + obj.append( "name" , name ); + obj.append( "num" , num ); + conn.insert( "test.people" , obj.obj() ); +} + +int main( int argc, const char **argv ) { + + const char *port = "27017"; + if ( argc != 1 ) { + if ( argc != 3 ) + throw -12; + port = argv[ 2 ]; + } + + mongo::DBClientConnection conn; + string errmsg; + if ( ! conn.connect( string( "127.0.0.1:" ) + port , errmsg ) ) { + cout << "couldn't connect : " << errmsg << endl; + throw -11; + } + + { // clean up old data from any previous tests + mongo::BSONObjBuilder query; + conn.remove( "test.people" , query.obj() ); + } + + insert( conn , "eliot" , 15 ); + insert( conn , "sara" , 23 ); + + { + mongo::BSONObjBuilder query; + auto_ptr<mongo::DBClientCursor> cursor = conn.query( "test.people" , query.obj() ); + cout << "using cursor" << endl; + while ( cursor->more() ) { + mongo::BSONObj obj = cursor->next(); + cout << "\t" << obj.jsonString() << endl; + } + + } + + { + mongo::BSONObjBuilder query; + query.append( "name" , "eliot" ); + mongo::BSONObj res = conn.findOne( "test.people" , query.obj() ); + cout << res.isEmpty() << "\t" << res.jsonString() << endl; + } + + { + mongo::BSONObjBuilder query; + query.append( "name" , "asd" ); + mongo::BSONObj res = conn.findOne( "test.people" , query.obj() ); + cout << res.isEmpty() << "\t" << res.jsonString() << endl; + } + + +} diff --git a/client/examples/second.cpp b/client/examples/second.cpp new file mode 100644 index 0000000..68eafaa --- /dev/null +++ b/client/examples/second.cpp @@ -0,0 +1,56 @@ +// second.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> + +#include "client/dbclient.h" + +using namespace std; +using namespace mongo; + +int main( int argc, const char **argv ) { + + const char *port = "27017"; + if ( argc != 1 ) { + if ( argc != 3 ) + throw -12; + port = argv[ 2 ]; + } + + DBClientConnection conn; + string errmsg; + if ( ! conn.connect( string( "127.0.0.1:" ) + port , errmsg ) ) { + cout << "couldn't connect : " << errmsg << endl; + throw -11; + } + + const char * ns = "test.second"; + + conn.remove( ns , BSONObj() ); + + conn.insert( ns , BSON( "name" << "eliot" << "num" << 17 ) ); + conn.insert( ns , BSON( "name" << "sara" << "num" << 24 ) ); + + auto_ptr<DBClientCursor> cursor = conn.query( ns , BSONObj() ); + cout << "using cursor" << endl; + while ( cursor->more() ) { + BSONObj obj = cursor->next(); + cout << "\t" << obj.jsonString() << endl; + } + + conn.ensureIndex( ns , BSON( "name" << 1 << "num" << -1 ) ); +} diff --git a/client/examples/tail.cpp b/client/examples/tail.cpp new file mode 100644 index 0000000..e844b32 --- /dev/null +++ b/client/examples/tail.cpp @@ -0,0 +1,55 @@ +// tail.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* example of using a tailable cursor */ + +#include "../../client/dbclient.h" +#include "../../util/goodies.h" + +using namespace mongo; + +void foo() { } + +/* "tail" the specified namespace, outputting elements as they are added. + _id values must be inserted in increasing order for this to work. (Some other + field could also be used.) + + Note: one could use a capped collection and $natural order to do something + similar, using sort({$natural:1}), and then not need to worry about + _id's being in order. +*/ +void tail(DBClientBase& conn, const char *ns) { + conn.ensureIndex(ns, fromjson("{_id:1}")); + BSONElement lastId; + Query query = Query().sort("_id"); + while( 1 ) { + auto_ptr<DBClientCursor> c = conn.query(ns, query, 0, 0, 0, Option_CursorTailable); + while( 1 ) { + if( !c->more() ) { + if( c->isDead() ) { + // we need to requery + break; + } + sleepsecs(1); + } + BSONObj o = c->next(); + lastId = o["_id"]; + cout << o.toString() << endl; + } + query = QUERY( "_id" << GT << lastId ).sort("_id"); + } +} diff --git a/client/examples/tutorial.cpp b/client/examples/tutorial.cpp new file mode 100644 index 0000000..28e1b27 --- /dev/null +++ b/client/examples/tutorial.cpp @@ -0,0 +1,67 @@ +//tutorial.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> +#include "../../client/dbclient.h" + +// g++ tutorial.cpp -lmongoclient -lboost_thread -lboost_filesystem -o tutorial + +using namespace mongo; + +void printIfAge(DBClientConnection& c, int age) { + auto_ptr<DBClientCursor> cursor = c.query("tutorial.persons", QUERY( "age" << age ).sort("name") ); + while( cursor->more() ) { + BSONObj p = cursor->next(); + cout << p.getStringField("name") << endl; + } +} + +void run() { + DBClientConnection c; + c.connect("localhost"); //"192.168.58.1"); + cout << "connected ok" << endl; + BSONObj p = BSON( "name" << "Joe" << "age" << 33 ); + c.insert("tutorial.persons", p); + p = BSON( "name" << "Jane" << "age" << 40 ); + c.insert("tutorial.persons", p); + p = BSON( "name" << "Abe" << "age" << 33 ); + c.insert("tutorial.persons", p); + p = BSON( "name" << "Samantha" << "age" << 21 << "city" << "Los Angeles" << "state" << "CA" ); + c.insert("tutorial.persons", p); + + c.ensureIndex("tutorial.persons", fromjson("{age:1}")); + + cout << "count:" << c.count("tutorial.persons") << endl; + + auto_ptr<DBClientCursor> cursor = c.query("tutorial.persons", BSONObj()); + while( cursor->more() ) { + cout << cursor->next().toString() << endl; + } + + cout << "\nprintifage:\n"; + printIfAge(c, 33); +} + +int main() { + try { + run(); + } + catch( DBException &e ) { + cout << "caught " << e.what() << endl; + } + return 0; +} diff --git a/client/examples/whereExample.cpp b/client/examples/whereExample.cpp new file mode 100644 index 0000000..a26d921 --- /dev/null +++ b/client/examples/whereExample.cpp @@ -0,0 +1,68 @@ +// whereExample.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> + +#include "client/dbclient.h" + +using namespace std; +using namespace mongo; + +int main( int argc, const char **argv ) { + + const char *port = "27017"; + if ( argc != 1 ) { + if ( argc != 3 ) + throw -12; + port = argv[ 2 ]; + } + + DBClientConnection conn; + string errmsg; + if ( ! conn.connect( string( "127.0.0.1:" ) + port , errmsg ) ) { + cout << "couldn't connect : " << errmsg << endl; + throw -11; + } + + const char * ns = "test.where"; + + conn.remove( ns , BSONObj() ); + + conn.insert( ns , BSON( "name" << "eliot" << "num" << 17 ) ); + conn.insert( ns , BSON( "name" << "sara" << "num" << 24 ) ); + + auto_ptr<DBClientCursor> cursor = conn.query( ns , BSONObj() ); + + while ( cursor->more() ) { + BSONObj obj = cursor->next(); + cout << "\t" << obj.jsonString() << endl; + } + + cout << "now using $where" << endl; + + Query q = Query("{}").where("this.name == name" , BSON( "name" << "sara" )); + + cursor = conn.query( ns , q ); + + int num = 0; + while ( cursor->more() ) { + BSONObj obj = cursor->next(); + cout << "\t" << obj.jsonString() << endl; + num++; + } + assert( num == 1 ); +} diff --git a/client/gridfs.cpp b/client/gridfs.cpp new file mode 100644 index 0000000..892ec6e --- /dev/null +++ b/client/gridfs.cpp @@ -0,0 +1,233 @@ +// gridfs.cpp + +/* Copyright 2009 10gen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../stdafx.h" +#include <fcntl.h> +#include <utility> + +#include "gridfs.h" +#include <boost/smart_ptr.hpp> + +#if defined(_WIN32) +#include <io.h> +#endif + +#ifndef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +#endif + +namespace mongo { + + const unsigned DEFAULT_CHUNK_SIZE = 256 * 1024; + + Chunk::Chunk( BSONObj o ){ + _data = o; + } + + Chunk::Chunk( BSONObj fileObject , int chunkNumber , const char * data , int len ){ + BSONObjBuilder b; + b.appendAs( fileObject["_id"] , "files_id" ); + b.append( "n" , chunkNumber ); + b.appendBinDataArray( "data" , data , len ); + _data = b.obj(); + } + + + GridFS::GridFS( DBClientBase& client , const string& dbName , const string& prefix ) : _client( client ) , _dbName( dbName ) , _prefix( prefix ){ + _filesNS = dbName + "." + prefix + ".files"; + _chunksNS = dbName + "." + prefix + ".chunks"; + + + client.ensureIndex( _filesNS , BSON( "filename" << 1 ) ); + client.ensureIndex( _chunksNS , BSON( "files_id" << 1 << "n" << 1 ) ); + } + + GridFS::~GridFS(){ + + } + + BSONObj GridFS::storeFile( const char* data , size_t length , const string& remoteName , const string& contentType){ + massert( 10279 , "large files not yet implemented", length <= 0xffffffff); + char const * const end = data + length; + + OID id; + id.init(); + BSONObj idObj = BSON("_id" << id); + + int chunkNumber = 0; + while (data < end){ + int chunkLen = MIN(DEFAULT_CHUNK_SIZE, (unsigned)(end-data)); + Chunk c(idObj, chunkNumber, data, chunkLen); + _client.insert( _chunksNS.c_str() , c._data ); + + chunkNumber++; + data += chunkLen; + } + + return insertFile(remoteName, id, length, contentType); + } + + + BSONObj GridFS::storeFile( const string& fileName , const string& remoteName , const string& contentType){ + uassert( 10012 , "file doesn't exist" , fileName == "-" || boost::filesystem::exists( fileName ) ); + + FILE* fd; + if (fileName == "-") + fd = stdin; + else + fd = fopen( fileName.c_str() , "rb" ); + uassert( 10013 , "error opening file", fd); + + OID id; + id.init(); + BSONObj idObj = BSON("_id" << id); + + int chunkNumber = 0; + gridfs_offset length = 0; + while (!feof(fd)){ + boost::scoped_array<char>buf (new char[DEFAULT_CHUNK_SIZE]); + char* bufPos = buf.get(); + unsigned int chunkLen = 0; // how much in the chunk now + while(chunkLen != DEFAULT_CHUNK_SIZE && !feof(fd)){ + int readLen = fread(bufPos, 1, DEFAULT_CHUNK_SIZE - chunkLen, fd); + chunkLen += readLen; + bufPos += readLen; + + assert(chunkLen <= DEFAULT_CHUNK_SIZE); + } + + Chunk c(idObj, chunkNumber, buf.get(), chunkLen); + _client.insert( _chunksNS.c_str() , c._data ); + + length += chunkLen; + chunkNumber++; + } + + if (fd != stdin) + fclose( fd ); + + massert( 10280 , "large files not yet implemented", length <= 0xffffffff); + + return insertFile((remoteName.empty() ? fileName : remoteName), id, length, contentType); + } + + BSONObj GridFS::insertFile(const string& name, const OID& id, unsigned length, const string& contentType){ + + BSONObj res; + if ( ! _client.runCommand( _dbName.c_str() , BSON( "filemd5" << id << "root" << _prefix ) , res ) ) + throw UserException( 9008 , "filemd5 failed" ); + + BSONObjBuilder file; + file << "_id" << id + << "filename" << name + << "length" << (unsigned) length + << "chunkSize" << DEFAULT_CHUNK_SIZE + << "uploadDate" << DATENOW + << "md5" << res["md5"] + ; + + if (!contentType.empty()) + file << "contentType" << contentType; + + BSONObj ret = file.obj(); + _client.insert(_filesNS.c_str(), ret); + + return ret; + } + + void GridFS::removeFile( const string& fileName ){ + auto_ptr<DBClientCursor> files = _client.query( _filesNS , BSON( "filename" << fileName ) ); + while (files->more()){ + BSONObj file = files->next(); + BSONElement id = file["_id"]; + _client.remove( _filesNS.c_str() , BSON( "_id" << id ) ); + _client.remove( _chunksNS.c_str() , BSON( "files_id" << id ) ); + } + } + + GridFile::GridFile( GridFS * grid , BSONObj obj ){ + _grid = grid; + _obj = obj; + } + + GridFile GridFS::findFile( const string& fileName ){ + return findFile( BSON( "filename" << fileName ) ); + }; + + GridFile GridFS::findFile( BSONObj query ){ + query = BSON("query" << query << "orderby" << BSON("uploadDate" << -1)); + return GridFile( this , _client.findOne( _filesNS.c_str() , query ) ); + } + + auto_ptr<DBClientCursor> GridFS::list(){ + return _client.query( _filesNS.c_str() , BSONObj() ); + } + + auto_ptr<DBClientCursor> GridFS::list( BSONObj o ){ + return _client.query( _filesNS.c_str() , o ); + } + + BSONObj GridFile::getMetadata(){ + BSONElement meta_element = _obj["metadata"]; + if( meta_element.eoo() ){ + return BSONObj(); + } + + return meta_element.embeddedObject(); + } + + Chunk GridFile::getChunk( int n ){ + _exists(); + BSONObjBuilder b; + b.appendAs( _obj["_id"] , "files_id" ); + b.append( "n" , n ); + + BSONObj o = _grid->_client.findOne( _grid->_chunksNS.c_str() , b.obj() ); + uassert( 10014 , "chunk is empty!" , ! o.isEmpty() ); + return Chunk(o); + } + + gridfs_offset GridFile::write( ostream & out ){ + _exists(); + + const int num = getNumChunks(); + + for ( int i=0; i<num; i++ ){ + Chunk c = getChunk( i ); + + int len; + const char * data = c.data( len ); + out.write( data , len ); + } + + return getContentLength(); + } + + gridfs_offset GridFile::write( const string& where ){ + if (where == "-"){ + return write( cout ); + } else { + ofstream out(where.c_str() , ios::out | ios::binary ); + return write( out ); + } + } + + void GridFile::_exists(){ + uassert( 10015 , "doesn't exists" , exists() ); + } + +} diff --git a/client/gridfs.h b/client/gridfs.h new file mode 100644 index 0000000..3165d5f --- /dev/null +++ b/client/gridfs.h @@ -0,0 +1,203 @@ +/** @file gridfs.h */ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "dbclient.h" + +namespace mongo { + + typedef unsigned long long gridfs_offset; + + class GridFS; + class GridFile; + + class Chunk { + public: + Chunk( BSONObj data ); + Chunk( BSONObj fileId , int chunkNumber , const char * data , int len ); + + int len(){ + int len; + const char * data = _data["data"].binData( len ); + int * foo = (int*)data; + assert( len - 4 == foo[0] ); + return len - 4; + } + + const char * data( int & len ){ + const char * data = _data["data"].binData( len ); + int * foo = (int*)data; + assert( len - 4 == foo[0] ); + + len = len - 4; + return data + 4; + } + + private: + BSONObj _data; + friend class GridFS; + }; + + + /** + this is the main entry point into the mongo grid fs + */ + class GridFS{ + public: + /** + * @param client - db connection + * @param dbName - root database name + * @param prefix - if you want your data somewhere besides <dbname>.fs + */ + GridFS( DBClientBase& client , const string& dbName , const string& prefix="fs" ); + ~GridFS(); + + /** + * puts the file reference by fileName into the db + * @param fileName local filename relative to process + * @param remoteName optional filename to use for file stored in GridFS + * (default is to use fileName parameter) + * @param contentType optional MIME type for this object. + * (default is to omit) + * @return the file object + */ + BSONObj storeFile( const string& fileName , const string& remoteName="" , const string& contentType=""); + + /** + * puts the file represented by data into the db + * @param data pointer to buffer to store in GridFS + * @param length length of buffer + * @param remoteName optional filename to use for file stored in GridFS + * (default is to use fileName parameter) + * @param contentType optional MIME type for this object. + * (default is to omit) + * @return the file object + */ + BSONObj storeFile( const char* data , size_t length , const string& remoteName , const string& contentType=""); + /** + * removes file referenced by fileName from the db + * @param fileName filename (in GridFS) of the file to remove + * @return the file object + */ + void removeFile( const string& fileName ); + + /** + * returns a file object matching the query + */ + GridFile findFile( BSONObj query ); + + /** + * equiv to findFile( { filename : filename } ) + */ + GridFile findFile( const string& fileName ); + + /** + * convenience method to get all the files + */ + auto_ptr<DBClientCursor> list(); + + /** + * convenience method to get all the files with a filter + */ + auto_ptr<DBClientCursor> list( BSONObj query ); + + private: + DBClientBase& _client; + string _dbName; + string _prefix; + string _filesNS; + string _chunksNS; + + // insert fileobject. All chunks must be in DB. + BSONObj insertFile(const string& name, const OID& id, unsigned length, const string& contentType); + + friend class GridFile; + }; + + /** + wrapper for a file stored in the Mongo database + */ + class GridFile { + public: + /** + * @return whether or not this file exists + * findFile will always return a GriFile, so need to check this + */ + bool exists(){ + return ! _obj.isEmpty(); + } + + string getFilename(){ + return _obj["filename"].str(); + } + + int getChunkSize(){ + return (int)(_obj["chunkSize"].number()); + } + + gridfs_offset getContentLength(){ + return (gridfs_offset)(_obj["length"].number()); + } + + string getContentType(){ + return _obj["contentType"].valuestr(); + } + + Date_t getUploadDate(){ + return _obj["uploadDate"].date(); + } + + string getMD5(){ + return _obj["md5"].str(); + } + + BSONElement getFileField( const string& name ){ + return _obj[name]; + } + + BSONObj getMetadata(); + + int getNumChunks(){ + return (int) ceil( (double)getContentLength() / (double)getChunkSize() ); + } + + Chunk getChunk( int n ); + + /** + write the file to the output stream + */ + gridfs_offset write( ostream & out ); + + /** + write the file to this filename + */ + gridfs_offset write( const string& where ); + + private: + GridFile( GridFS * grid , BSONObj obj ); + + void _exists(); + + GridFS * _grid; + BSONObj _obj; + + friend class GridFS; + }; +} + + diff --git a/client/model.cpp b/client/model.cpp new file mode 100644 index 0000000..3978105 --- /dev/null +++ b/client/model.cpp @@ -0,0 +1,97 @@ +// model.cpp + +/* Copyright 2009 10gen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "model.h" +#include "connpool.h" + +namespace mongo { + + bool Model::load(BSONObj& query){ + ScopedDbConnection conn( modelServer() ); + + BSONObj b = conn->findOne(getNS(), query); + conn.done(); + + if ( b.isEmpty() ) + return false; + + unserialize(b); + _id = b["_id"].wrap().getOwned(); + return true; + } + + void Model::remove( bool safe ){ + uassert( 10016 , "_id isn't set - needed for remove()" , _id["_id"].type() ); + + ScopedDbConnection conn( modelServer() ); + conn->remove( getNS() , _id ); + + string errmsg = ""; + if ( safe ) + errmsg = conn->getLastError(); + + conn.done(); + + if ( safe && errmsg.size() ) + throw UserException( 9002 , (string)"error on Model::remove: " + errmsg ); + } + + void Model::save( bool safe ){ + ScopedDbConnection conn( modelServer() ); + + BSONObjBuilder b; + serialize( b ); + + if ( _id.isEmpty() ){ + OID oid; + oid.init(); + b.appendOID( "_id" , &oid ); + + BSONObj o = b.obj(); + conn->insert( getNS() , o ); + _id = o["_id"].wrap().getOwned(); + + log(4) << "inserted new model " << getNS() << " " << o << endl; + } + else { + BSONElement id = _id["_id"]; + b.append( id ); + + BSONObjBuilder qb; + qb.append( id ); + + BSONObj q = qb.obj(); + BSONObj o = b.obj(); + + log(4) << "updated old model" << getNS() << " " << q << " " << o << endl; + + conn->update( getNS() , q , o ); + + } + + string errmsg = ""; + if ( safe ) + errmsg = conn->getLastError(); + + conn.done(); + + if ( safe && errmsg.size() ) + throw UserException( 9003 , (string)"error on Model::save: " + errmsg ); + } + +} // namespace mongo diff --git a/client/model.h b/client/model.h new file mode 100644 index 0000000..f3a63ad --- /dev/null +++ b/client/model.h @@ -0,0 +1,57 @@ +/** @file model.h */ + +/* Copyright 2009 10gen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "dbclient.h" + +namespace mongo { + + /** Model is a base class for defining objects which are serializable to the Mongo + database via the database driver. + + Definition + Your serializable class should inherit from Model and implement the abstract methods + below. + + Loading + To load, first construct an (empty) object. Then call load(). Do not load an object + more than once. + */ + class Model { + public: + Model() { } + virtual ~Model() { } + + virtual const char * getNS() = 0; + virtual void serialize(BSONObjBuilder& to) = 0; + virtual void unserialize(const BSONObj& from) = 0; + + virtual string modelServer() = 0; + + /** Load a single object. + @return true if successful. + */ + virtual bool load(BSONObj& query); + virtual void save( bool safe=false ); + virtual void remove( bool safe=false ); + + protected: + BSONObj _id; + }; + +} // namespace mongo diff --git a/client/parallel.cpp b/client/parallel.cpp new file mode 100644 index 0000000..449f436 --- /dev/null +++ b/client/parallel.cpp @@ -0,0 +1,259 @@ +// parallel.cpp + +#include "stdafx.h" +#include "parallel.h" +#include "connpool.h" +#include "../db/queryutil.h" +#include "../db/dbmessage.h" +#include "../s/util.h" + +namespace mongo { + + // -------- ClusteredCursor ----------- + + ClusteredCursor::ClusteredCursor( QueryMessage& q ){ + _ns = q.ns; + _query = q.query.copy(); + _options = q.queryOptions; + if ( q.fields.get() ) + _fields = q.fields->getSpec(); + _done = false; + } + + ClusteredCursor::ClusteredCursor( const string& ns , const BSONObj& q , int options , const BSONObj& fields ){ + _ns = ns; + _query = q.getOwned(); + _options = options; + _fields = fields.getOwned(); + _done = false; + } + + ClusteredCursor::~ClusteredCursor(){ + _done = true; // just in case + } + + auto_ptr<DBClientCursor> ClusteredCursor::query( const string& server , int num , BSONObj extra ){ + uassert( 10017 , "cursor already done" , ! _done ); + + BSONObj q = _query; + if ( ! extra.isEmpty() ){ + q = concatQuery( q , extra ); + } + + ScopedDbConnection conn( server ); + checkShardVersion( conn.conn() , _ns ); + + log(5) << "ClusteredCursor::query server:" << server << " ns:" << _ns << " query:" << q << " num:" << num << " _fields:" << _fields << " options: " << _options << endl; + auto_ptr<DBClientCursor> cursor = conn->query( _ns.c_str() , q , num , 0 , ( _fields.isEmpty() ? 0 : &_fields ) , _options ); + if ( cursor->hasResultFlag( QueryResult::ResultFlag_ShardConfigStale ) ) + throw StaleConfigException( _ns , "ClusteredCursor::query" ); + + conn.done(); + return cursor; + } + + BSONObj ClusteredCursor::concatQuery( const BSONObj& query , const BSONObj& extraFilter ){ + if ( ! query.hasField( "query" ) ) + return _concatFilter( query , extraFilter ); + + BSONObjBuilder b; + BSONObjIterator i( query ); + while ( i.more() ){ + BSONElement e = i.next(); + + if ( strcmp( e.fieldName() , "query" ) ){ + b.append( e ); + continue; + } + + b.append( "query" , _concatFilter( e.embeddedObjectUserCheck() , extraFilter ) ); + } + return b.obj(); + } + + BSONObj ClusteredCursor::_concatFilter( const BSONObj& filter , const BSONObj& extra ){ + BSONObjBuilder b; + b.appendElements( filter ); + b.appendElements( extra ); + return b.obj(); + // TODO: should do some simplification here if possibl ideally + } + + + // -------- SerialServerClusteredCursor ----------- + + SerialServerClusteredCursor::SerialServerClusteredCursor( set<ServerAndQuery> servers , QueryMessage& q , int sortOrder) : ClusteredCursor( q ){ + for ( set<ServerAndQuery>::iterator i = servers.begin(); i!=servers.end(); i++ ) + _servers.push_back( *i ); + + if ( sortOrder > 0 ) + sort( _servers.begin() , _servers.end() ); + else if ( sortOrder < 0 ) + sort( _servers.rbegin() , _servers.rend() ); + + _serverIndex = 0; + } + + bool SerialServerClusteredCursor::more(){ + if ( _current.get() && _current->more() ) + return true; + + if ( _serverIndex >= _servers.size() ){ + return false; + } + + ServerAndQuery& sq = _servers[_serverIndex++]; + + _current = query( sq._server , 0 , sq._extra ); + if ( _current->more() ) + return true; + + // this sq has nothing, so keep looking + return more(); + } + + BSONObj SerialServerClusteredCursor::next(){ + uassert( 10018 , "no more items" , more() ); + return _current->next(); + } + + // -------- ParallelSortClusteredCursor ----------- + + ParallelSortClusteredCursor::ParallelSortClusteredCursor( set<ServerAndQuery> servers , QueryMessage& q , + const BSONObj& sortKey ) + : ClusteredCursor( q ) , _servers( servers ){ + _sortKey = sortKey.getOwned(); + _init(); + } + + ParallelSortClusteredCursor::ParallelSortClusteredCursor( set<ServerAndQuery> servers , const string& ns , + const Query& q , + int options , const BSONObj& fields ) + : ClusteredCursor( ns , q.obj , options , fields ) , _servers( servers ){ + _sortKey = q.getSort().copy(); + _init(); + } + + void ParallelSortClusteredCursor::_init(){ + _numServers = _servers.size(); + _cursors = new auto_ptr<DBClientCursor>[_numServers]; + _nexts = new BSONObj[_numServers]; + + // TODO: parellize + int num = 0; + for ( set<ServerAndQuery>::iterator i = _servers.begin(); i!=_servers.end(); i++ ){ + const ServerAndQuery& sq = *i; + _cursors[num++] = query( sq._server , 0 , sq._extra ); + } + + } + + ParallelSortClusteredCursor::~ParallelSortClusteredCursor(){ + delete [] _cursors; + delete [] _nexts; + } + + bool ParallelSortClusteredCursor::more(){ + for ( int i=0; i<_numServers; i++ ){ + if ( ! _nexts[i].isEmpty() ) + return true; + + if ( _cursors[i].get() && _cursors[i]->more() ) + return true; + } + return false; + } + + BSONObj ParallelSortClusteredCursor::next(){ + advance(); + + BSONObj best = BSONObj(); + int bestFrom = -1; + + for ( int i=0; i<_numServers; i++){ + if ( _nexts[i].isEmpty() ) + continue; + + if ( best.isEmpty() ){ + best = _nexts[i]; + bestFrom = i; + continue; + } + + int comp = best.woSortOrder( _nexts[i] , _sortKey ); + if ( comp < 0 ) + continue; + + best = _nexts[i]; + bestFrom = i; + } + + uassert( 10019 , "no more elements" , ! best.isEmpty() ); + _nexts[bestFrom] = BSONObj(); + + return best; + } + + void ParallelSortClusteredCursor::advance(){ + for ( int i=0; i<_numServers; i++ ){ + + if ( ! _nexts[i].isEmpty() ){ + // already have a good object there + continue; + } + + if ( ! _cursors[i]->more() ){ + // cursor is dead, oh well + continue; + } + + _nexts[i] = _cursors[i]->next(); + } + + } + + // ----------------- + // ---- Future ----- + // ----------------- + + Future::CommandResult::CommandResult( const string& server , const string& db , const BSONObj& cmd ){ + _server = server; + _db = db; + _cmd = cmd; + _done = false; + } + + bool Future::CommandResult::join(){ + while ( ! _done ) + sleepmicros( 50 ); + return _ok; + } + + void Future::commandThread(){ + assert( _grab ); + shared_ptr<CommandResult> res = *_grab; + _grab = 0; + + ScopedDbConnection conn( res->_server ); + res->_ok = conn->runCommand( res->_db , res->_cmd , res->_res ); + res->_done = true; + } + + shared_ptr<Future::CommandResult> Future::spawnCommand( const string& server , const string& db , const BSONObj& cmd ){ + shared_ptr<Future::CommandResult> res; + res.reset( new Future::CommandResult( server , db , cmd ) ); + + _grab = &res; + + boost::thread thr( Future::commandThread ); + + while ( _grab ) + sleepmicros(2); + + return res; + } + + shared_ptr<Future::CommandResult> * Future::_grab; + + +} diff --git a/client/parallel.h b/client/parallel.h new file mode 100644 index 0000000..5a22624 --- /dev/null +++ b/client/parallel.h @@ -0,0 +1,195 @@ +// parallel.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + tools for wokring in parallel/sharded/clustered environment + */ + +#include "../stdafx.h" +#include "dbclient.h" +#include "../db/dbmessage.h" + +namespace mongo { + + /** + * this is a cursor that works over a set of servers + * can be used in serial/paralellel as controlled by sub classes + */ + class ClusteredCursor { + public: + ClusteredCursor( QueryMessage& q ); + ClusteredCursor( const string& ns , const BSONObj& q , int options=0 , const BSONObj& fields=BSONObj() ); + virtual ~ClusteredCursor(); + + virtual bool more() = 0; + virtual BSONObj next() = 0; + + static BSONObj concatQuery( const BSONObj& query , const BSONObj& extraFilter ); + + virtual string type() const = 0; + + protected: + auto_ptr<DBClientCursor> query( const string& server , int num = 0 , BSONObj extraFilter = BSONObj() ); + + static BSONObj _concatFilter( const BSONObj& filter , const BSONObj& extraFilter ); + + string _ns; + BSONObj _query; + int _options; + BSONObj _fields; + + bool _done; + }; + + + /** + * holder for a server address and a query to run + */ + class ServerAndQuery { + public: + ServerAndQuery( const string& server , BSONObj extra = BSONObj() , BSONObj orderObject = BSONObj() ) : + _server( server ) , _extra( extra.getOwned() ) , _orderObject( orderObject.getOwned() ){ + } + + bool operator<( const ServerAndQuery& other ) const{ + if ( ! _orderObject.isEmpty() ) + return _orderObject.woCompare( other._orderObject ) < 0; + + if ( _server < other._server ) + return true; + if ( other._server > _server ) + return false; + return _extra.woCompare( other._extra ) < 0; + } + + string toString() const { + StringBuilder ss; + ss << "server:" << _server << " _extra:" << _extra << " _orderObject:" << _orderObject; + return ss.str(); + } + + operator string() const { + return toString(); + } + + string _server; + BSONObj _extra; + BSONObj _orderObject; + }; + + + /** + * runs a query in serial across any number of servers + * returns all results from 1 server, then the next, etc... + */ + class SerialServerClusteredCursor : public ClusteredCursor { + public: + SerialServerClusteredCursor( set<ServerAndQuery> servers , QueryMessage& q , int sortOrder=0); + virtual bool more(); + virtual BSONObj next(); + virtual string type() const { return "SerialServer"; } + private: + vector<ServerAndQuery> _servers; + unsigned _serverIndex; + + auto_ptr<DBClientCursor> _current; + }; + + + /** + * runs a query in parellel across N servers + * sots + */ + class ParallelSortClusteredCursor : public ClusteredCursor { + public: + ParallelSortClusteredCursor( set<ServerAndQuery> servers , QueryMessage& q , const BSONObj& sortKey ); + ParallelSortClusteredCursor( set<ServerAndQuery> servers , const string& ns , + const Query& q , int options=0, const BSONObj& fields=BSONObj() ); + virtual ~ParallelSortClusteredCursor(); + virtual bool more(); + virtual BSONObj next(); + virtual string type() const { return "ParallelSort"; } + private: + void _init(); + + void advance(); + + int _numServers; + set<ServerAndQuery> _servers; + BSONObj _sortKey; + + auto_ptr<DBClientCursor> * _cursors; + BSONObj * _nexts; + }; + + /** + * tools for doing asynchronous operations + * right now uses underlying sync network ops and uses another thread + * should be changed to use non-blocking io + */ + class Future { + public: + class CommandResult { + public: + + string getServer() const { return _server; } + + bool isDone() const { return _done; } + + bool ok() const { + assert( _done ); + return _ok; + } + + BSONObj result() const { + assert( _done ); + return _res; + } + + /** + blocks until command is done + returns ok() + */ + bool join(); + + private: + + CommandResult( const string& server , const string& db , const BSONObj& cmd ); + + string _server; + string _db; + BSONObj _cmd; + + boost::thread _thr; + + BSONObj _res; + bool _done; + bool _ok; + + friend class Future; + }; + + static void commandThread(); + + static shared_ptr<CommandResult> spawnCommand( const string& server , const string& db , const BSONObj& cmd ); + + private: + static shared_ptr<CommandResult> * _grab; + }; + + +} diff --git a/client/syncclusterconnection.cpp b/client/syncclusterconnection.cpp new file mode 100644 index 0000000..b942709 --- /dev/null +++ b/client/syncclusterconnection.cpp @@ -0,0 +1,165 @@ +// syncclusterconnection.cpp + +#include "stdafx.h" +#include "syncclusterconnection.h" + +// error codes 8000-8009 + +namespace mongo { + + SyncCluterConnection::SyncCluterConnection( string commaSeperated ){ + string::size_type idx; + while ( ( idx = commaSeperated.find( ',' ) ) != string::npos ){ + string h = commaSeperated.substr( 0 , idx ); + commaSeperated = commaSeperated.substr( idx + 1 ); + _connect( h ); + } + _connect( commaSeperated ); + uassert( 8004 , "SyncCluterConnection needs 3 servers" , _conns.size() == 3 ); + } + + SyncCluterConnection::SyncCluterConnection( string a , string b , string c ){ + // connect to all even if not working + _connect( a ); + _connect( b ); + _connect( c ); + } + + SyncCluterConnection::~SyncCluterConnection(){ + for ( size_t i=0; i<_conns.size(); i++ ) + delete _conns[i]; + _conns.clear(); + } + + bool SyncCluterConnection::prepare( string& errmsg ){ + return fsync( errmsg ); + } + + bool SyncCluterConnection::fsync( string& errmsg ){ + bool ok = true; + errmsg = ""; + for ( size_t i=0; i<_conns.size(); i++ ){ + BSONObj res; + try { + if ( _conns[i]->simpleCommand( "admin" , 0 , "fsync" ) ) + continue; + } + catch ( std::exception& e ){ + errmsg += e.what(); + } + catch ( ... ){ + } + ok = false; + errmsg += _conns[i]->toString() + ":" + res.toString(); + } + return ok; + } + + void SyncCluterConnection::_checkLast(){ + vector<BSONObj> all; + vector<string> errors; + + for ( size_t i=0; i<_conns.size(); i++ ){ + BSONObj res; + string err; + try { + if ( ! _conns[i]->runCommand( "admin" , BSON( "getlasterror" << 1 << "fsync" << 1 ) , res ) ) + err = "cmd failed: "; + } + catch ( std::exception& e ){ + err += e.what(); + } + catch ( ... ){ + err += "unknown failure"; + } + all.push_back( res ); + errors.push_back( err ); + } + + assert( all.size() == errors.size() && all.size() == _conns.size() ); + + stringstream err; + bool ok = true; + + for ( size_t i = 0; i<_conns.size(); i++ ){ + BSONObj res = all[i]; + if ( res["ok"].trueValue() && res["fsyncFiles"].numberInt() > 0 ) + continue; + ok = false; + err << _conns[i]->toString() << ": " << res << " " << errors[i]; + } + + if ( ok ) + return; + throw UserException( 8001 , (string)"SyncCluterConnection write op failed: " + err.str() ); + } + + void SyncCluterConnection::_connect( string host ){ + log() << "SyncCluterConnection connecting to: " << host << endl; + DBClientConnection * c = new DBClientConnection( true ); + string errmsg; + if ( ! c->connect( host , errmsg ) ) + log() << "SyncCluterConnection connect fail to: " << host << " errmsg: " << errmsg << endl; + _conns.push_back( c ); + } + + auto_ptr<DBClientCursor> SyncCluterConnection::query(const string &ns, Query query, int nToReturn, int nToSkip, + const BSONObj *fieldsToReturn, int queryOptions){ + + uassert( 10021 , "$cmd not support yet in SyncCluterConnection::query" , ns.find( "$cmd" ) == string::npos ); + + for ( size_t i=0; i<_conns.size(); i++ ){ + try { + auto_ptr<DBClientCursor> cursor = + _conns[i]->query( ns , query , nToReturn , nToSkip , fieldsToReturn , queryOptions ); + if ( cursor.get() ) + return cursor; + log() << "query failed to: " << _conns[i]->toString() << " no data" << endl; + } + catch ( ... ){ + log() << "query failed to: " << _conns[i]->toString() << " exception" << endl; + } + } + throw UserException( 8002 , "all servers down!" ); + } + + auto_ptr<DBClientCursor> SyncCluterConnection::getMore( const string &ns, long long cursorId, int nToReturn, int options ){ + uassert( 10022 , "SyncCluterConnection::getMore not supported yet" , 0); + auto_ptr<DBClientCursor> c; + return c; + } + + void SyncCluterConnection::insert( const string &ns, BSONObj obj ){ + string errmsg; + if ( ! prepare( errmsg ) ) + throw UserException( 8003 , (string)"SyncCluterConnection::insert prepare failed: " + errmsg ); + + for ( size_t i=0; i<_conns.size(); i++ ){ + _conns[i]->insert( ns , obj ); + } + + _checkLast(); + } + + void SyncCluterConnection::insert( const string &ns, const vector< BSONObj >& v ){ + uassert( 10023 , "SyncCluterConnection bulk insert not implemented" , 0); + } + + void SyncCluterConnection::remove( const string &ns , Query query, bool justOne ){ assert(0); } + + void SyncCluterConnection::update( const string &ns , Query query , BSONObj obj , bool upsert , bool multi ){ assert(0); } + + string SyncCluterConnection::toString(){ + stringstream ss; + ss << "SyncCluterConnection ["; + for ( size_t i=0; i<_conns.size(); i++ ){ + if ( i > 0 ) + ss << ","; + ss << _conns[i]->toString(); + } + ss << "]"; + return ss.str(); + } + + +} diff --git a/client/syncclusterconnection.h b/client/syncclusterconnection.h new file mode 100644 index 0000000..c14a9bb --- /dev/null +++ b/client/syncclusterconnection.h @@ -0,0 +1,57 @@ +// syncclusterconnection.h + +#include "../stdafx.h" +#include "dbclient.h" + +namespace mongo { + + /** + * this is a connection to a cluster of servers that operate as one + * for super high durability + */ + class SyncCluterConnection : public DBClientWithCommands { + public: + /** + * @param commaSeperated should be 3 hosts comma seperated + */ + SyncCluterConnection( string commaSeperated ); + SyncCluterConnection( string a , string b , string c ); + ~SyncCluterConnection(); + + + /** + * @return true if all servers are up and ready for writes + */ + bool prepare( string& errmsg ); + + /** + * runs fsync on all servers + */ + bool fsync( string& errmsg ); + + // --- from DBClientInterface + + virtual auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn, int nToSkip, + const BSONObj *fieldsToReturn, int queryOptions); + + virtual auto_ptr<DBClientCursor> getMore( const string &ns, long long cursorId, int nToReturn, int options ); + + virtual void insert( const string &ns, BSONObj obj ); + + virtual void insert( const string &ns, const vector< BSONObj >& v ); + + virtual void remove( const string &ns , Query query, bool justOne ); + + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert , bool multi ); + + virtual string toString(); + private: + + void _checkLast(); + + void _connect( string host ); + vector<DBClientConnection*> _conns; + }; + + +}; diff --git a/db/btree.cpp b/db/btree.cpp new file mode 100644 index 0000000..8b910f5 --- /dev/null +++ b/db/btree.cpp @@ -0,0 +1,1050 @@ +// btree.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "db.h" +#include "btree.h" +#include "pdfile.h" +#include "json.h" +#include "clientcursor.h" +#include "client.h" +#include "dbhelpers.h" +#include "curop.h" + +namespace mongo { + +#define VERIFYTHISLOC dassert( thisLoc.btree() == this ); + + KeyNode::KeyNode(const BucketBasics& bb, const _KeyNode &k) : + prevChildBucket(k.prevChildBucket), + recordLoc(k.recordLoc), key(bb.data+k.keyDataOfs()) + { } + + const int KeyMax = BucketSize / 10; + + extern int otherTraceLevel; + const int split_debug = 0; + const int insert_debug = 0; + + /* BucketBasics --------------------------------------------------- */ + + inline void BucketBasics::modified(const DiskLoc& thisLoc) { + VERIFYTHISLOC + btreeStore->modified(thisLoc); + } + + int BucketBasics::Size() const { + assert( _Size == BucketSize ); + return _Size; + } + inline void BucketBasics::setNotPacked() { + flags &= ~Packed; + } + inline void BucketBasics::setPacked() { + flags |= Packed; + } + + void BucketBasics::_shape(int level, stringstream& ss) { + for ( int i = 0; i < level; i++ ) ss << ' '; + ss << "*\n"; + for ( int i = 0; i < n; i++ ) + if ( !k(i).prevChildBucket.isNull() ) + k(i).prevChildBucket.btree()->_shape(level+1,ss); + if ( !nextChild.isNull() ) + nextChild.btree()->_shape(level+1,ss); + } + + int bt_fv=0; + int bt_dmp=0; + + void BucketBasics::dumpTree(DiskLoc thisLoc, const BSONObj &order) { + bt_dmp=1; + fullValidate(thisLoc, order); + bt_dmp=0; + } + + int BucketBasics::fullValidate(const DiskLoc& thisLoc, const BSONObj &order) { + { + bool f = false; + assert( f = true ); + massert( 10281 , "assert is misdefined", f); + } + + killCurrentOp.checkForInterrupt(); + assertValid(order, true); +// if( bt_fv==0 ) +// return; + + if ( bt_dmp ) { + out() << thisLoc.toString() << ' '; + ((BtreeBucket *) this)->dump(); + } + + // keycount + int kc = 0; + + for ( int i = 0; i < n; i++ ) { + _KeyNode& kn = k(i); + + if ( kn.isUsed() ) kc++; + if ( !kn.prevChildBucket.isNull() ) { + DiskLoc left = kn.prevChildBucket; + BtreeBucket *b = left.btree(); + wassert( b->parent == thisLoc ); + kc += b->fullValidate(kn.prevChildBucket, order); + } + } + if ( !nextChild.isNull() ) { + BtreeBucket *b = nextChild.btree(); + wassert( b->parent == thisLoc ); + kc += b->fullValidate(nextChild, order); + } + + return kc; + } + + int nDumped = 0; + + void BucketBasics::assertValid(const BSONObj &order, bool force) { + if ( !debug && !force ) + return; + wassert( n >= 0 && n < Size() ); + wassert( emptySize >= 0 && emptySize < BucketSize ); + wassert( topSize >= n && topSize <= BucketSize ); + DEV { + // slow: + for ( int i = 0; i < n-1; i++ ) { + BSONObj k1 = keyNode(i).key; + BSONObj k2 = keyNode(i+1).key; + int z = k1.woCompare(k2, order); //OK + if ( z > 0 ) { + out() << "ERROR: btree key order corrupt. Keys:" << endl; + if ( ++nDumped < 5 ) { + for ( int j = 0; j < n; j++ ) { + out() << " " << keyNode(j).key.toString() << endl; + } + ((BtreeBucket *) this)->dump(); + } + wassert(false); + break; + } + else if ( z == 0 ) { + if ( !(k(i).recordLoc < k(i+1).recordLoc) ) { + out() << "ERROR: btree key order corrupt (recordloc's wrong). Keys:" << endl; + out() << " k(" << i << "):" << keyNode(i).key.toString() << " RL:" << k(i).recordLoc.toString() << endl; + out() << " k(" << i+1 << "):" << keyNode(i+1).key.toString() << " RL:" << k(i+1).recordLoc.toString() << endl; + wassert( k(i).recordLoc < k(i+1).recordLoc ); + } + } + } + } + else { + //faster: + if ( n > 1 ) { + BSONObj k1 = keyNode(0).key; + BSONObj k2 = keyNode(n-1).key; + int z = k1.woCompare(k2, order); + //wassert( z <= 0 ); + if ( z > 0 ) { + problem() << "btree keys out of order" << '\n'; + ONCE { + ((BtreeBucket *) this)->dump(); + } + assert(false); + } + } + } + } + + inline void BucketBasics::markUnused(int keypos) { + assert( keypos >= 0 && keypos < n ); + k(keypos).setUnused(); + } + + inline int BucketBasics::totalDataSize() const { + return Size() - (data-(char*)this); + } + + void BucketBasics::init() { + parent.Null(); + nextChild.Null(); + _Size = BucketSize; + flags = Packed; + n = 0; + emptySize = totalDataSize(); + topSize = 0; + reserved = 0; + } + + /* see _alloc */ + inline void BucketBasics::_unalloc(int bytes) { + topSize -= bytes; + emptySize += bytes; + } + + /* we allocate space from the end of the buffer for data. + the keynodes grow from the front. + */ + inline int BucketBasics::_alloc(int bytes) { + topSize += bytes; + emptySize -= bytes; + int ofs = totalDataSize() - topSize; + assert( ofs > 0 ); + return ofs; + } + + void BucketBasics::_delKeyAtPos(int keypos) { + assert( keypos >= 0 && keypos <= n ); + assert( childForPos(keypos).isNull() ); + n--; + assert( n > 0 || nextChild.isNull() ); + for ( int j = keypos; j < n; j++ ) + k(j) = k(j+1); + emptySize += sizeof(_KeyNode); + setNotPacked(); + } + + /* pull rightmost key from the bucket. this version requires its right child to be null so it + does not bother returning that value. + */ + void BucketBasics::popBack(DiskLoc& recLoc, BSONObj& key) { + massert( 10282 , "n==0 in btree popBack()", n > 0 ); + assert( k(n-1).isUsed() ); // no unused skipping in this function at this point - btreebuilder doesn't require that + KeyNode kn = keyNode(n-1); + recLoc = kn.recordLoc; + key = kn.key; + int keysize = kn.key.objsize(); + + massert( 10283 , "rchild not null in btree popBack()", nextChild.isNull()); + + /* weirdly, we also put the rightmost down pointer in nextchild, even when bucket isn't full. */ + nextChild = kn.prevChildBucket; + + n--; + emptySize += sizeof(_KeyNode); + _unalloc(keysize); + } + + /* add a key. must be > all existing. be careful to set next ptr right. */ + bool BucketBasics::_pushBack(const DiskLoc& recordLoc, BSONObj& key, const BSONObj &order, DiskLoc prevChild) { + int bytesNeeded = key.objsize() + sizeof(_KeyNode); + if ( bytesNeeded > emptySize ) + return false; + assert( bytesNeeded <= emptySize ); + assert( n == 0 || keyNode(n-1).key.woCompare(key, order) <= 0 ); + emptySize -= sizeof(_KeyNode); + _KeyNode& kn = k(n++); + kn.prevChildBucket = prevChild; + kn.recordLoc = recordLoc; + kn.setKeyDataOfs( (short) _alloc(key.objsize()) ); + char *p = dataAt(kn.keyDataOfs()); + memcpy(p, key.objdata(), key.objsize()); + return true; + } + /*void BucketBasics::pushBack(const DiskLoc& recordLoc, BSONObj& key, const BSONObj &order, DiskLoc prevChild, DiskLoc nextChild) { + pushBack(recordLoc, key, order, prevChild); + childForPos(n) = nextChild; + }*/ + + /* insert a key in a bucket with no complexity -- no splits required */ + bool BucketBasics::basicInsert(const DiskLoc& thisLoc, int keypos, const DiskLoc& recordLoc, const BSONObj& key, const BSONObj &order) { + modified(thisLoc); + assert( keypos >= 0 && keypos <= n ); + int bytesNeeded = key.objsize() + sizeof(_KeyNode); + if ( bytesNeeded > emptySize ) { + pack( order ); + if ( bytesNeeded > emptySize ) + return false; + } + for ( int j = n; j > keypos; j-- ) // make room + k(j) = k(j-1); + n++; + emptySize -= sizeof(_KeyNode); + _KeyNode& kn = k(keypos); + kn.prevChildBucket.Null(); + kn.recordLoc = recordLoc; + kn.setKeyDataOfs((short) _alloc(key.objsize()) ); + char *p = dataAt(kn.keyDataOfs()); + memcpy(p, key.objdata(), key.objsize()); + return true; + } + + /* when we delete things we just leave empty space until the node is + full and then we repack it. + */ + void BucketBasics::pack( const BSONObj &order ) { + if ( flags & Packed ) + return; + + int tdz = totalDataSize(); + char temp[BucketSize]; + int ofs = tdz; + topSize = 0; + for ( int j = 0; j < n; j++ ) { + short ofsold = k(j).keyDataOfs(); + int sz = keyNode(j).key.objsize(); + ofs -= sz; + topSize += sz; + memcpy(temp+ofs, dataAt(ofsold), sz); + k(j).setKeyDataOfsSavingUse( ofs ); + } + int dataUsed = tdz - ofs; + memcpy(data + ofs, temp + ofs, dataUsed); + emptySize = tdz - dataUsed - n * sizeof(_KeyNode); + assert( emptySize >= 0 ); + + setPacked(); + assertValid( order ); + } + + inline void BucketBasics::truncateTo(int N, const BSONObj &order) { + n = N; + setNotPacked(); + pack( order ); + } + + /* - BtreeBucket --------------------------------------------------- */ + + /* return largest key in the subtree. */ + void BtreeBucket::findLargestKey(const DiskLoc& thisLoc, DiskLoc& largestLoc, int& largestKey) { + DiskLoc loc = thisLoc; + while ( 1 ) { + BtreeBucket *b = loc.btree(); + if ( !b->nextChild.isNull() ) { + loc = b->nextChild; + continue; + } + + assert(b->n>0); + largestLoc = loc; + largestKey = b->n-1; + + break; + } + } + + bool BtreeBucket::exists(const IndexDetails& idx, DiskLoc thisLoc, const BSONObj& key, BSONObj order) { + int pos; + bool found; + DiskLoc b = locate(idx, thisLoc, key, order, pos, found, minDiskLoc); + + // skip unused keys + while ( 1 ) { + if( b.isNull() ) + break; + BtreeBucket *bucket = b.btree(); + _KeyNode& kn = bucket->k(pos); + if ( kn.isUsed() ) + return bucket->keyAt(pos).woEqual(key); + b = bucket->advance(b, pos, 1, "BtreeBucket::exists"); + } + return false; + } + + string BtreeBucket::dupKeyError( const IndexDetails& idx , const BSONObj& key ){ + stringstream ss; + ss << "E11000 duplicate key error"; + ss << "index: " << idx.indexNamespace() << " "; + ss << "dup key: " << key; + return ss.str(); + } + + /* Find a key withing this btree bucket. + + When duplicate keys are allowed, we use the DiskLoc of the record as if it were part of the + key. That assures that even when there are many duplicates (e.g., 1 million) for a key, + our performance is still good. + + assertIfDup: if the key exists (ignoring the recordLoc), uassert + + pos: for existing keys k0...kn-1. + returns # it goes BEFORE. so key[pos-1] < key < key[pos] + returns n if it goes after the last existing key. + note result might be an Unused location! + */ + char foo; + bool BtreeBucket::find(const IndexDetails& idx, const BSONObj& key, DiskLoc recordLoc, const BSONObj &order, int& pos, bool assertIfDup) { +#if defined(_EXPERIMENT1) + { + char *z = (char *) this; + int i = 0; + while( 1 ) { + i += 4096; + if( i >= BucketSize ) + break; + foo += z[i]; + } + } +#endif + /* binary search for this key */ + bool dupsChecked = false; + int l=0; + int h=n-1; + while ( l <= h ) { + int m = (l+h)/2; + KeyNode M = keyNode(m); + int x = key.woCompare(M.key, order); + if ( x == 0 ) { + if( assertIfDup ) { + if( k(m).isUnused() ) { + // ok that key is there if unused. but we need to check that there aren't other + // entries for the key then. as it is very rare that we get here, we don't put any + // coding effort in here to make this particularly fast + if( !dupsChecked ) { + dupsChecked = true; + if( idx.head.btree()->exists(idx, idx.head, key, order) ) + uasserted( ASSERT_ID_DUPKEY , dupKeyError( idx , key ) ); + } + } + else + uasserted( ASSERT_ID_DUPKEY , dupKeyError( idx , key ) ); + } + + // dup keys allowed. use recordLoc as if it is part of the key + DiskLoc unusedRL = M.recordLoc; + unusedRL.GETOFS() &= ~1; // so we can test equality without the used bit messing us up + x = recordLoc.compare(unusedRL); + } + if ( x < 0 ) // key < M.key + h = m-1; + else if ( x > 0 ) + l = m+1; + else { + // found it. + pos = m; + return true; + } + } + // not found + pos = l; + if ( pos != n ) { + BSONObj keyatpos = keyNode(pos).key; + wassert( key.woCompare(keyatpos, order) <= 0 ); + if ( pos > 0 ) { + wassert( keyNode(pos-1).key.woCompare(key, order) <= 0 ); + } + } + + return false; + } + + void BtreeBucket::delBucket(const DiskLoc& thisLoc, IndexDetails& id) { + ClientCursor::informAboutToDeleteBucket(thisLoc); + assert( !isHead() ); + + BtreeBucket *p = parent.btreemod(); + if ( p->nextChild == thisLoc ) { + p->nextChild.Null(); + } + else { + for ( int i = 0; i < p->n; i++ ) { + if ( p->k(i).prevChildBucket == thisLoc ) { + p->k(i).prevChildBucket.Null(); + goto found; + } + } + out() << "ERROR: can't find ref to deleted bucket.\n"; + out() << "To delete:\n"; + dump(); + out() << "Parent:\n"; + p->dump(); + assert(false); + } +found: +#if 1 + /* as a temporary defensive measure, we zap the whole bucket, AND don't truly delete + it (meaning it is ineligible for reuse). + */ + memset(this, 0, Size()); + modified(thisLoc); +#else + //defensive: + n = -1; + parent.Null(); + massert( 10284 , "todo: use RecStoreInterface instead", false); + // TODO: this was broken anyway as deleteRecord does unindexRecord() call which assumes the data is a BSONObj, + // and it isn't. + assert(false); +// theDataFileMgr.deleteRecord(id.indexNamespace().c_str(), thisLoc.rec(), thisLoc); +#endif + } + + /* note: may delete the entire bucket! this invalid upon return sometimes. */ + void BtreeBucket::delKeyAtPos(const DiskLoc& thisLoc, IndexDetails& id, int p) { + modified(thisLoc); + assert(n>0); + DiskLoc left = childForPos(p); + + if ( n == 1 ) { + if ( left.isNull() && nextChild.isNull() ) { + if ( isHead() ) + _delKeyAtPos(p); // we don't delete the top bucket ever + else + delBucket(thisLoc, id); + return; + } + markUnused(p); + return; + } + + if ( left.isNull() ) + _delKeyAtPos(p); + else + markUnused(p); + } + + int qqq = 0; + + /* remove a key from the index */ + bool BtreeBucket::unindex(const DiskLoc& thisLoc, IndexDetails& id, BSONObj& key, const DiskLoc& recordLoc ) { + if ( key.objsize() > KeyMax ) { + OCCASIONALLY problem() << "unindex: key too large to index, skipping " << id.indexNamespace() << /* ' ' << key.toString() << */ '\n'; + return false; + } + + int pos; + bool found; + DiskLoc loc = locate(id, thisLoc, key, id.keyPattern(), pos, found, recordLoc, 1); + if ( found ) { + loc.btree()->delKeyAtPos(loc, id, pos); + return true; + } + return false; + } + + BtreeBucket* BtreeBucket::allocTemp() { + BtreeBucket *b = (BtreeBucket*) malloc(BucketSize); + b->init(); + return b; + } + + inline void fix(const DiskLoc& thisLoc, const DiskLoc& child) { + if ( !child.isNull() ) { + if ( insert_debug ) + out() << " " << child.toString() << ".parent=" << thisLoc.toString() << endl; + child.btreemod()->parent = thisLoc; + } + } + + /* this sucks. maybe get rid of parent ptrs. */ + void BtreeBucket::fixParentPtrs(const DiskLoc& thisLoc) { + VERIFYTHISLOC + fix(thisLoc, nextChild); + for ( int i = 0; i < n; i++ ) + fix(thisLoc, k(i).prevChildBucket); + } + + /* insert a key in this bucket, splitting if necessary. + keypos - where to insert the key i3n range 0..n. 0=make leftmost, n=make rightmost. + */ + void BtreeBucket::insertHere(DiskLoc thisLoc, int keypos, + DiskLoc recordLoc, const BSONObj& key, const BSONObj& order, + DiskLoc lchild, DiskLoc rchild, IndexDetails& idx) + { + modified(thisLoc); + if ( insert_debug ) + out() << " " << thisLoc.toString() << ".insertHere " << key.toString() << '/' << recordLoc.toString() << ' ' + << lchild.toString() << ' ' << rchild.toString() << " keypos:" << keypos << endl; + + DiskLoc oldLoc = thisLoc; + + if ( basicInsert(thisLoc, keypos, recordLoc, key, order) ) { + _KeyNode& kn = k(keypos); + if ( keypos+1 == n ) { // last key + if ( nextChild != lchild ) { + out() << "ERROR nextChild != lchild" << endl; + out() << " thisLoc: " << thisLoc.toString() << ' ' << idx.indexNamespace() << endl; + out() << " keyPos: " << keypos << " n:" << n << endl; + out() << " nextChild: " << nextChild.toString() << " lchild: " << lchild.toString() << endl; + out() << " recordLoc: " << recordLoc.toString() << " rchild: " << rchild.toString() << endl; + out() << " key: " << key.toString() << endl; + dump(); +#if 0 + out() << "\n\nDUMPING FULL INDEX" << endl; + bt_dmp=1; + bt_fv=1; + idx.head.btree()->fullValidate(idx.head); +#endif + assert(false); + } + kn.prevChildBucket = nextChild; + assert( kn.prevChildBucket == lchild ); + nextChild = rchild; + if ( !rchild.isNull() ) + rchild.btreemod()->parent = thisLoc; + } + else { + k(keypos).prevChildBucket = lchild; + if ( k(keypos+1).prevChildBucket != lchild ) { + out() << "ERROR k(keypos+1).prevChildBucket != lchild" << endl; + out() << " thisLoc: " << thisLoc.toString() << ' ' << idx.indexNamespace() << endl; + out() << " keyPos: " << keypos << " n:" << n << endl; + out() << " k(keypos+1).pcb: " << k(keypos+1).prevChildBucket.toString() << " lchild: " << lchild.toString() << endl; + out() << " recordLoc: " << recordLoc.toString() << " rchild: " << rchild.toString() << endl; + out() << " key: " << key.toString() << endl; + dump(); +#if 0 + out() << "\n\nDUMPING FULL INDEX" << endl; + bt_dmp=1; + bt_fv=1; + idx.head.btree()->fullValidate(idx.head); +#endif + assert(false); + } + k(keypos+1).prevChildBucket = rchild; + if ( !rchild.isNull() ) + rchild.btreemod()->parent = thisLoc; + } + return; + } + + /* ---------- split ---------------- */ + + if ( split_debug ) + out() << " " << thisLoc.toString() << ".split" << endl; + + int mid = n / 2; + + DiskLoc rLoc = addBucket(idx); + BtreeBucket *r = rLoc.btreemod(); + if ( split_debug ) + out() << " mid:" << mid << ' ' << keyNode(mid).key.toString() << " n:" << n << endl; + for ( int i = mid+1; i < n; i++ ) { + KeyNode kn = keyNode(i); + r->pushBack(kn.recordLoc, kn.key, order, kn.prevChildBucket); + } + r->nextChild = nextChild; + r->assertValid( order ); + + if ( split_debug ) + out() << " new rLoc:" << rLoc.toString() << endl; + r = 0; + rLoc.btree()->fixParentPtrs(rLoc); + + { + KeyNode middle = keyNode(mid); + nextChild = middle.prevChildBucket; // middle key gets promoted, its children will be thisLoc (l) and rLoc (r) + if ( split_debug ) { + out() << " middle key:" << middle.key.toString() << endl; + } + + // promote middle to a parent node + if ( parent.isNull() ) { + // make a new parent if we were the root + DiskLoc L = addBucket(idx); + BtreeBucket *p = L.btreemod(); + p->pushBack(middle.recordLoc, middle.key, order, thisLoc); + p->nextChild = rLoc; + p->assertValid( order ); + parent = idx.head = L; + if ( split_debug ) + out() << " we were root, making new root:" << hex << parent.getOfs() << dec << endl; + rLoc.btreemod()->parent = parent; + } + else { + /* set this before calling _insert - if it splits it will do fixParent() logic and change the value. + */ + rLoc.btreemod()->parent = parent; + if ( split_debug ) + out() << " promoting middle key " << middle.key.toString() << endl; + parent.btree()->_insert(parent, middle.recordLoc, middle.key, order, /*dupsallowed*/true, thisLoc, rLoc, idx); + } + } + + truncateTo(mid, order); // note this may trash middle.key. thus we had to promote it before finishing up here. + + // add our new key, there is room now + { + + if ( keypos <= mid ) { + if ( split_debug ) + out() << " keypos<mid, insertHere() the new key" << endl; + insertHere(thisLoc, keypos, recordLoc, key, order, lchild, rchild, idx); + } else { + int kp = keypos-mid-1; + assert(kp>=0); + rLoc.btree()->insertHere(rLoc, kp, recordLoc, key, order, lchild, rchild, idx); + } + } + + if ( split_debug ) + out() << " split end " << hex << thisLoc.getOfs() << dec << endl; + } + + /* start a new index off, empty */ + DiskLoc BtreeBucket::addBucket(IndexDetails& id) { + DiskLoc loc = btreeStore->insert(id.indexNamespace().c_str(), 0, BucketSize, true); + BtreeBucket *b = loc.btreemod(); + b->init(); + return loc; + } + + void BtreeBucket::renameIndexNamespace(const char *oldNs, const char *newNs) { + btreeStore->rename( oldNs, newNs ); + } + + DiskLoc BtreeBucket::getHead(const DiskLoc& thisLoc) { + DiskLoc p = thisLoc; + while ( !p.btree()->isHead() ) + p = p.btree()->parent; + return p; + } + + DiskLoc BtreeBucket::advance(const DiskLoc& thisLoc, int& keyOfs, int direction, const char *caller) { + if ( keyOfs < 0 || keyOfs >= n ) { + out() << "ASSERT failure BtreeBucket::advance, caller: " << caller << endl; + out() << " thisLoc: " << thisLoc.toString() << endl; + out() << " keyOfs: " << keyOfs << " n:" << n << " direction: " << direction << endl; + out() << bucketSummary() << endl; + assert(false); + } + int adj = direction < 0 ? 1 : 0; + int ko = keyOfs + direction; + DiskLoc nextDown = childForPos(ko+adj); + if ( !nextDown.isNull() ) { + while ( 1 ) { + keyOfs = direction>0 ? 0 : nextDown.btree()->n - 1; + DiskLoc loc = nextDown.btree()->childForPos(keyOfs + adj); + if ( loc.isNull() ) + break; + nextDown = loc; + } + return nextDown; + } + + if ( ko < n && ko >= 0 ) { + keyOfs = ko; + return thisLoc; + } + + // end of bucket. traverse back up. + DiskLoc childLoc = thisLoc; + DiskLoc ancestor = parent; + while ( 1 ) { + if ( ancestor.isNull() ) + break; + BtreeBucket *an = ancestor.btree(); + for ( int i = 0; i < an->n; i++ ) { + if ( an->childForPos(i+adj) == childLoc ) { + keyOfs = i; + return ancestor; + } + } + assert( direction<0 || an->nextChild == childLoc ); + // parent exhausted also, keep going up + childLoc = ancestor; + ancestor = an->parent; + } + + return DiskLoc(); + } + + DiskLoc BtreeBucket::locate(const IndexDetails& idx, const DiskLoc& thisLoc, const BSONObj& key, const BSONObj &order, int& pos, bool& found, DiskLoc recordLoc, int direction) { + int p; + found = find(idx, key, recordLoc, order, p, /*assertIfDup*/ false); + if ( found ) { + pos = p; + return thisLoc; + } + + DiskLoc child = childForPos(p); + + if ( !child.isNull() ) { + DiskLoc l = child.btree()->locate(idx, child, key, order, pos, found, recordLoc, direction); + if ( !l.isNull() ) + return l; + } + + pos = p; + if ( direction < 0 ) + return --pos == -1 ? DiskLoc() /*theend*/ : thisLoc; + else + return pos == n ? DiskLoc() /*theend*/ : thisLoc; + } + + /* @thisLoc disk location of *this + */ + int BtreeBucket::_insert(DiskLoc thisLoc, DiskLoc recordLoc, + const BSONObj& key, const BSONObj &order, bool dupsAllowed, + DiskLoc lChild, DiskLoc rChild, IndexDetails& idx) { + if ( key.objsize() > KeyMax ) { + problem() << "ERROR: key too large len:" << key.objsize() << " max:" << KeyMax << ' ' << key.objsize() << ' ' << idx.indexNamespace() << endl; + return 2; + } + assert( key.objsize() > 0 ); + + int pos; + bool found = find(idx, key, recordLoc, order, pos, !dupsAllowed); + if ( insert_debug ) { + out() << " " << thisLoc.toString() << '.' << "_insert " << + key.toString() << '/' << recordLoc.toString() << + " l:" << lChild.toString() << " r:" << rChild.toString() << endl; + out() << " found:" << found << " pos:" << pos << " n:" << n << endl; + } + + if ( found ) { + _KeyNode& kn = k(pos); + if ( kn.isUnused() ) { + log(4) << "btree _insert: reusing unused key" << endl; + massert( 10285 , "_insert: reuse key but lchild is not null", lChild.isNull()); + massert( 10286 , "_insert: reuse key but rchild is not null", rChild.isNull()); + kn.setUsed(); + return 0; + } + + out() << "_insert(): key already exists in index\n"; + out() << " " << idx.indexNamespace().c_str() << " thisLoc:" << thisLoc.toString() << '\n'; + out() << " " << key.toString() << '\n'; + out() << " " << "recordLoc:" << recordLoc.toString() << " pos:" << pos << endl; + out() << " old l r: " << childForPos(pos).toString() << ' ' << childForPos(pos+1).toString() << endl; + out() << " new l r: " << lChild.toString() << ' ' << rChild.toString() << endl; + massert( 10287 , "btree: key+recloc already in index", false); + } + + DEBUGGING out() << "TEMP: key: " << key.toString() << endl; + DiskLoc& child = childForPos(pos); + if ( insert_debug ) + out() << " getChild(" << pos << "): " << child.toString() << endl; + if ( child.isNull() || !rChild.isNull() /* means an 'internal' insert */ ) { + insertHere(thisLoc, pos, recordLoc, key, order, lChild, rChild, idx); + return 0; + } + + return child.btree()->bt_insert(child, recordLoc, key, order, dupsAllowed, idx, /*toplevel*/false); + } + + void BtreeBucket::dump() { + out() << "DUMP btreebucket n:" << n; + out() << " parent:" << hex << parent.getOfs() << dec; + for ( int i = 0; i < n; i++ ) { + out() << '\n'; + KeyNode k = keyNode(i); + out() << '\t' << i << '\t' << k.key.toString() << "\tleft:" << hex << + k.prevChildBucket.getOfs() << "\tRecLoc:" << k.recordLoc.toString() << dec; + if ( this->k(i).isUnused() ) + out() << " UNUSED"; + } + out() << " right:" << hex << nextChild.getOfs() << dec << endl; + } + + /* todo: meaning of return code unclear clean up */ + int BtreeBucket::bt_insert(DiskLoc thisLoc, DiskLoc recordLoc, + const BSONObj& key, const BSONObj &order, bool dupsAllowed, + IndexDetails& idx, bool toplevel) + { + if ( toplevel ) { + if ( key.objsize() > KeyMax ) { + problem() << "Btree::insert: key too large to index, skipping " << idx.indexNamespace().c_str() << ' ' << key.objsize() << ' ' << key.toString() << '\n'; + return 3; + } + } + + int x = _insert(thisLoc, recordLoc, key, order, dupsAllowed, DiskLoc(), DiskLoc(), idx); + assertValid( order ); + + return x; + } + + void BtreeBucket::shape(stringstream& ss) { + _shape(0, ss); + } + + DiskLoc BtreeBucket::findSingle( const IndexDetails& indexdetails , const DiskLoc& thisLoc, const BSONObj& key ){ + int pos; + bool found; + DiskLoc bucket = locate( indexdetails , indexdetails.head , key , BSONObj() , pos , found , minDiskLoc ); + if ( bucket.isNull() ) + return bucket; + + BtreeBucket *b = bucket.btree(); + while ( 1 ){ + _KeyNode& knraw = b->k(pos); + if ( knraw.isUsed() ) + break; + bucket = b->advance( bucket , pos , 1 , "findSingle" ); + if ( bucket.isNull() ) + return bucket; + b = bucket.btree(); + } + KeyNode kn = b->keyNode( pos ); + if ( key.woCompare( kn.key ) != 0 ) + return DiskLoc(); + return kn.recordLoc; + } + +} // namespace mongo + +#include "db.h" +#include "dbhelpers.h" + +namespace mongo { + + void BtreeBucket::a_test(IndexDetails& id) { + BtreeBucket *b = id.head.btree(); + + // record locs for testing + DiskLoc A(1, 20); + DiskLoc B(1, 30); + DiskLoc C(1, 40); + + DiskLoc rl; + BSONObj key = fromjson("{x:9}"); + BSONObj order = fromjson("{}"); + + b->bt_insert(id.head, A, key, order, true, id); + A.GETOFS() += 2; + b->bt_insert(id.head, A, key, order, true, id); + A.GETOFS() += 2; + b->bt_insert(id.head, A, key, order, true, id); + A.GETOFS() += 2; + b->bt_insert(id.head, A, key, order, true, id); + A.GETOFS() += 2; + assert( b->k(0).isUsed() ); +// b->k(0).setUnused(); + b->k(1).setUnused(); + b->k(2).setUnused(); + b->k(3).setUnused(); + + b->dumpTree(id.head, order); + + /* b->bt_insert(id.head, B, key, order, false, id); + b->k(1).setUnused(); + + b->dumpTree(id.head, order); + cout << "---\n"; + + b->bt_insert(id.head, A, key, order, false, id); + + b->dumpTree(id.head, order); + cout << "---\n";*/ + + // this should assert. does it? (it might "accidentally" though, not asserting proves a problem, asserting proves nothing) + b->bt_insert(id.head, C, key, order, false, id); + + b->dumpTree(id.head, order); + } + + /* --- BtreeBuilder --- */ + + BtreeBuilder::BtreeBuilder(bool _dupsAllowed, IndexDetails& _idx) : + dupsAllowed(_dupsAllowed), idx(_idx), n(0) + { + first = cur = BtreeBucket::addBucket(idx); + b = cur.btreemod(); + order = idx.keyPattern(); + committed = false; + } + + void BtreeBuilder::newBucket() { + DiskLoc L = BtreeBucket::addBucket(idx); + b->tempNext() = L; + cur = L; + b = cur.btreemod(); + } + + void BtreeBuilder::addKey(BSONObj& key, DiskLoc loc) { + if( !dupsAllowed ) { + if( n > 0 ) { + int cmp = keyLast.woCompare(key, order); + massert( 10288 , "bad key order in BtreeBuilder - server internal error", cmp <= 0 ); + if( cmp == 0 ) { + //if( !dupsAllowed ) + uasserted( ASSERT_ID_DUPKEY , BtreeBucket::dupKeyError( idx , keyLast ) ); + } + } + keyLast = key; + } + + if ( ! b->_pushBack(loc, key, order, DiskLoc()) ){ + // no room + if ( key.objsize() > KeyMax ) { + problem() << "Btree::insert: key too large to index, skipping " << idx.indexNamespace().c_str() << ' ' << key.objsize() << ' ' << key.toString() << '\n'; + } + else { + // bucket was full + newBucket(); + b->pushBack(loc, key, order, DiskLoc()); + } + } + n++; + } + + void BtreeBuilder::buildNextLevel(DiskLoc loc) { + int levels = 1; + while( 1 ) { + if( loc.btree()->tempNext().isNull() ) { + // only 1 bucket at this level. we are done. + idx.head = loc; + break; + } + levels++; + + DiskLoc upLoc = BtreeBucket::addBucket(idx); + DiskLoc upStart = upLoc; + BtreeBucket *up = upLoc.btreemod(); + + DiskLoc xloc = loc; + while( !xloc.isNull() ) { + BtreeBucket *x = xloc.btreemod(); + BSONObj k; + DiskLoc r; + x->popBack(r,k); + if( x->n == 0 ) + log() << "warning: empty bucket on BtreeBuild " << k.toString() << endl; + + if ( ! up->_pushBack(r, k, order, xloc) ){ + // current bucket full + DiskLoc n = BtreeBucket::addBucket(idx); + up->tempNext() = n; + upLoc = n; + up = upLoc.btreemod(); + up->pushBack(r, k, order, xloc); + } + + xloc = x->tempNext(); /* get next in chain at current level */ + x->parent = upLoc; + } + + loc = upStart; + } + + if( levels > 1 ) + log(2) << "btree levels: " << levels << endl; + } + + /* when all addKeys are done, we then build the higher levels of the tree */ + void BtreeBuilder::commit() { + buildNextLevel(first); + committed = true; + } + + BtreeBuilder::~BtreeBuilder() { + if( !committed ) { + log(2) << "Rolling back partially built index space" << endl; + DiskLoc x = first; + while( !x.isNull() ) { + DiskLoc next = x.btree()->tempNext(); + btreeStore->deleteRecord(idx.indexNamespace().c_str(), x); + x = next; + } + assert( idx.head.isNull() ); + log(2) << "done rollback" << endl; + } + } + +} diff --git a/db/btree.h b/db/btree.h new file mode 100644 index 0000000..2c2ab81 --- /dev/null +++ b/db/btree.h @@ -0,0 +1,405 @@ +// btree.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" +#include "storage.h" +#include "pdfile.h" + +namespace mongo { + +#pragma pack(1) + + struct _KeyNode { + DiskLoc prevChildBucket; + DiskLoc recordLoc; + short keyDataOfs() const { + return (short) _kdo; + } + unsigned short _kdo; + void setKeyDataOfs(short s) { + _kdo = s; + assert(s>=0); + } + void setKeyDataOfsSavingUse(short s) { + _kdo = s; + assert(s>=0); + } + void setUsed() { + recordLoc.GETOFS() &= ~1; + } + void setUnused() { + /* Setting ofs to odd is the sentinel for unused, as real recordLoc's are always + even numbers. + Note we need to keep its value basically the same as we use the recordLoc + as part of the key in the index (to handle duplicate keys efficiently). + */ + recordLoc.GETOFS() |= 1; + } + int isUnused() { + return recordLoc.getOfs() & 1; + } + int isUsed() { + return !isUnused(); + } + }; + +#pragma pack() + + class BucketBasics; + + /* wrapper - this is our in memory representation of the key. _KeyNode is the disk representation. */ + class KeyNode { + public: + KeyNode(const BucketBasics& bb, const _KeyNode &k); + const DiskLoc& prevChildBucket; + const DiskLoc& recordLoc; + BSONObj key; + }; + +#pragma pack(1) + + /* this class is all about the storage management */ + class BucketBasics { + friend class BtreeBuilder; + friend class KeyNode; + public: + void dumpTree(DiskLoc thisLoc, const BSONObj &order); + bool isHead() { return parent.isNull(); } + void assertValid(const BSONObj &order, bool force = false); + int fullValidate(const DiskLoc& thisLoc, const BSONObj &order); /* traverses everything */ + protected: + void modified(const DiskLoc& thisLoc); + KeyNode keyNode(int i) const { + assert( i < n ); + return KeyNode(*this, k(i)); + } + + char * dataAt(short ofs) { + return data + ofs; + } + + void init(); // initialize a new node + + /* returns false if node is full and must be split + keypos is where to insert -- inserted after that key #. so keypos=0 is the leftmost one. + */ + bool basicInsert(const DiskLoc& thisLoc, int keypos, const DiskLoc& recordLoc, const BSONObj& key, const BSONObj &order); + + /** + * @return true if works, false if not enough space + */ + bool _pushBack(const DiskLoc& recordLoc, BSONObj& key, const BSONObj &order, DiskLoc prevChild); + void pushBack(const DiskLoc& recordLoc, BSONObj& key, const BSONObj &order, DiskLoc prevChild){ + bool ok = _pushBack( recordLoc , key , order , prevChild ); + assert(ok); + } + void popBack(DiskLoc& recLoc, BSONObj& key); + void _delKeyAtPos(int keypos); // low level version that doesn't deal with child ptrs. + + /* !Packed means there is deleted fragment space within the bucket. + We "repack" when we run out of space before considering the node + to be full. + */ + enum Flags { Packed=1 }; + + DiskLoc& childForPos(int p) { + return p == n ? nextChild : k(p).prevChildBucket; + } + + int totalDataSize() const; + void pack( const BSONObj &order ); + void setNotPacked(); + void setPacked(); + int _alloc(int bytes); + void _unalloc(int bytes); + void truncateTo(int N, const BSONObj &order); + void markUnused(int keypos); + + /* BtreeBuilder uses the parent var as a temp place to maintain a linked list chain. + we use tempNext() when we do that to be less confusing. (one might have written a union in C) + */ + DiskLoc& tempNext() { return parent; } + + public: + DiskLoc parent; + + string bucketSummary() const { + stringstream ss; + ss << " Bucket info:" << endl; + ss << " n: " << n << endl; + ss << " parent: " << parent.toString() << endl; + ss << " nextChild: " << parent.toString() << endl; + ss << " Size: " << _Size << " flags:" << flags << endl; + ss << " emptySize: " << emptySize << " topSize: " << topSize << endl; + return ss.str(); + } + + protected: + void _shape(int level, stringstream&); + DiskLoc nextChild; // child bucket off and to the right of the highest key. + int _Size; // total size of this btree node in bytes. constant. + int Size() const; + int flags; + int emptySize; // size of the empty region + int topSize; // size of the data at the top of the bucket (keys are at the beginning or 'bottom') + int n; // # of keys so far. + int reserved; + const _KeyNode& k(int i) const { + return ((_KeyNode*)data)[i]; + } + _KeyNode& k(int i) { + return ((_KeyNode*)data)[i]; + } + char data[4]; + }; + + class BtreeBucket : public BucketBasics { + friend class BtreeCursor; + public: + void dump(); + + /* @return true if key exists in index + + order - indicates order of keys in the index. this is basically the index's key pattern, e.g.: + BSONObj order = ((IndexDetails&)idx).keyPattern(); + likewise below in bt_insert() etc. + */ + bool exists(const IndexDetails& idx, DiskLoc thisLoc, const BSONObj& key, BSONObj order); + + static DiskLoc addBucket(IndexDetails&); /* start a new index off, empty */ + + static void renameIndexNamespace(const char *oldNs, const char *newNs); + + int bt_insert(DiskLoc thisLoc, DiskLoc recordLoc, + const BSONObj& key, const BSONObj &order, bool dupsAllowed, + IndexDetails& idx, bool toplevel = true); + + bool unindex(const DiskLoc& thisLoc, IndexDetails& id, BSONObj& key, const DiskLoc& recordLoc); + + /* locate may return an "unused" key that is just a marker. so be careful. + looks for a key:recordloc pair. + + found - returns true if exact match found. note you can get back a position + result even if found is false. + */ + DiskLoc locate(const IndexDetails& , const DiskLoc& thisLoc, const BSONObj& key, const BSONObj &order, + int& pos, bool& found, DiskLoc recordLoc, int direction=1); + + /** + * find the first instance of the key + * does not handle dups + * returned DiskLock isNull if can't find anything with that + */ + DiskLoc findSingle( const IndexDetails& , const DiskLoc& thisLoc, const BSONObj& key ); + + /* advance one key position in the index: */ + DiskLoc advance(const DiskLoc& thisLoc, int& keyOfs, int direction, const char *caller); + DiskLoc getHead(const DiskLoc& thisLoc); + + /* get tree shape */ + void shape(stringstream&); + + static void a_test(IndexDetails&); + + private: + void fixParentPtrs(const DiskLoc& thisLoc); + void delBucket(const DiskLoc& thisLoc, IndexDetails&); + void delKeyAtPos(const DiskLoc& thisLoc, IndexDetails& id, int p); + BSONObj keyAt(int keyOfs) { + return keyOfs >= n ? BSONObj() : keyNode(keyOfs).key; + } + static BtreeBucket* allocTemp(); /* caller must release with free() */ + void insertHere(DiskLoc thisLoc, int keypos, + DiskLoc recordLoc, const BSONObj& key, const BSONObj &order, + DiskLoc lchild, DiskLoc rchild, IndexDetails&); + int _insert(DiskLoc thisLoc, DiskLoc recordLoc, + const BSONObj& key, const BSONObj &order, bool dupsAllowed, + DiskLoc lChild, DiskLoc rChild, IndexDetails&); + bool find(const IndexDetails& idx, const BSONObj& key, DiskLoc recordLoc, const BSONObj &order, int& pos, bool assertIfDup); + static void findLargestKey(const DiskLoc& thisLoc, DiskLoc& largestLoc, int& largestKey); + public: + // simply builds and returns a dup key error message string + static string dupKeyError( const IndexDetails& idx , const BSONObj& key ); + }; + + class BtreeCursor : public Cursor { + public: + BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails&, const BSONObj &startKey, const BSONObj &endKey, bool endKeyInclusive, int direction ); + + BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails& _id, const BoundList &_bounds, int _direction ); + + virtual bool ok() { + return !bucket.isNull(); + } + bool eof() { + return !ok(); + } + virtual bool advance(); + + virtual void noteLocation(); // updates keyAtKeyOfs... + virtual void checkLocation(); + + /* used for multikey index traversal to avoid sending back dups. see Matcher::matches(). + if a multikey index traversal: + if loc has already been sent, returns true. + otherwise, marks loc as sent. + @return true if the loc has not been seen + */ + set<DiskLoc> dups; + virtual bool getsetdup(DiskLoc loc) { + if( multikey ) { + pair<set<DiskLoc>::iterator, bool> p = dups.insert(loc); + return !p.second; + } + return false; + } + + _KeyNode& _currKeyNode() { + assert( !bucket.isNull() ); + _KeyNode& kn = bucket.btree()->k(keyOfs); + assert( kn.isUsed() ); + return kn; + } + KeyNode currKeyNode() const { + assert( !bucket.isNull() ); + return bucket.btree()->keyNode(keyOfs); + } + virtual BSONObj currKey() const { + return currKeyNode().key; + } + + virtual BSONObj indexKeyPattern() { + return indexDetails.keyPattern(); + } + + virtual void aboutToDeleteBucket(const DiskLoc& b) { + if ( bucket == b ) + keyOfs = -1; + } + + virtual DiskLoc currLoc() { + return !bucket.isNull() ? _currKeyNode().recordLoc : DiskLoc(); + } + virtual DiskLoc refLoc() { + return currLoc(); + } + virtual Record* _current() { + return currLoc().rec(); + } + virtual BSONObj current() { + return BSONObj(_current()); + } + virtual string toString() { + string s = string("BtreeCursor ") + indexDetails.indexName(); + if ( direction < 0 ) s += " reverse"; + if ( bounds_.size() > 1 ) s += " multi"; + return s; + } + + BSONObj prettyKey( const BSONObj &key ) const { + return key.replaceFieldNames( indexDetails.keyPattern() ).clientReadable(); + } + + virtual BSONObj prettyStartKey() const { + return prettyKey( startKey ); + } + virtual BSONObj prettyEndKey() const { + return prettyKey( endKey ); + } + + void forgetEndKey() { endKey = BSONObj(); } + + private: + /* Our btrees may (rarely) have "unused" keys when items are deleted. + Skip past them. + */ + void skipUnusedKeys(); + + /* Check if the current key is beyond endKey. */ + void checkEnd(); + + // selective audits on construction + void audit(); + + // set initial bucket + void init(); + + // init start / end keys with a new range + void initInterval(); + + friend class BtreeBucket; + NamespaceDetails *d; + int idxNo; + BSONObj startKey; + BSONObj endKey; + bool endKeyInclusive_; + bool multikey; // note this must be updated every getmore batch in case someone added a multikey... + + const IndexDetails& indexDetails; + BSONObj order; + DiskLoc bucket; + int keyOfs; + int direction; // 1=fwd,-1=reverse + BSONObj keyAtKeyOfs; // so we can tell if things moved around on us between the query and the getMore call + DiskLoc locAtKeyOfs; + BoundList bounds_; + unsigned boundIndex_; + }; + +#pragma pack() + + inline bool IndexDetails::hasKey(const BSONObj& key) { + return head.btree()->exists(*this, head, key, keyPattern()); + } + + /* build btree from the bottom up */ + /* _ TODO dropDups */ + class BtreeBuilder { + bool dupsAllowed; + IndexDetails& idx; + unsigned long long n; + BSONObj keyLast; + BSONObj order; + bool committed; + + DiskLoc cur, first; + BtreeBucket *b; + + void newBucket(); + void buildNextLevel(DiskLoc); + + public: + ~BtreeBuilder(); + + BtreeBuilder(bool _dupsAllowed, IndexDetails& _idx); + + /* keys must be added in order */ + void addKey(BSONObj& key, DiskLoc loc); + + /* commit work. if not called, destructor will clean up partially completed work + (in case exception has happened). + */ + void commit(); + + unsigned long long getn() { return n; } + }; + +} // namespace mongo; diff --git a/db/btreecursor.cpp b/db/btreecursor.cpp new file mode 100644 index 0000000..bb477d6 --- /dev/null +++ b/db/btreecursor.cpp @@ -0,0 +1,204 @@ +// btreecursor.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "btree.h" +#include "pdfile.h" +#include "jsobj.h" +#include "curop.h" + +namespace mongo { + + extern int otherTraceLevel; + + BtreeCursor::BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails &_id, + const BSONObj &_startKey, const BSONObj &_endKey, bool endKeyInclusive, int _direction ) : + d(_d), idxNo(_idxNo), + startKey( _startKey ), + endKey( _endKey ), + endKeyInclusive_( endKeyInclusive ), + multikey( d->isMultikey( idxNo ) ), + indexDetails( _id ), + order( _id.keyPattern() ), + direction( _direction ), + boundIndex_() + { + audit(); + init(); + } + + BtreeCursor::BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails& _id, const vector< pair< BSONObj, BSONObj > > &_bounds, int _direction ) + : + d(_d), idxNo(_idxNo), + endKeyInclusive_( true ), + multikey( d->isMultikey( idxNo ) ), + indexDetails( _id ), + order( _id.keyPattern() ), + direction( _direction ), + bounds_( _bounds ), + boundIndex_() + { + assert( !bounds_.empty() ); + audit(); + initInterval(); + } + + void BtreeCursor::audit() { + dassert( d->idxNo((IndexDetails&) indexDetails) == idxNo ); + + if ( otherTraceLevel >= 12 ) { + if ( otherTraceLevel >= 200 ) { + out() << "::BtreeCursor() qtl>200. validating entire index." << endl; + indexDetails.head.btree()->fullValidate(indexDetails.head, order); + } + else { + out() << "BTreeCursor(). dumping head bucket" << endl; + indexDetails.head.btree()->dump(); + } + } + } + + void BtreeCursor::init() { + bool found; + bucket = indexDetails.head.btree()-> + locate(indexDetails, indexDetails.head, startKey, order, keyOfs, found, direction > 0 ? minDiskLoc : maxDiskLoc, direction); + skipUnusedKeys(); + checkEnd(); + } + + void BtreeCursor::initInterval() { + do { + startKey = bounds_[ boundIndex_ ].first; + endKey = bounds_[ boundIndex_ ].second; + init(); + } while ( !ok() && ++boundIndex_ < bounds_.size() ); + } + + /* skip unused keys. */ + void BtreeCursor::skipUnusedKeys() { + int u = 0; + while ( 1 ) { + if ( !ok() ) + break; + BtreeBucket *b = bucket.btree(); + _KeyNode& kn = b->k(keyOfs); + if ( kn.isUsed() ) + break; + bucket = b->advance(bucket, keyOfs, direction, "skipUnusedKeys"); + u++; + } + if ( u > 10 ) + OCCASIONALLY log() << "btree unused skipped:" << u << '\n'; + } + +// Return a value in the set {-1, 0, 1} to represent the sign of parameter i. + int sgn( int i ) { + if ( i == 0 ) + return 0; + return i > 0 ? 1 : -1; + } + + // Check if the current key is beyond endKey. + void BtreeCursor::checkEnd() { + if ( bucket.isNull() ) + return; + if ( !endKey.isEmpty() ) { + int cmp = sgn( endKey.woCompare( currKey(), order ) ); + if ( ( cmp != 0 && cmp != direction ) || + ( cmp == 0 && !endKeyInclusive_ ) ) + bucket = DiskLoc(); + } + } + + bool BtreeCursor::advance() { + killCurrentOp.checkForInterrupt(); + if ( bucket.isNull() ) + return false; + bucket = bucket.btree()->advance(bucket, keyOfs, direction, "BtreeCursor::advance"); + skipUnusedKeys(); + checkEnd(); + if( !ok() && ++boundIndex_ < bounds_.size() ) + initInterval(); + return !bucket.isNull(); + } + + void BtreeCursor::noteLocation() { + if ( !eof() ) { + BSONObj o = bucket.btree()->keyAt(keyOfs).copy(); + keyAtKeyOfs = o; + locAtKeyOfs = bucket.btree()->k(keyOfs).recordLoc; + } + } + + /* Since the last noteLocation(), our key may have moved around, and that old cached + information may thus be stale and wrong (although often it is right). We check + that here; if we have moved, we have to search back for where we were at. + + i.e., after operations on the index, the BtreeCursor's cached location info may + be invalid. This function ensures validity, so you should call it before using + the cursor if other writers have used the database since the last noteLocation + call. + */ + void BtreeCursor::checkLocation() { + if ( eof() ) + return; + + multikey = d->isMultikey(idxNo); + + if ( keyOfs >= 0 ) { + BtreeBucket *b = bucket.btree(); + + assert( !keyAtKeyOfs.isEmpty() ); + + // Note keyAt() returns an empty BSONObj if keyOfs is now out of range, + // which is possible as keys may have been deleted. + if ( b->keyAt(keyOfs).woEqual(keyAtKeyOfs) && + b->k(keyOfs).recordLoc == locAtKeyOfs ) { + if ( !b->k(keyOfs).isUsed() ) { + /* we were deleted but still exist as an unused + marker key. advance. + */ + skipUnusedKeys(); + } + return; + } + } + + /* normally we don't get to here. when we do, old position is no longer + valid and we must refind where we left off (which is expensive) + */ + + bool found; + + /* TODO: Switch to keep indexdetails and do idx.head! */ + bucket = indexDetails.head.btree()->locate(indexDetails, indexDetails.head, keyAtKeyOfs, order, keyOfs, found, locAtKeyOfs, direction); + RARELY log() << " key seems to have moved in the index, refinding. found:" << found << endl; + if ( ! bucket.isNull() ) + skipUnusedKeys(); + + } + + /* ----------------------------------------------------------------------------- */ + + struct BtreeCursorUnitTest { + BtreeCursorUnitTest() { + assert( minDiskLoc.compare(maxDiskLoc) < 0 ); + } + } btut; + +} // namespace mongo diff --git a/db/client.cpp b/db/client.cpp new file mode 100644 index 0000000..68a0c9e --- /dev/null +++ b/db/client.cpp @@ -0,0 +1,99 @@ +// client.cpp
+
+/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Client represents a connection to the database (the server-side) and corresponds + to an open socket (or logical connection if pooling on sockets) from a client. +*/ + +#include "stdafx.h" +#include "db.h" +#include "client.h" +#include "curop.h" +#include "json.h" + +namespace mongo { + + boost::mutex Client::clientsMutex; + set<Client*> Client::clients; // always be in clientsMutex when manipulating this + boost::thread_specific_ptr<Client> currentClient; + + Client::Client(const char *desc) : + _curOp(new CurOp()), + _database(0), _ns("")/*, _nsstr("")*/ + ,_shutdown(false), + _desc(desc), + _god(0) + { + ai = new AuthenticationInfo(); + boostlock bl(clientsMutex); + clients.insert(this); + } + + Client::~Client() { + delete _curOp; + delete ai; + ai = 0; + _god = 0; + if ( !_shutdown ) { + cout << "ERROR: Client::shutdown not called!" << endl; + } + } + + bool Client::shutdown(){ + _shutdown = true; + + { + boostlock bl(clientsMutex); + clients.erase(this); + } + + bool didAnything = false; + + if ( _tempCollections.size() ){ + didAnything = true; + for ( list<string>::iterator i = _tempCollections.begin(); i!=_tempCollections.end(); i++ ){ + string ns = *i; + dblock l; + setClient( ns.c_str() ); + if ( ! nsdetails( ns.c_str() ) ) + continue; + try { + string err; + BSONObjBuilder b; + dropCollection( ns , err , b ); + } + catch ( ... ){ + log() << "error dropping temp collection: " << ns << endl; + } + } + _tempCollections.clear(); + } + + return didAnything; + } + + BSONObj CurOp::_tooBig = fromjson("{\"$msg\":\"query not recording (too large)\"}"); + WrappingInt CurOp::_nextOpNum; + + Client::Context::Context( string ns , Database * db ) + : _client( currentClient.get() ) { + assert( db && db->isOk() ); + _client->setns( ns.c_str() , db ); + } + +} diff --git a/db/client.h b/db/client.h new file mode 100644 index 0000000..99092ca --- /dev/null +++ b/db/client.h @@ -0,0 +1,193 @@ +// client.h
+
+/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Client represents a connection to the database (the server-side) and corresponds + to an open socket (or logical connection if pooling on sockets) from a client. + + todo: switch to asio...this will fit nicely with that. +*/ + +#pragma once + +#include "../stdafx.h" +#include "namespace.h" +#include "lasterror.h" +#include "../util/top.h" + +namespace mongo { + + class AuthenticationInfo; + class Database; + class CurOp; + class Command; + class Client; + + extern boost::thread_specific_ptr<Client> currentClient; + + bool setClient(const char *ns, const string& path=dbpath, mongolock *lock = 0); + + + class Client : boost::noncopyable { + public: + static boost::mutex clientsMutex; + static set<Client*> clients; // always be in clientsMutex when manipulating this + + class GodScope { + bool _prev; + public: + GodScope(); + ~GodScope(); + }; + + /* Set database we want to use, then, restores when we finish (are out of scope) + Note this is also helpful if an exception happens as the state if fixed up. + */ + class Context { + Client * _client; + Database * _olddb; + string _oldns; + public: + Context(const char *ns) + : _client( currentClient.get() ) { + _olddb = _client->_database; + _oldns = _client->_ns; + setClient(ns); + } + Context(string ns) + : _client( currentClient.get() ){ + _olddb = _client->_database; + _oldns = _client->_ns; + setClient(ns.c_str()); + } + + /* this version saves the context but doesn't yet set the new one: */ + Context() + : _client( currentClient.get() ) { + _olddb = _client->database(); + _oldns = _client->ns(); + + } + + /** + * if you are doing this after allowing a write there could be a race condition + * if someone closes that db. this checks that the DB is still valid + */ + Context( string ns , Database * db ); + + ~Context() { + DEV assert( _client == currentClient.get() ); + _client->setns( _oldns.c_str(), _olddb ); + } + + }; + + private: + CurOp * const _curOp; + Database *_database; + Namespace _ns; + //NamespaceString _nsstr; + bool _shutdown; + list<string> _tempCollections; + const char *_desc; + bool _god; + public: + AuthenticationInfo *ai; + Top top; + + CurOp* curop() { return _curOp; } + Database* database() { + return _database; + } + const char *ns() { return _ns.buf; } + + void setns(const char *ns, Database *db) { + _database = db; + _ns = ns; + //_nsstr = ns; + } + void clearns() { setns("", 0); } + + Client(const char *desc); + ~Client(); + + const char *desc() const { return _desc; } + + void addTempCollection( const string& ns ){ + _tempCollections.push_back( ns ); + } + + /* each thread which does db operations has a Client object in TLS. + call this when your thread starts. + */ + static void initThread(const char *desc); + + /* + this has to be called as the client goes away, but before thread termination + @return true if anything was done + */ + bool shutdown(); + + bool isGod() const { return _god; } + }; + + inline Client& cc() { + return *currentClient.get(); + } + + /* each thread which does db operations has a Client object in TLS. + call this when your thread starts. + */ + inline void Client::initThread(const char *desc) { + assert( currentClient.get() == 0 ); + currentClient.reset( new Client(desc) ); + } + + inline Client::GodScope::GodScope(){ + _prev = cc()._god; + cc()._god = true; + } + + inline Client::GodScope::~GodScope(){ + cc()._god = _prev; + } + + /* this unlocks, does NOT upgrade. that works for our current usage */ + inline void mongolock::releaseAndWriteLock() { + if( !_writelock ) { + +#if BOOST_VERSION >= 103500 + int s = dbMutex.getState(); + if( s != -1 ) { + log() << "error: releaseAndWriteLock() s == " << s << endl; + msgasserted( 12600, "releaseAndWriteLock: unlock_shared failed, probably recursive" ); + } +#endif + + _writelock = true; + dbMutex.unlock_shared(); + dbMutex.lock(); + + /* this is defensive; as we were unlocked for a moment above, + the Database object we reference could have been deleted: + */ + cc().clearns(); + } + } + +}; + diff --git a/db/clientcursor.cpp b/db/clientcursor.cpp new file mode 100644 index 0000000..0de0b2e --- /dev/null +++ b/db/clientcursor.cpp @@ -0,0 +1,278 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* clientcursor.cpp + + ClientCursor is a wrapper that represents a cursorid from our database + application's perspective. + + Cursor -- and its derived classes -- are our internal cursors. +*/ + +#include "stdafx.h" +#include "query.h" +#include "introspect.h" +#include <time.h> +#include "db.h" +#include "commands.h" + +namespace mongo { + + CCById ClientCursor::clientCursorsById; + CCByLoc ClientCursor::byLoc; + boost::recursive_mutex ClientCursor::ccmutex; + + unsigned ClientCursor::byLocSize() { + recursive_boostlock lock(ccmutex); + return byLoc.size(); + } + + void ClientCursor::setLastLoc_inlock(DiskLoc L) { + if ( L == _lastLoc ) + return; + + if ( !_lastLoc.isNull() ) { + CCByLoc::iterator i = kv_find(byLoc, _lastLoc, this); + if ( i != byLoc.end() ) + byLoc.erase(i); + } + + if ( !L.isNull() ) + byLoc.insert( make_pair(L, this) ); + _lastLoc = L; + } + + /* ------------------------------------------- */ + + /* must call this when a btree node is updated */ + //void removedKey(const DiskLoc& btreeLoc, int keyPos) { + //} + + /* todo: this implementation is incomplete. we use it as a prefix for dropDatabase, which + works fine as the prefix will end with '.'. however, when used with drop and + deleteIndexes, this could take out cursors that belong to something else -- if you + drop "foo", currently, this will kill cursors for "foobar". + */ + void ClientCursor::invalidate(const char *nsPrefix) { + vector<ClientCursor*> toDelete; + + int len = strlen(nsPrefix); + assert( len > 0 && strchr(nsPrefix, '.') ); + + { + recursive_boostlock lock(ccmutex); + + for ( CCByLoc::iterator i = byLoc.begin(); i != byLoc.end(); ++i ) { + ClientCursor *cc = i->second; + if ( strncmp(nsPrefix, cc->ns.c_str(), len) == 0 ) + toDelete.push_back(i->second); + } + + for ( vector<ClientCursor*>::iterator i = toDelete.begin(); i != toDelete.end(); ++i ) + delete (*i); + } + } + + /* called every 4 seconds. millis is amount of idle time passed since the last call -- could be zero */ + void ClientCursor::idleTimeReport(unsigned millis) { + recursive_boostlock lock(ccmutex); + for ( CCByLoc::iterator i = byLoc.begin(); i != byLoc.end(); ) { + CCByLoc::iterator j = i; + i++; + if( j->second->shouldTimeout( millis ) ){ + log(1) << "killing old cursor " << j->second->cursorid << ' ' << j->second->ns + << " idle:" << j->second->idleTime() << "ms\n"; + delete j->second; + } + } + } + + /* must call when a btree bucket going away. + note this is potentially slow + */ + void ClientCursor::informAboutToDeleteBucket(const DiskLoc& b) { + recursive_boostlock lock(ccmutex); + RARELY if ( byLoc.size() > 70 ) { + log() << "perf warning: byLoc.size=" << byLoc.size() << " in aboutToDeleteBucket\n"; + } + for ( CCByLoc::iterator i = byLoc.begin(); i != byLoc.end(); i++ ) + i->second->c->aboutToDeleteBucket(b); + } + void aboutToDeleteBucket(const DiskLoc& b) { + ClientCursor::informAboutToDeleteBucket(b); + } + + /* must call this on a delete so we clean up the cursors. */ + void ClientCursor::aboutToDelete(const DiskLoc& dl) { + recursive_boostlock lock(ccmutex); + + CCByLoc::iterator j = byLoc.lower_bound(dl); + CCByLoc::iterator stop = byLoc.upper_bound(dl); + if ( j == stop ) + return; + + vector<ClientCursor*> toAdvance; + + while ( 1 ) { + toAdvance.push_back(j->second); + WIN assert( j->first == dl ); + ++j; + if ( j == stop ) + break; + } + + wassert( toAdvance.size() < 5000 ); + + for ( vector<ClientCursor*>::iterator i = toAdvance.begin(); i != toAdvance.end(); ++i ){ + ClientCursor* cc = *i; + + if ( cc->_doingDeletes ) continue; + + Cursor *c = cc->c.get(); + if ( c->capped() ){ + delete cc; + continue; + } + + c->checkLocation(); + DiskLoc tmp1 = c->refLoc(); + if ( tmp1 != dl ) { + /* this might indicate a failure to call ClientCursor::updateLocation() */ + problem() << "warning: cursor loc " << tmp1 << " does not match byLoc position " << dl << " !" << endl; + } + c->advance(); + if ( c->eof() ) { + // advanced to end -- delete cursor + delete cc; + } + else { + wassert( c->refLoc() != dl ); + cc->updateLocation(); + } + } + } + void aboutToDelete(const DiskLoc& dl) { ClientCursor::aboutToDelete(dl); } + + ClientCursor::~ClientCursor() { + assert( pos != -2 ); + + { + recursive_boostlock lock(ccmutex); + setLastLoc_inlock( DiskLoc() ); // removes us from bylocation multimap + clientCursorsById.erase(cursorid); + + // defensive: + (CursorId&) cursorid = -1; + pos = -2; + } + } + + /* call when cursor's location changes so that we can update the + cursorsbylocation map. if you are locked and internally iterating, only + need to call when you are ready to "unlock". + */ + void ClientCursor::updateLocation() { + assert( cursorid ); + _idleAgeMillis = 0; + DiskLoc cl = c->refLoc(); + if ( lastLoc() == cl ) { + //log() << "info: lastloc==curloc " << ns << '\n'; + return; + } + { + recursive_boostlock lock(ccmutex); + setLastLoc_inlock(cl); + c->noteLocation(); + } + } + + bool ClientCursor::yield() { + // need to store on the stack in case this gets deleted + CursorId id = cursorid; + + bool doingDeletes = _doingDeletes; + _doingDeletes = false; + + updateLocation(); + + { + /* a quick test that our temprelease is safe. + todo: make a YieldingCursor class + and then make the following code part of a unit test. + */ + const int test = 0; + static bool inEmpty = false; + if( test && !inEmpty ) { + inEmpty = true; + log() << "TEST: manipulate collection during remove" << endl; + if( test == 1 ) + Helpers::emptyCollection(ns.c_str()); + else if( test == 2 ) { + BSONObjBuilder b; string m; + dropCollection(ns.c_str(), m, b); + } + else { + dropDatabase(ns.c_str()); + } + } + } + + { + dbtempreleasecond unlock; + } + + if ( ClientCursor::find( id , false ) == 0 ){ + // i was deleted + return false; + } + + _doingDeletes = doingDeletes; + return true; + } + + int ctmLast = 0; // so we don't have to do find() which is a little slow very often. + long long ClientCursor::allocCursorId_inlock() { + long long x; + int ctm = (int) curTimeMillis(); + while ( 1 ) { + x = (((long long)rand()) << 32); + x = x | ctm | 0x80000000; // OR to make sure not zero + if ( ctm != ctmLast || ClientCursor::find_inlock(x, false) == 0 ) + break; + } + ctmLast = ctm; + DEV out() << " alloccursorid " << x << endl; + return x; + } + + // QUESTION: Restrict to the namespace from which this command was issued? + // Alternatively, make this command admin-only? + class CmdCursorInfo : public Command { + public: + CmdCursorInfo() : Command( "cursorInfo" ) {} + virtual bool slaveOk() { return true; } + virtual void help( stringstream& help ) const { + help << " example: { cursorInfo : 1 }"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + recursive_boostlock lock(ClientCursor::ccmutex); + result.append("byLocation_size", unsigned( ClientCursor::byLoc.size() ) ); + result.append("clientCursors_size", unsigned( ClientCursor::clientCursorsById.size() ) ); + return true; + } + } cmdCursorInfo; + +} // namespace mongo diff --git a/db/clientcursor.h b/db/clientcursor.h new file mode 100644 index 0000000..03f20e9 --- /dev/null +++ b/db/clientcursor.h @@ -0,0 +1,216 @@ +/* clientcursor.h */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Cursor -- and its derived classes -- are our internal cursors. + + ClientCursor is a wrapper that represents a cursorid from our database + application's perspective. +*/ + +#pragma once + +#include "../stdafx.h" +#include "cursor.h" +#include "jsobj.h" +#include "../util/message.h" +#include "storage.h" +#include "dbhelpers.h" +#include "matcher.h" + +namespace mongo { + + typedef long long CursorId; /* passed to the client so it can send back on getMore */ + class Cursor; /* internal server cursor base class */ + class ClientCursor; + + /* todo: make this map be per connection. this will prevent cursor hijacking security attacks perhaps. + */ + typedef map<CursorId, ClientCursor*> CCById; + + typedef multimap<DiskLoc, ClientCursor*> CCByLoc; + + extern BSONObj id_obj; + + class ClientCursor { + friend class CmdCursorInfo; + DiskLoc _lastLoc; // use getter and setter not this (important) + unsigned _idleAgeMillis; // how long has the cursor been around, relative to server idle time + + /* 0 = normal + 1 = no timeout allowed + 100 = in use (pinned) -- see Pointer class + */ + unsigned _pinValue; + + bool _doingDeletes; + + static CCById clientCursorsById; + static CCByLoc byLoc; + static boost::recursive_mutex ccmutex; // must use this for all statics above! + + static CursorId allocCursorId_inlock(); + + public: + /* use this to assure we don't in the background time out cursor while it is under use. + if you are using noTimeout() already, there is no risk anyway. + Further, this mechanism guards against two getMore requests on the same cursor executing + at the same time - which might be bad. That should never happen, but if a client driver + had a bug, it could (or perhaps some sort of attack situation). + */ + class Pointer : boost::noncopyable { + public: + ClientCursor *_c; + void release() { + if( _c ) { + assert( _c->_pinValue >= 100 ); + _c->_pinValue -= 100; + } + _c = 0; + } + Pointer(long long cursorid) { + recursive_boostlock lock(ccmutex); + _c = ClientCursor::find_inlock(cursorid, true); + if( _c ) { + if( _c->_pinValue >= 100 ) { + _c = 0; + uassert(12051, "clientcursor already in use? driver problem?", false); + } + _c->_pinValue += 100; + } + } + ~Pointer() { + release(); + } + }; + + /*const*/ CursorId cursorid; + string ns; + auto_ptr<CoveredIndexMatcher> matcher; + auto_ptr<Cursor> c; + int pos; // # objects into the cursor so far + BSONObj query; + + ClientCursor() : _idleAgeMillis(0), _pinValue(0), _doingDeletes(false), pos(0) { + recursive_boostlock lock(ccmutex); + cursorid = allocCursorId_inlock(); + clientCursorsById.insert( make_pair(cursorid, this) ); + } + ~ClientCursor(); + + DiskLoc lastLoc() const { + return _lastLoc; + } + + auto_ptr< FieldMatcher > filter; // which fields query wants returned + Message originalMessage; // this is effectively an auto ptr for data the matcher points to + + /* Get rid of cursors for namespaces that begin with nsprefix. + Used by drop, deleteIndexes, dropDatabase. + */ + static void invalidate(const char *nsPrefix); + + /** + * do a dbtemprelease + * note: caller should check matcher.docMatcher().atomic() first and not yield if atomic - + * we don't do herein as this->matcher (above) is only initialized for true queries/getmore. + * (ie not set for remote/update) + * @return if the cursor is still valid. + * if false is returned, then this ClientCursor should be considered deleted + */ + bool yield(); + private: + void setLastLoc_inlock(DiskLoc); + + static ClientCursor* find_inlock(CursorId id, bool warn = true) { + CCById::iterator it = clientCursorsById.find(id); + if ( it == clientCursorsById.end() ) { + if ( warn ) + OCCASIONALLY out() << "ClientCursor::find(): cursor not found in map " << id << " (ok after a drop)\n"; + return 0; + } + return it->second; + } + public: + static ClientCursor* find(CursorId id, bool warn = true) { + recursive_boostlock lock(ccmutex); + ClientCursor *c = find_inlock(id, warn); + // if this asserts, your code was not thread safe - you either need to set no timeout + // for the cursor or keep a ClientCursor::Pointer in scope for it. + massert( 12521, "internal error: use of an unlocked ClientCursor", c->_pinValue ); + return c; + } + + static bool erase(CursorId id) { + recursive_boostlock lock(ccmutex); + ClientCursor *cc = find_inlock(id); + if ( cc ) { + assert( cc->_pinValue < 100 ); // you can't still have an active ClientCursor::Pointer + delete cc; + return true; + } + return false; + } + + /* call when cursor's location changes so that we can update the + cursorsbylocation map. if you are locked and internally iterating, only + need to call when you are ready to "unlock". + */ + void updateLocation(); + + void cleanupByLocation(DiskLoc loc); + + void mayUpgradeStorage() { + /* if ( !ids_.get() ) + return; + stringstream ss; + ss << ns << "." << cursorid; + ids_->mayUpgradeStorage( ss.str() );*/ + } + + /** + * @param millis amount of idle passed time since last call + */ + bool shouldTimeout( unsigned millis ){ + _idleAgeMillis += millis; + return _idleAgeMillis > 600000 && _pinValue == 0; + } + + unsigned idleTime(){ + return _idleAgeMillis; + } + + static void idleTimeReport(unsigned millis); + + // cursors normally timeout after an inactivy period to prevent excess memory use + // setting this prevents timeout of the cursor in question. + void noTimeout() { + _pinValue++; + } + + void setDoingDeletes( bool doingDeletes ){ + _doingDeletes = doingDeletes; + } + + static unsigned byLocSize(); // just for diagnostics + + static void informAboutToDeleteBucket(const DiskLoc& b); + static void aboutToDelete(const DiskLoc& dl); + }; + + +} // namespace mongo diff --git a/db/cloner.cpp b/db/cloner.cpp new file mode 100644 index 0000000..862f37c --- /dev/null +++ b/db/cloner.cpp @@ -0,0 +1,724 @@ +// cloner.cpp - copy a database (export/import basically) + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "pdfile.h" +#include "../client/dbclient.h" +#include "../util/builder.h" +#include "jsobj.h" +#include "query.h" +#include "commands.h" +#include "db.h" +#include "instance.h" +#include "repl.h" + +namespace mongo { + + void ensureHaveIdIndex(const char *ns); + + bool replAuthenticate(DBClientConnection *); + + class Cloner: boost::noncopyable { + auto_ptr< DBClientWithCommands > conn; + void copy(const char *from_ns, const char *to_ns, bool isindex, bool logForRepl, + bool masterSameProcess, bool slaveOk, Query q = Query()); + void replayOpLog( DBClientCursor *c, const BSONObj &query ); + public: + Cloner() { } + + /* slaveOk - if true it is ok if the source of the data is !ismaster. + useReplAuth - use the credentials we normally use as a replication slave for the cloning + snapshot - use $snapshot mode for copying collections. note this should not be used when it isn't required, as it will be slower. + for example repairDatabase need not use it. + */ + bool go(const char *masterHost, string& errmsg, const string& fromdb, bool logForRepl, bool slaveOk, bool useReplAuth, bool snapshot); + bool startCloneCollection( const char *fromhost, const char *ns, const BSONObj &query, string& errmsg, bool logForRepl, bool copyIndexes, int logSizeMb, long long &cursorId ); + bool finishCloneCollection( const char *fromhost, const char *ns, const BSONObj &query, long long cursorId, string &errmsg ); + }; + + /* for index info object: + { "name" : "name_1" , "ns" : "foo.index3" , "key" : { "name" : 1.0 } } + we need to fix up the value in the "ns" parameter so that the name prefix is correct on a + copy to a new name. + */ + BSONObj fixindex(BSONObj o) { + BSONObjBuilder b; + BSONObjIterator i(o); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( string("ns") == e.fieldName() ) { + uassert( 10024 , "bad ns field for index during dbcopy", e.type() == String); + const char *p = strchr(e.valuestr(), '.'); + uassert( 10025 , "bad ns field for index during dbcopy [2]", p); + string newname = cc().database()->name + p; + b.append("ns", newname); + } + else + b.append(e); + } + BSONObj res= b.obj(); + + /* if( mod ) { + out() << "before: " << o.toString() << endl; + o.dump(); + out() << "after: " << res.toString() << endl; + res.dump(); + }*/ + + return res; + } + + /* copy the specified collection + isindex - if true, this is system.indexes collection, in which we do some transformation when copying. + */ + void Cloner::copy(const char *from_collection, const char *to_collection, bool isindex, bool logForRepl, bool masterSameProcess, bool slaveOk, Query query) { + auto_ptr<DBClientCursor> c; + { + dbtemprelease r; + c = conn->query( from_collection, query, 0, 0, 0, QueryOption_NoCursorTimeout | ( slaveOk ? QueryOption_SlaveOk : 0 ) ); + } + + list<BSONObj> storedForLater; + + assert( c.get() ); + long long n = 0; + time_t saveLast = time( 0 ); + while ( 1 ) { + { + dbtemprelease r; + if ( !c->more() ) + break; + } + BSONObj tmp = c->next(); + + /* assure object is valid. note this will slow us down a little. */ + if ( !tmp.valid() ) { + stringstream ss; + ss << "skipping corrupt object from " << from_collection; + BSONElement e = tmp.firstElement(); + try { + e.validate(); + ss << " firstElement: " << e; + } + catch( ... ){ + ss << " firstElement corrupt"; + } + out() << ss.str() << endl; + continue; + } + + ++n; + + BSONObj js = tmp; + if ( isindex ) { + assert( strstr(from_collection, "system.indexes") ); + js = fixindex(tmp); + storedForLater.push_back( js.getOwned() ); + continue; + } + + try { + theDataFileMgr.insert(to_collection, js); + if ( logForRepl ) + logOp("i", to_collection, js); + } + catch( UserException& e ) { + log() << "warning: exception cloning object in " << from_collection << ' ' << e.what() << " obj:" << js.toString() << '\n'; + } + + RARELY if ( time( 0 ) - saveLast > 60 ) { + log() << n << " objects cloned so far from collection " << from_collection << endl; + saveLast = time( 0 ); + } + } + + if ( storedForLater.size() ){ + for ( list<BSONObj>::iterator i = storedForLater.begin(); i!=storedForLater.end(); i++ ){ + BSONObj js = *i; + try { + theDataFileMgr.insert(to_collection, js); + if ( logForRepl ) + logOp("i", to_collection, js); + } + catch( UserException& e ) { + log() << "warning: exception cloning object in " << from_collection << ' ' << e.what() << " obj:" << js.toString() << '\n'; + } + } + } + } + + bool Cloner::go(const char *masterHost, string& errmsg, const string& fromdb, bool logForRepl, bool slaveOk, bool useReplAuth, bool snapshot) { + + massert( 10289 , "useReplAuth is not written to replication log", !useReplAuth || !logForRepl ); + + string todb = cc().database()->name; + stringstream a,b; + a << "localhost:" << cmdLine.port; + b << "127.0.0.1:" << cmdLine.port; + bool masterSameProcess = ( a.str() == masterHost || b.str() == masterHost ); + if ( masterSameProcess ) { + if ( fromdb == todb && cc().database()->path == dbpath ) { + // guard against an "infinite" loop + /* if you are replicating, the local.sources config may be wrong if you get this */ + errmsg = "can't clone from self (localhost)."; + return false; + } + } + /* todo: we can put these releases inside dbclient or a dbclient specialization. + or just wait until we get rid of global lock anyway. + */ + string ns = fromdb + ".system.namespaces"; + list<BSONObj> toClone; + { + dbtemprelease r; + + auto_ptr<DBClientCursor> c; + { + if ( !masterSameProcess ) { + auto_ptr< DBClientConnection > c( new DBClientConnection() ); + if ( !c->connect( masterHost, errmsg ) ) + return false; + if( !replAuthenticate(c.get()) ) + return false; + + conn = c; + } else { + conn.reset( new DBDirectClient() ); + } + c = conn->query( ns.c_str(), BSONObj(), 0, 0, 0, slaveOk ? QueryOption_SlaveOk : 0 ); + } + + if ( c.get() == 0 ) { + errmsg = "query failed " + ns; + return false; + } + + while ( c->more() ){ + BSONObj collection = c->next(); + + log(2) << "\t cloner got " << collection << endl; + + BSONElement e = collection.findElement("name"); + if ( e.eoo() ) { + string s = "bad system.namespaces object " + collection.toString(); + massert( 10290 , s.c_str(), false); + } + assert( !e.eoo() ); + assert( e.type() == String ); + const char *from_name = e.valuestr(); + + if( strstr(from_name, ".system.") ) { + /* system.users is cloned -- but nothing else from system. */ + if( legalClientSystemNS( from_name , true ) == 0 ){ + log(2) << "\t\t not cloning because system collection" << endl; + continue; + } + } + else if( strchr(from_name, '$') ) { + // don't clone index namespaces -- we take care of those separately below. + log(2) << "\t\t not cloning because has $ " << endl; + continue; + } + + toClone.push_back( collection.getOwned() ); + } + } + + for ( list<BSONObj>::iterator i=toClone.begin(); i != toClone.end(); i++ ){ + { + dbtemprelease r; + } + BSONObj collection = *i; + log(2) << " really will clone: " << collection << endl; + const char * from_name = collection["name"].valuestr(); + BSONObj options = collection.getObjectField("options"); + + /* change name "<fromdb>.collection" -> <todb>.collection */ + const char *p = strchr(from_name, '.'); + assert(p); + string to_name = todb + p; + + { + string err; + const char *toname = to_name.c_str(); + userCreateNS(toname, options, err, logForRepl); + } + log(1) << "\t\t cloning " << from_name << " -> " << to_name << endl; + Query q; + if( snapshot ) + q.snapshot(); + copy(from_name, to_name.c_str(), false, logForRepl, masterSameProcess, slaveOk, q); + } + + // now build the indexes + string system_indexes_from = fromdb + ".system.indexes"; + string system_indexes_to = todb + ".system.indexes"; + /* [dm]: is the ID index sometimes not called "_id_"? There is other code in the system that looks for a "_id" prefix + rather than this exact value. we should standardize. OR, remove names - which is in the bugdb. Anyway, this + is dubious here at the moment. + */ + copy(system_indexes_from.c_str(), system_indexes_to.c_str(), true, logForRepl, masterSameProcess, slaveOk, BSON( "name" << NE << "_id_" ) ); + + return true; + } + + bool Cloner::startCloneCollection( const char *fromhost, const char *ns, const BSONObj &query, string &errmsg, bool logForRepl, bool copyIndexes, int logSizeMb, long long &cursorId ) { + char db[256]; + nsToDatabase( ns, db ); + + NamespaceDetails *nsd = nsdetails( ns ); + if ( nsd ){ + /** note: its ok to clone into a collection, but only if the range you're copying + doesn't exist on this server */ + string err; + if ( runCount( ns , BSON( "query" << query ) , err ) > 0 ){ + log() << "WARNING: data already exists for: " << ns << " in range : " << query << " deleting..." << endl; + deleteObjects( ns , query , false , logForRepl , false ); + } + } + + { + dbtemprelease r; + auto_ptr< DBClientConnection > c( new DBClientConnection() ); + if ( !c->connect( fromhost, errmsg ) ) + return false; + if( !replAuthenticate(c.get()) ) + return false; + conn = c; + + // Start temporary op log + BSONObjBuilder cmdSpec; + cmdSpec << "logCollection" << ns << "start" << 1; + if ( logSizeMb != INT_MIN ) + cmdSpec << "logSizeMb" << logSizeMb; + BSONObj info; + if ( !conn->runCommand( db, cmdSpec.done(), info ) ) { + errmsg = "logCollection failed: " + (string)info; + return false; + } + } + + if ( ! nsd ) { + BSONObj spec = conn->findOne( string( db ) + ".system.namespaces", BSON( "name" << ns ) ); + if ( !userCreateNS( ns, spec.getObjectField( "options" ), errmsg, true ) ) + return false; + } + + copy( ns, ns, false, logForRepl, false, false, query ); + + if ( copyIndexes ) { + string indexNs = string( db ) + ".system.indexes"; + copy( indexNs.c_str(), indexNs.c_str(), true, logForRepl, false, false, BSON( "ns" << ns << "name" << NE << "_id_" ) ); + } + + auto_ptr< DBClientCursor > c; + { + dbtemprelease r; + string logNS = "local.temp.oplog." + string( ns ); + c = conn->query( logNS.c_str(), Query(), 0, 0, 0, QueryOption_CursorTailable ); + } + if ( c->more() ) { + replayOpLog( c.get(), query ); + cursorId = c->getCursorId(); + massert( 10291 , "Expected valid tailing cursor", cursorId != 0 ); + } else { + massert( 10292 , "Did not expect valid cursor for empty query result", c->getCursorId() == 0 ); + cursorId = 0; + } + c->decouple(); + return true; + } + + void Cloner::replayOpLog( DBClientCursor *c, const BSONObj &query ) { + Matcher matcher( query ); + while( 1 ) { + BSONObj op; + { + dbtemprelease t; + if ( !c->more() ) + break; + op = c->next(); + } + // For sharding v1.0, we don't allow shard key updates -- so just + // filter each insert by value. + if ( op.getStringField( "op" )[ 0 ] != 'i' || matcher.matches( op.getObjectField( "o" ) ) ) + ReplSource::applyOperation( op ); + } + } + + bool Cloner::finishCloneCollection( const char *fromhost, const char *ns, const BSONObj &query, long long cursorId, string &errmsg ) { + char db[256]; + nsToDatabase( ns, db ); + + auto_ptr< DBClientCursor > cur; + { + dbtemprelease r; + auto_ptr< DBClientConnection > c( new DBClientConnection() ); + if ( !c->connect( fromhost, errmsg ) ) + return false; + if( !replAuthenticate(c.get()) ) + return false; + conn = c; + string logNS = "local.temp.oplog." + string( ns ); + if ( cursorId != 0 ) + cur = conn->getMore( logNS.c_str(), cursorId ); + else + cur = conn->query( logNS.c_str(), Query() ); + } + replayOpLog( cur.get(), query ); + { + dbtemprelease t; + BSONObj info; + if ( !conn->runCommand( db, BSON( "logCollection" << ns << "validateComplete" << 1 ), info ) ) { + errmsg = "logCollection failed: " + (string)info; + return false; + } + } + return true; + } + + /* slaveOk - if true it is ok if the source of the data is !ismaster. + useReplAuth - use the credentials we normally use as a replication slave for the cloning + snapshot - use $snapshot mode for copying collections. note this should not be used when it isn't required, as it will be slower. + for example repairDatabase need not use it. + */ + bool cloneFrom(const char *masterHost, string& errmsg, const string& fromdb, bool logForReplication, + bool slaveOk, bool useReplAuth, bool snapshot) + { + Cloner c; + return c.go(masterHost, errmsg, fromdb, logForReplication, slaveOk, useReplAuth, snapshot); + } + + /* Usage: + mydb.$cmd.findOne( { clone: "fromhost" } ); + */ + class CmdClone : public Command { + public: + virtual bool slaveOk() { + return false; + } + virtual void help( stringstream &help ) const { + help << "clone this database from an instance of the db on another host\n"; + help << "example: { clone : \"host13\" }"; + } + CmdClone() : Command("clone") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string from = cmdObj.getStringField("clone"); + if ( from.empty() ) + return false; + /* replication note: we must logOp() not the command, but the cloned data -- if the slave + were to clone it would get a different point-in-time and not match. + */ + return cloneFrom(from.c_str(), errmsg, cc().database()->name, + /*logForReplication=*/!fromRepl, /*slaveok*/false, /*usereplauth*/false, /*snapshot*/true); + } + } cmdclone; + + class CmdCloneCollection : public Command { + public: + virtual bool slaveOk() { + return false; + } + CmdCloneCollection() : Command("cloneCollection") { } + virtual void help( stringstream &help ) const { + help << " example: { cloneCollection: <collection ns>, from: <hostname>, query: <query> }"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string fromhost = cmdObj.getStringField("from"); + if ( fromhost.empty() ) { + errmsg = "missing from spec"; + return false; + } + string collection = cmdObj.getStringField("cloneCollection"); + if ( collection.empty() ) { + errmsg = "missing cloneCollection spec"; + return false; + } + BSONObj query = cmdObj.getObjectField("query"); + if ( query.isEmpty() ) + query = BSONObj(); + BSONElement copyIndexesSpec = cmdObj.getField("copyindexes"); + bool copyIndexes = copyIndexesSpec.isBoolean() ? copyIndexesSpec.boolean() : true; + // Will not be used if doesn't exist. + int logSizeMb = cmdObj.getIntField( "logSizeMb" ); + + /* replication note: we must logOp() not the command, but the cloned data -- if the slave + were to clone it would get a different point-in-time and not match. + */ + setClient( collection.c_str() ); + + log() << "cloneCollection. db:" << ns << " collection:" << collection << " from: " << fromhost << " query: " << query << " logSizeMb: " << logSizeMb << ( copyIndexes ? "" : ", not copying indexes" ) << endl; + + Cloner c; + long long cursorId; + if ( !c.startCloneCollection( fromhost.c_str(), collection.c_str(), query, errmsg, !fromRepl, copyIndexes, logSizeMb, cursorId ) ) + return false; + return c.finishCloneCollection( fromhost.c_str(), collection.c_str(), query, cursorId, errmsg); + } + } cmdclonecollection; + + class CmdStartCloneCollection : public Command { + public: + virtual bool slaveOk() { + return false; + } + CmdStartCloneCollection() : Command("startCloneCollection") { } + virtual void help( stringstream &help ) const { + help << " example: { startCloneCollection: <collection ns>, from: <hostname>, query: <query> }"; + help << ", returned object includes a finishToken field, the value of which may be passed to the finishCloneCollection command"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string fromhost = cmdObj.getStringField("from"); + if ( fromhost.empty() ) { + errmsg = "missing from spec"; + return false; + } + string collection = cmdObj.getStringField("startCloneCollection"); + if ( collection.empty() ) { + errmsg = "missing startCloneCollection spec"; + return false; + } + BSONObj query = cmdObj.getObjectField("query"); + if ( query.isEmpty() ) + query = BSONObj(); + BSONElement copyIndexesSpec = cmdObj.getField("copyindexes"); + bool copyIndexes = copyIndexesSpec.isBoolean() ? copyIndexesSpec.boolean() : true; + // Will not be used if doesn't exist. + int logSizeMb = cmdObj.getIntField( "logSizeMb" ); + + /* replication note: we must logOp() not the command, but the cloned data -- if the slave + were to clone it would get a different point-in-time and not match. + */ + setClient( collection.c_str() ); + + log() << "startCloneCollection. db:" << ns << " collection:" << collection << " from: " << fromhost << " query: " << query << endl; + + Cloner c; + long long cursorId; + bool res = c.startCloneCollection( fromhost.c_str(), collection.c_str(), query, errmsg, !fromRepl, copyIndexes, logSizeMb, cursorId ); + + if ( res ) { + BSONObjBuilder b; + b << "fromhost" << fromhost; + b << "collection" << collection; + b << "query" << query; + b.appendDate( "cursorId", cursorId ); + BSONObj token = b.done(); + result << "finishToken" << token; + } + return res; + } + } cmdstartclonecollection; + + class CmdFinishCloneCollection : public Command { + public: + virtual bool slaveOk() { + return false; + } + CmdFinishCloneCollection() : Command("finishCloneCollection") { } + virtual void help( stringstream &help ) const { + help << " example: { finishCloneCollection: <finishToken> }"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + BSONObj fromToken = cmdObj.getObjectField("finishCloneCollection"); + if ( fromToken.isEmpty() ) { + errmsg = "missing finishCloneCollection finishToken spec"; + return false; + } + string fromhost = fromToken.getStringField( "fromhost" ); + if ( fromhost.empty() ) { + errmsg = "missing fromhost spec"; + return false; + } + string collection = fromToken.getStringField("collection"); + if ( collection.empty() ) { + errmsg = "missing collection spec"; + return false; + } + BSONObj query = fromToken.getObjectField("query"); + if ( query.isEmpty() ) { + query = BSONObj(); + } + long long cursorId = 0; + BSONElement cursorIdToken = fromToken.getField( "cursorId" ); + if ( cursorIdToken.type() == Date ) { + cursorId = cursorIdToken._numberLong(); + } + + setClient( collection.c_str() ); + + log() << "finishCloneCollection. db:" << ns << " collection:" << collection << " from: " << fromhost << " query: " << query << endl; + + Cloner c; + return c.finishCloneCollection( fromhost.c_str(), collection.c_str(), query, cursorId, errmsg ); + } + } cmdfinishclonecollection; + + /* Usage: + admindb.$cmd.findOne( { copydb: 1, fromhost: <hostname>, fromdb: <db>, todb: <db> } ); + */ + class CmdCopyDb : public Command { + public: + CmdCopyDb() : Command("copydb") { } + virtual bool adminOnly() { + return true; + } + virtual bool slaveOk() { + return false; + } + virtual void help( stringstream &help ) const { + help << "copy a database from antoher host to this host\n"; + help << "usage: {copydb: 1, fromhost: <hostname>, fromdb: <db>, todb: <db>}"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string fromhost = cmdObj.getStringField("fromhost"); + if ( fromhost.empty() ) { + /* copy from self */ + stringstream ss; + ss << "localhost:" << cmdLine.port; + fromhost = ss.str(); + } + string fromdb = cmdObj.getStringField("fromdb"); + string todb = cmdObj.getStringField("todb"); + if ( fromhost.empty() || todb.empty() || fromdb.empty() ) { + errmsg = "parms missing - {copydb: 1, fromhost: <hostname>, fromdb: <db>, todb: <db>}"; + return false; + } + setClient(todb.c_str()); + bool res = cloneFrom(fromhost.c_str(), errmsg, fromdb, /*logForReplication=*/!fromRepl, /*slaveok*/false, /*replauth*/false, /*snapshot*/true); + cc().clearns(); + return res; + } + } cmdcopydb; + + class CmdRenameCollection : public Command { + public: + CmdRenameCollection() : Command( "renameCollection" ) {} + virtual bool adminOnly() { + return true; + } + virtual bool slaveOk() { + return false; + } + virtual bool logTheOp() { + return true; // can't log steps when doing fast rename within a db, so always log the op rather than individual steps comprising it. + } + virtual void help( stringstream &help ) const { + help << " example: { renameCollection: foo.a, to: bar.b }"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string source = cmdObj.getStringField( name.c_str() ); + string target = cmdObj.getStringField( "to" ); + if ( source.empty() || target.empty() ) { + errmsg = "invalid command syntax"; + return false; + } + + setClient( source.c_str() ); + NamespaceDetails *nsd = nsdetails( source.c_str() ); + uassert( 10026 , "source namespace does not exist", nsd ); + bool capped = nsd->capped; + long long size = 0; + if ( capped ) + for( DiskLoc i = nsd->firstExtent; !i.isNull(); i = i.ext()->xnext ) + size += i.ext()->length; + + setClient( target.c_str() ); + + if ( nsdetails( target.c_str() ) ){ + uassert( 10027 , "target namespace exists", cmdObj["dropTarget"].trueValue() ); + BSONObjBuilder bb( result.subobjStart( "dropTarget" ) ); + dropCollection( target , errmsg , bb ); + bb.done(); + if ( errmsg.size() > 0 ) + return false; + } + + { + char from[256]; + nsToDatabase( source.c_str(), from ); + char to[256]; + nsToDatabase( target.c_str(), to ); + if ( strcmp( from, to ) == 0 ) { + renameNamespace( source.c_str(), target.c_str() ); + return true; + } + } + + BSONObjBuilder spec; + if ( capped ) { + spec.appendBool( "capped", true ); + spec.append( "size", double( size ) ); + } + if ( !userCreateNS( target.c_str(), spec.done(), errmsg, false ) ) + return false; + + auto_ptr< DBClientCursor > c; + DBDirectClient bridge; + + { + c = bridge.query( source, BSONObj() ); + } + while( 1 ) { + { + if ( !c->more() ) + break; + } + BSONObj o = c->next(); + theDataFileMgr.insert( target.c_str(), o ); + } + + char cl[256]; + nsToDatabase( source.c_str(), cl ); + string sourceIndexes = string( cl ) + ".system.indexes"; + nsToDatabase( target.c_str(), cl ); + string targetIndexes = string( cl ) + ".system.indexes"; + { + c = bridge.query( sourceIndexes, QUERY( "ns" << source ) ); + } + while( 1 ) { + { + if ( !c->more() ) + break; + } + BSONObj o = c->next(); + BSONObjBuilder b; + BSONObjIterator i( o ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( strcmp( e.fieldName(), "ns" ) == 0 ) { + b.append( "ns", target ); + } else { + b.append( e ); + } + } + BSONObj n = b.done(); + theDataFileMgr.insert( targetIndexes.c_str(), n ); + } + + setClient( source.c_str() ); + dropCollection( source, errmsg, result ); + return true; + } + } cmdrenamecollection; + +} // namespace mongo diff --git a/db/cmdline.h b/db/cmdline.h new file mode 100644 index 0000000..b071259 --- /dev/null +++ b/db/cmdline.h @@ -0,0 +1,57 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /* command line options + */ + /* concurrency: OK/READ */ + struct CmdLine { + int port; // --port + + string source; // --source + string only; // --only + + bool quiet; // --quiet + bool notablescan; // --notablescan + bool prealloc; // --noprealloc + bool smallfiles; // --smallfiles + + bool quota; // --quota + int quotaFiles; // --quotaFiles + bool cpu; // --cpu show cpu time periodically + + long long oplogSize; // --oplogSize + int defaultProfile; // --profile + int slowMS; // --time in ms that is "slow" + + enum { + DefaultDBPort = 27017, + ConfigServerPort = 27019, + ShardServerPort = 27018 + }; + + CmdLine() : + port(DefaultDBPort), quiet(false), notablescan(false), prealloc(true), smallfiles(false), + quota(false), quotaFiles(8), cpu(false), oplogSize(0), defaultProfile(0), slowMS(100) + { } + + }; + + extern CmdLine cmdLine; +} diff --git a/db/commands.cpp b/db/commands.cpp new file mode 100644 index 0000000..3078ea1 --- /dev/null +++ b/db/commands.cpp @@ -0,0 +1,102 @@ +/* commands.cpp + db "commands" (sent via db.$cmd.findOne(...)) + */ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "jsobj.h" +#include "commands.h" + +namespace mongo { + + map<string,Command*> * Command::_commands; + + Command::Command(const char *_name) : name(_name) { + // register ourself. + if ( _commands == 0 ) + _commands = new map<string,Command*>; + (*_commands)[name] = this; + } + + void Command::help( stringstream& help ) const { + help << "no help defined"; + } + + bool Command::runAgainstRegistered(const char *ns, BSONObj& jsobj, BSONObjBuilder& anObjBuilder) { + const char *p = strchr(ns, '.'); + if ( !p ) return false; + if ( strcmp(p, ".$cmd") != 0 ) return false; + + bool ok = false; + bool valid = false; + + BSONElement e; + e = jsobj.firstElement(); + + map<string,Command*>::iterator i; + + if ( e.eoo() ) + ; + /* check for properly registered command objects. Note that all the commands below should be + migrated over to the command object format. + */ + else if ( (i = _commands->find(e.fieldName())) != _commands->end() ) { + valid = true; + string errmsg; + Command *c = i->second; + if ( c->adminOnly() && strncmp(ns, "admin", 5) != 0 ) { + ok = false; + errmsg = "access denied"; + } + else if ( jsobj.getBoolField( "help" ) ){ + stringstream help; + help << "help for: " << e.fieldName() << " "; + c->help( help ); + anObjBuilder.append( "help" , help.str() ); + } + else { + ok = c->run(ns, jsobj, errmsg, anObjBuilder, false); + } + + anObjBuilder.append( "ok" , ok ? 1.0 : 0.0 ); + + if ( !ok ) { + anObjBuilder.append("errmsg", errmsg); + uassert_nothrow(errmsg.c_str()); + } + return true; + } + + return false; + } + + Command* Command::findCommand( const string& name ){ + map<string,Command*>::iterator i = _commands->find( name ); + if ( i == _commands->end() ) + return 0; + return i->second; + } + + + bool Command::readOnly( const string& name ){ + Command * c = findCommand( name ); + if ( ! c ) + return false; + return c->readOnly(); + } + +} // namespace mongo diff --git a/db/commands.h b/db/commands.h new file mode 100644 index 0000000..20fb98c --- /dev/null +++ b/db/commands.h @@ -0,0 +1,114 @@ +// commands.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" + +namespace mongo { + + class BSONObj; + class BSONObjBuilder; + class BufBuilder; + +// db "commands" (sent via db.$cmd.findOne(...)) +// subclass to make a command. + class Command { + public: + string name; + + /* run the given command + implement this... + + fromRepl - command is being invoked as part of replication syncing. In this situation you + normally do not want to log the command to the local oplog. + + return value is true if succeeded. if false, set errmsg text. + */ + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) = 0; + + /* true if a read lock is sufficient + note: logTheTop() MUST be false if readOnly + */ + virtual bool readOnly() { + return false; + } + + /* Return true if only the admin ns has privileges to run this command. */ + virtual bool adminOnly() { + return false; + } + + /* Like adminOnly, but even stricter: we must either be authenticated for admin db, + or, if running without auth, on the local interface. + + When localHostOnlyIfNoAuth() is true, adminOnly() must also be true. + */ + virtual bool localHostOnlyIfNoAuth(const BSONObj& cmdObj) { return false; } + + /* Return true if slaves of a replication pair are allowed to execute the command + (the command directly from a client -- if fromRepl, always allowed). + */ + virtual bool slaveOk() = 0; + + /* Return true if the client force a command to be run on a slave by + turning on the 'slaveok' option in the command query. + */ + virtual bool slaveOverrideOk() { + return false; + } + + /* Override and return true to if true,log the operation (logOp()) to the replication log. + (not done if fromRepl of course) + + Note if run() returns false, we do NOT log. + */ + virtual bool logTheOp() { + return false; + } + + virtual void help( stringstream& help ) const; + + /* Return true if authentication and security applies to the commands. Some commands + (e.g., getnonce, authenticate) can be done by anyone even unauthorized. + */ + virtual bool requiresAuth() { return true; } + + Command(const char *_name); + virtual ~Command() {} + + protected: + BSONObj getQuery( const BSONObj& cmdObj ){ + if ( cmdObj["query"].type() == Object ) + return cmdObj["query"].embeddedObject(); + if ( cmdObj["q"].type() == Object ) + return cmdObj["q"].embeddedObject(); + return BSONObj(); + } + + static map<string,Command*> * _commands; + + public: + static bool runAgainstRegistered(const char *ns, BSONObj& jsobj, BSONObjBuilder& anObjBuilder); + static bool readOnly( const string& name ); + static Command * findCommand( const string& name ); + }; + + bool _runCommands(const char *ns, BSONObj& jsobj, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions); + +} // namespace mongo diff --git a/db/concurrency.h b/db/concurrency.h new file mode 100644 index 0000000..daf09b6 --- /dev/null +++ b/db/concurrency.h @@ -0,0 +1,272 @@ +/* concurrency.h + + mongod concurrency rules & notes will be placed here. + + Mutex heirarchy (1 = "leaf") + name level + Logstream::mutex 1 + ClientCursor::ccmutex 2 + dblock 3 + + End func name with _inlock to indicate "caller must lock before calling". +*/ + +#pragma once + +#if BOOST_VERSION >= 103500 +#include <boost/thread/shared_mutex.hpp> +#undef assert +#define assert xassert +#else +#warning built with boost version 1.34 or older limited concurrency +#endif + +namespace mongo { + + /* mutex time stats */ + class MutexInfo { + unsigned long long start, enter, timeLocked; // all in microseconds + int locked; + + public: + MutexInfo() : locked(0) { + start = curTimeMicros64(); + } + void entered() { + if ( locked == 0 ) + enter = curTimeMicros64(); + locked++; + assert( locked >= 1 ); + } + void leaving() { + locked--; + assert( locked >= 0 ); + if ( locked == 0 ) + timeLocked += curTimeMicros64() - enter; + } + int isLocked() const { + return locked; + } + void getTimingInfo(unsigned long long &s, unsigned long long &tl) const { + s = start; + tl = timeLocked; + } + }; + +#if BOOST_VERSION >= 103500 +//#if 0 + class MongoMutex { + MutexInfo _minfo; + boost::shared_mutex _m; + ThreadLocalValue<int> _state; + + /* we use a separate TLS value for releasedEarly - that is ok as + our normal/common code path, we never even touch it. + */ + ThreadLocalValue<bool> _releasedEarly; + public: + /** + * @return + * > 0 write lock + * = 0 no lock + * < 0 read lock + */ + int getState(){ return _state.get(); } + void assertWriteLocked() { + assert( getState() > 0 ); + DEV assert( !_releasedEarly.get() ); + } + bool atLeastReadLocked() { return _state.get() != 0; } + void assertAtLeastReadLocked() { assert(atLeastReadLocked()); } + + void lock() { + DEV cout << "LOCK" << endl; + int s = _state.get(); + if( s > 0 ) { + _state.set(s+1); + return; + } + massert( 10293 , "internal error: locks are not upgradeable", s == 0 ); + _state.set(1); + _m.lock(); + _minfo.entered(); + } + void unlock() { + DEV cout << "UNLOCK" << endl; + int s = _state.get(); + if( s > 1 ) { + _state.set(s-1); + return; + } + if( s != 1 ) { + if( _releasedEarly.get() ) { + _releasedEarly.set(false); + return; + } + assert(false); // attempt to unlock when wasn't in a write lock + } + _state.set(0); + _minfo.leaving(); + _m.unlock(); + } + + /* unlock (write lock), and when unlock() is called later, + be smart then and don't unlock it again. + */ + void releaseEarly() { + assert( getState() == 1 ); // must not be recursive + assert( !_releasedEarly.get() ); + _releasedEarly.set(true); + unlock(); + } + + void lock_shared() { + DEV cout << " LOCKSHARED" << endl; + int s = _state.get(); + if( s ) { + if( s > 0 ) { + // already in write lock - just be recursive and stay write locked + _state.set(s+1); + return; + } + else { + // already in read lock - recurse + _state.set(s-1); + return; + } + } + _state.set(-1); + _m.lock_shared(); + } + void unlock_shared() { + DEV cout << " UNLOCKSHARED" << endl; + int s = _state.get(); + if( s > 0 ) { + assert( s > 1 ); /* we must have done a lock write first to have s > 1 */ + _state.set(s-1); + return; + } + if( s < -1 ) { + _state.set(s+1); + return; + } + assert( s == -1 ); + _state.set(0); + _m.unlock_shared(); + } + MutexInfo& info() { return _minfo; } + }; +#else + /* this will be for old versions of boost */ + class MongoMutex { + MutexInfo _minfo; + boost::recursive_mutex m; + ThreadLocalValue<bool> _releasedEarly; + public: + MongoMutex() { } + void lock() { +#if BOOST_VERSION >= 103500 + m.lock(); +#else + boost::detail::thread::lock_ops<boost::recursive_mutex>::lock(m); +#endif + _minfo.entered(); + } + + void releaseEarly() { + assertWriteLocked(); // aso must not be recursive, although we don't verify that in the old boost version + assert( !_releasedEarly.get() ); + _releasedEarly.set(true); + _unlock(); + } + + void _unlock() { + _minfo.leaving(); +#if BOOST_VERSION >= 103500 + m.unlock(); +#else + boost::detail::thread::lock_ops<boost::recursive_mutex>::unlock(m); +#endif + } + void unlock() { + if( _releasedEarly.get() ) { + _releasedEarly.set(false); + return; + } + _unlock(); + } + + void lock_shared() { lock(); } + void unlock_shared() { unlock(); } + MutexInfo& info() { return _minfo; } + void assertWriteLocked() { + assert( info().isLocked() ); + } + void assertAtLeastReadLocked() { + assert( info().isLocked() ); + } + bool atLeastReadLocked() { return info().isLocked(); } + int getState(){ return info().isLocked() ? 1 : 0; } + }; +#endif + + extern MongoMutex &dbMutex; + + void dbunlocking_write(); + void dbunlocking_read(); + + struct writelock { + writelock(const string& ns) { + dbMutex.lock(); + } + ~writelock() { + dbunlocking_write(); + dbMutex.unlock(); + } + }; + + struct readlock { + readlock(const string& ns) { + dbMutex.lock_shared(); + } + ~readlock() { + dbunlocking_read(); + dbMutex.unlock_shared(); + } + }; + + class mongolock { + bool _writelock; + public: + mongolock(bool write) : _writelock(write) { + if( _writelock ) { + dbMutex.lock(); + } + else + dbMutex.lock_shared(); + } + ~mongolock() { + if( _writelock ) { + dbunlocking_write(); + dbMutex.unlock(); + } + else { + dbunlocking_read(); + dbMutex.unlock_shared(); + } + } + /* this unlocks, does NOT upgrade. that works for our current usage */ + void releaseAndWriteLock(); + }; + + /* use writelock and readlock instead */ + struct dblock : public writelock { + dblock() : writelock("") { } + ~dblock() { + } + }; + + // eliminate + inline void assertInWriteLock() { dbMutex.assertWriteLocked(); } + +} diff --git a/db/curop.h b/db/curop.h new file mode 100644 index 0000000..8a28f4f --- /dev/null +++ b/db/curop.h @@ -0,0 +1,157 @@ +// curop.h + +#pragma once + +#include "namespace.h" +#include "security.h" +#include "client.h" + +namespace mongo { + + class OpDebug { + public: + StringBuilder str; + + void reset(){ + str.reset(); + } + }; + + /* Current operation (for the current Client). + an embedded member of Client class, and typically used from within the mutex there. */ + class CurOp : boost::noncopyable { + static WrappingInt _nextOpNum; + static BSONObj _tooBig; // { $msg : "query not recording (too large)" } + + bool _active; + Timer _timer; + int _op; + WrappingInt _opNum; + char _ns[Namespace::MaxNsLen+2]; + struct sockaddr_in client; + + char _queryBuf[256]; + bool haveQuery() const { return *((int *) _queryBuf) != 0; } + void resetQuery(int x=0) { *((int *)_queryBuf) = x; } + BSONObj query() { + if( *((int *) _queryBuf) == 1 ) { + return _tooBig; + } + BSONObj o(_queryBuf); + return o; + } + + OpDebug _debug; + public: + void reset( const sockaddr_in &_client) { + _active = true; + _opNum = _nextOpNum.atomicIncrement(); + _timer.reset(); + _ns[0] = '?'; // just in case not set later + _debug.reset(); + resetQuery(); + client = _client; + } + + OpDebug& debug(){ + return _debug; + } + + WrappingInt opNum() const { return _opNum; } + bool active() const { return _active; } + + int elapsedMillis(){ return _timer.millis(); } + + /** micros */ + unsigned long long startTime(){ + return _timer.startTime(); + } + + void setActive(bool active) { _active = active; } + void setNS(const char *ns) { + strncpy(_ns, ns, Namespace::MaxNsLen); + } + void setOp(int op) { _op = op; } + void setQuery(const BSONObj& query) { + if( query.objsize() > (int) sizeof(_queryBuf) ) { + resetQuery(1); // flag as too big and return + return; + } + memcpy(_queryBuf, query.objdata(), query.objsize()); + } + + CurOp() { + _active = false; +// opNum = 0; + _op = 0; + // These addresses should never be written to again. The zeroes are + // placed here as a precaution because currentOp may be accessed + // without the db mutex. + memset(_ns, 0, sizeof(_ns)); + memset(_queryBuf, 0, sizeof(_queryBuf)); + } + + BSONObj info() { + AuthenticationInfo *ai = currentClient.get()->ai; + if( !ai->isAuthorized("admin") ) { + BSONObjBuilder b; + b.append("err", "unauthorized"); + return b.obj(); + } + return infoNoauth(); + } + + BSONObj infoNoauth() { + BSONObjBuilder b; + b.append("opid", _opNum); + b.append("active", _active); + if( _active ) + b.append("secs_running", _timer.seconds() ); + if( _op == 2004 ) + b.append("op", "query"); + else if( _op == 2005 ) + b.append("op", "getMore"); + else if( _op == 2001 ) + b.append("op", "update"); + else if( _op == 2002 ) + b.append("op", "insert"); + else if( _op == 2006 ) + b.append("op", "delete"); + else + b.append("op", _op); + b.append("ns", _ns); + + if( haveQuery() ) { + b.append("query", query()); + } + // b.append("inLock", ?? + stringstream clientStr; + clientStr << inet_ntoa( client.sin_addr ) << ":" << ntohs( client.sin_port ); + b.append("client", clientStr.str()); + return b.obj(); + } + }; + + /* 0 = ok + 1 = kill current operation and reset this to 0 + future: maybe use this as a "going away" thing on process termination with a higher flag value + */ + extern class KillCurrentOp { + enum { Off, On, All } state; + WrappingInt toKill; + public: + void killAll() { state = All; } + void kill(WrappingInt i) { toKill = i; state = On; } + + void checkForInterrupt() { + if( state != Off ) { + if( state == All ) + uasserted(11600,"interrupted at shutdown"); + if( cc().curop()->opNum() == toKill ) { + state = Off; + uasserted(11601,"interrupted"); + } + } + } + } killCurrentOp; +} diff --git a/db/cursor.cpp b/db/cursor.cpp new file mode 100644 index 0000000..29f9c97 --- /dev/null +++ b/db/cursor.cpp @@ -0,0 +1,159 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "pdfile.h" +#include "curop.h" + +namespace mongo { + + bool BasicCursor::advance() { + killCurrentOp.checkForInterrupt(); + if ( eof() ) { + if ( tailable_ && !last.isNull() ) { + curr = s->next( last ); + } else { + return false; + } + } else { + last = curr; + curr = s->next( curr ); + } + return ok(); + } + + /* these will be used outside of mutexes - really functors - thus the const */ + class Forward : public AdvanceStrategy { + virtual DiskLoc next( const DiskLoc &prev ) const { + return prev.rec()->getNext( prev ); + } + } _forward; + + class Reverse : public AdvanceStrategy { + virtual DiskLoc next( const DiskLoc &prev ) const { + return prev.rec()->getPrev( prev ); + } + } _reverse; + + const AdvanceStrategy *forward() { + return &_forward; + } + const AdvanceStrategy *reverse() { + return &_reverse; + } + + DiskLoc nextLoop( NamespaceDetails *nsd, const DiskLoc &prev ) { + assert( nsd->capLooped() ); + DiskLoc next = forward()->next( prev ); + if ( !next.isNull() ) + return next; + return nsd->firstRecord(); + } + + DiskLoc prevLoop( NamespaceDetails *nsd, const DiskLoc &curr ) { + assert( nsd->capLooped() ); + DiskLoc prev = reverse()->next( curr ); + if ( !prev.isNull() ) + return prev; + return nsd->lastRecord(); + } + + ForwardCappedCursor::ForwardCappedCursor( NamespaceDetails *_nsd, const DiskLoc &startLoc ) : + nsd( _nsd ) { + if ( !nsd ) + return; + DiskLoc start = startLoc; + if ( start.isNull() ) { + if ( !nsd->capLooped() ) + start = nsd->firstRecord(); + else { + start = nsd->capExtent.ext()->firstRecord; + if ( !start.isNull() && start == nsd->capFirstNewRecord ) { + start = nsd->capExtent.ext()->lastRecord; + start = nextLoop( nsd, start ); + } + } + } + curr = start; + s = this; + } + + DiskLoc ForwardCappedCursor::next( const DiskLoc &prev ) const { + assert( nsd ); + if ( !nsd->capLooped() ) + return forward()->next( prev ); + + DiskLoc i = prev; + // Last record + if ( i == nsd->capExtent.ext()->lastRecord ) + return DiskLoc(); + i = nextLoop( nsd, i ); + // If we become capFirstNewRecord from same extent, advance to next extent. + if ( i == nsd->capFirstNewRecord && + i != nsd->capExtent.ext()->firstRecord ) + i = nextLoop( nsd, nsd->capExtent.ext()->lastRecord ); + // If we have just gotten to beginning of capExtent, skip to capFirstNewRecord + if ( i == nsd->capExtent.ext()->firstRecord ) + i = nsd->capFirstNewRecord; + return i; + } + + ReverseCappedCursor::ReverseCappedCursor( NamespaceDetails *_nsd, const DiskLoc &startLoc ) : + nsd( _nsd ) { + if ( !nsd ) + return; + DiskLoc start = startLoc; + if ( start.isNull() ) { + if ( !nsd->capLooped() ) { + start = nsd->lastRecord(); + } else { + start = nsd->capExtent.ext()->lastRecord; + } + } + curr = start; + s = this; + } + + DiskLoc ReverseCappedCursor::next( const DiskLoc &prev ) const { + assert( nsd ); + if ( !nsd->capLooped() ) + return reverse()->next( prev ); + + DiskLoc i = prev; + // Last record + if ( nsd->capFirstNewRecord == nsd->capExtent.ext()->firstRecord ) { + if ( i == nextLoop( nsd, nsd->capExtent.ext()->lastRecord ) ) { + return DiskLoc(); + } + } else { + if ( i == nsd->capExtent.ext()->firstRecord ) { + return DiskLoc(); + } + } + // If we are capFirstNewRecord, advance to prev extent, otherwise just get prev. + if ( i == nsd->capFirstNewRecord ) + i = prevLoop( nsd, nsd->capExtent.ext()->firstRecord ); + else + i = prevLoop( nsd, i ); + // If we just became last in cap extent, advance past capFirstNewRecord + // (We know capExtent.ext()->firstRecord != capFirstNewRecord, since would + // have returned DiskLoc() earlier otherwise.) + if ( i == nsd->capExtent.ext()->lastRecord ) + i = reverse()->next( nsd->capFirstNewRecord ); + + return i; + } +} // namespace mongo diff --git a/db/cursor.h b/db/cursor.h new file mode 100644 index 0000000..3868cca --- /dev/null +++ b/db/cursor.h @@ -0,0 +1,198 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" + +#include "jsobj.h" +#include "storage.h" + +namespace mongo { + + class Record; + + /* Query cursors, base class. This is for our internal cursors. "ClientCursor" is a separate + concept and is for the user's cursor. + + WARNING concurrency: the vfunctions below are called back from within a + ClientCursor::ccmutex. Don't cause a deadlock, you've been warned. + */ + class Cursor { + public: + virtual ~Cursor() {} + virtual bool ok() = 0; + bool eof() { + return !ok(); + } + virtual Record* _current() = 0; + virtual BSONObj current() = 0; + virtual DiskLoc currLoc() = 0; + virtual bool advance() = 0; /*true=ok*/ + virtual BSONObj currKey() const { return BSONObj(); } + + // DiskLoc the cursor requires for continued operation. Before this + // DiskLoc is deleted, the cursor must be incremented or destroyed. + virtual DiskLoc refLoc() = 0; + + /* Implement these if you want the cursor to be "tailable" */ + + /* Request that the cursor starts tailing after advancing past last record. */ + /* The implementation may or may not honor this request. */ + virtual void setTailable() {} + /* indicates if tailing is enabled. */ + virtual bool tailable() { + return false; + } + + virtual void aboutToDeleteBucket(const DiskLoc& b) { } + + /* optional to implement. if implemented, means 'this' is a prototype */ + virtual Cursor* clone() { + return 0; + } + + virtual BSONObj indexKeyPattern() { + return BSONObj(); + } + + /* called after every query block is iterated -- i.e. between getMore() blocks + so you can note where we are, if necessary. + */ + virtual void noteLocation() { } + + /* called before query getmore block is iterated */ + virtual void checkLocation() { } + + virtual string toString() { + return "abstract?"; + } + + /* used for multikey index traversal to avoid sending back dups. see Matcher::matches(). + if a multikey index traversal: + if loc has already been sent, returns true. + otherwise, marks loc as sent. + @param deep - match was against an array, so we know it is multikey. this is legacy and kept + for backwards datafile compatibility. 'deep' can be eliminated next time we + force a data file conversion. 7Jul09 + */ + virtual bool getsetdup(DiskLoc loc) = 0; + + virtual BSONObj prettyStartKey() const { return BSONObj(); } + virtual BSONObj prettyEndKey() const { return BSONObj(); } + + virtual bool capped() const { return false; } + }; + + // strategy object implementing direction of traversal. + class AdvanceStrategy { + public: + virtual ~AdvanceStrategy() { } + virtual DiskLoc next( const DiskLoc &prev ) const = 0; + }; + + const AdvanceStrategy *forward(); + const AdvanceStrategy *reverse(); + + /* table-scan style cursor */ + class BasicCursor : public Cursor { + protected: + DiskLoc curr, last; + const AdvanceStrategy *s; + + private: + bool tailable_; + void init() { + tailable_ = false; + } + public: + bool ok() { + return !curr.isNull(); + } + Record* _current() { + assert( ok() ); + return curr.rec(); + } + BSONObj current() { + Record *r = _current(); + BSONObj j(r); + return j; + } + virtual DiskLoc currLoc() { + return curr; + } + virtual DiskLoc refLoc() { + return curr.isNull() ? last : curr; + } + + bool advance(); + + BasicCursor(DiskLoc dl, const AdvanceStrategy *_s = forward()) : curr(dl), s( _s ) { + init(); + } + BasicCursor(const AdvanceStrategy *_s = forward()) : s( _s ) { + init(); + } + virtual string toString() { + return "BasicCursor"; + } + virtual void setTailable() { + if ( !curr.isNull() || !last.isNull() ) + tailable_ = true; + } + virtual bool tailable() { + return tailable_; + } + virtual bool getsetdup(DiskLoc loc) { return false; } + }; + + /* used for order { $natural: -1 } */ + class ReverseCursor : public BasicCursor { + public: + ReverseCursor(DiskLoc dl) : BasicCursor( dl, reverse() ) { } + ReverseCursor() : BasicCursor( reverse() ) { } + virtual string toString() { + return "ReverseCursor"; + } + }; + + class NamespaceDetails; + + class ForwardCappedCursor : public BasicCursor, public AdvanceStrategy { + public: + ForwardCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() ); + virtual string toString() { + return "ForwardCappedCursor"; + } + virtual DiskLoc next( const DiskLoc &prev ) const; + virtual bool capped() const { return true; } + private: + NamespaceDetails *nsd; + }; + + class ReverseCappedCursor : public BasicCursor, public AdvanceStrategy { + public: + ReverseCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() ); + virtual string toString() { + return "ReverseCappedCursor"; + } + virtual DiskLoc next( const DiskLoc &prev ) const; + virtual bool capped() const { return true; } + private: + NamespaceDetails *nsd; + }; + +} // namespace mongo diff --git a/db/database.cpp b/db/database.cpp new file mode 100644 index 0000000..6361e86 --- /dev/null +++ b/db/database.cpp @@ -0,0 +1,64 @@ +// database.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "pdfile.h" +#include "database.h" + +namespace mongo { + + bool Database::_openAllFiles = false; + + bool Database::setProfilingLevel( int newLevel , string& errmsg ){ + if ( profile == newLevel ) + return true; + + if ( newLevel < 0 || newLevel > 2 ){ + errmsg = "profiling level has to be >=0 and <= 2"; + return false; + } + + if ( newLevel == 0 ){ + profile = 0; + return true; + } + + assert( cc().database() == this ); + + if ( ! namespaceIndex.details( profileName.c_str() ) ){ + log(1) << "creating profile ns: " << profileName << endl; + BSONObjBuilder spec; + spec.appendBool( "capped", true ); + spec.append( "size", 131072.0 ); + if ( ! userCreateNS( profileName.c_str(), spec.done(), errmsg , true ) ){ + return false; + } + } + profile = newLevel; + return true; + } + + void Database::finishInit(){ + if ( cmdLine.defaultProfile == profile ) + return; + + string errmsg; + massert( 12506 , errmsg , setProfilingLevel( cmdLine.defaultProfile , errmsg ) ); + } + +} // namespace mongo diff --git a/db/database.h b/db/database.h new file mode 100644 index 0000000..0fcf386 --- /dev/null +++ b/db/database.h @@ -0,0 +1,207 @@ +// database.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "cmdline.h" + +namespace mongo { + + + /** + * Database represents a database database + * Each database database has its own set of files -- dbname.ns, dbname.0, dbname.1, ... + * NOT memory mapped + */ + class Database { + public: + static bool _openAllFiles; + + Database(const char *nm, bool& newDb, const string& _path = dbpath) + : name(nm), path(_path), namespaceIndex( path, name ) { + + { // check db name is valid + int L = strlen(nm); + uassert( 10028 , "db name is empty", L > 0 ); + uassert( 10029 , "bad db name [1]", *nm != '.' ); + uassert( 10030 , "bad db name [2]", nm[L-1] != '.' ); + uassert( 10031 , "bad char(s) in db name", strchr(nm, ' ') == 0 ); + uassert( 10032 , "db name too long", L < 64 ); + } + + newDb = namespaceIndex.exists(); + profile = 0; + profileName = name + ".system.profile"; + + // If already exists, open. Otherwise behave as if empty until + // there's a write, then open. + if ( ! newDb || cmdLine.defaultProfile ) { + namespaceIndex.init(); + if( _openAllFiles ) + openAllFiles(); + + } + + magic = 781231; + } + + ~Database() { + magic = 0; + btreeStore->closeFiles(name, path); + int n = files.size(); + for ( int i = 0; i < n; i++ ) + delete files[i]; + } + + /** + * tries to make sure that this hasn't been deleted + */ + bool isOk(){ + return magic == 781231; + } + + bool isEmpty(){ + return ! namespaceIndex.allocated(); + } + + bool exists(int n) { + stringstream ss; + ss << name << '.' << n; + boost::filesystem::path fullName; + fullName = boost::filesystem::path(path) / ss.str(); + return boost::filesystem::exists(fullName); + } + + void openAllFiles() { + int n = 0; + while( exists(n) ) { + getFile(n); + n++; + } + // If last file is empty, consider it preallocated and make sure it's not mapped + // until a write is requested + if ( n > 1 && getFile( n - 1 )->getHeader()->isEmpty() ) { + delete files[ n - 1 ]; + files.pop_back(); + } + } + + MongoDataFile* getFile( int n, int sizeNeeded = 0, bool preallocateOnly = false ) { + assert(this); + + namespaceIndex.init(); + if ( n < 0 || n >= DiskLoc::MaxFiles ) { + out() << "getFile(): n=" << n << endl; +#if !defined(_RECSTORE) + if( n >= RecCache::Base && n <= RecCache::Base+1000 ) + massert( 10294 , "getFile(): bad file number - using recstore db w/nonrecstore db build?", false); +#endif + massert( 10295 , "getFile(): bad file number value (corrupt db?): run repair", false); + } + DEV { + if ( n > 100 ) + out() << "getFile(): n=" << n << "?" << endl; + } + MongoDataFile* p = 0; + if ( !preallocateOnly ) { + while ( n >= (int) files.size() ) + files.push_back(0); + p = files[n]; + } + if ( p == 0 ) { + stringstream ss; + ss << name << '.' << n; + boost::filesystem::path fullName; + fullName = boost::filesystem::path(path) / ss.str(); + string fullNameString = fullName.string(); + p = new MongoDataFile(n); + int minSize = 0; + if ( n != 0 && files[ n - 1 ] ) + minSize = files[ n - 1 ]->getHeader()->fileLength; + if ( sizeNeeded + MDFHeader::headerSize() > minSize ) + minSize = sizeNeeded + MDFHeader::headerSize(); + try { + p->open( fullNameString.c_str(), minSize, preallocateOnly ); + } + catch ( AssertionException& ) { + delete p; + throw; + } + if ( preallocateOnly ) + delete p; + else + files[n] = p; + } + return preallocateOnly ? 0 : p; + } + + MongoDataFile* addAFile( int sizeNeeded = 0, bool preallocateNextFile = false ) { + int n = (int) files.size(); + MongoDataFile *ret = getFile( n, sizeNeeded ); + if ( preallocateNextFile ) + preallocateAFile(); + return ret; + } + + // safe to call this multiple times - the implementation will only preallocate one file + void preallocateAFile() { + int n = (int) files.size(); + getFile( n, 0, true ); + } + + MongoDataFile* suitableFile( int sizeNeeded ) { + MongoDataFile* f = newestFile(); + for ( int i = 0; i < 8; i++ ) { + if ( f->getHeader()->unusedLength >= sizeNeeded ) + break; + f = addAFile( sizeNeeded ); + if ( f->getHeader()->fileLength >= MongoDataFile::maxSize() ) // this is as big as they get so might as well stop + break; + } + return f; + } + + Extent* allocExtent( const char *ns, int size, bool capped ) { + Extent *e = DataFileMgr::allocFromFreeList( ns, size, capped ); + if( e ) return e; + return suitableFile( size )->createExtent( ns, size, capped ); + } + + MongoDataFile* newestFile() { + int n = (int) files.size(); + if ( n > 0 ) n--; + return getFile(n); + } + + /** + * @return true if success, false otherwise + */ + bool setProfilingLevel( int newLevel , string& errmsg ); + + void finishInit(); + + vector<MongoDataFile*> files; + string name; // "alleyinsider" + string path; + NamespaceIndex namespaceIndex; + int profile; // 0=off. + string profileName; // "alleyinsider.system.profile" + int magic; // used for making sure the object is still loaded in memory + }; + +} // namespace mongo diff --git a/db/db.cpp b/db/db.cpp new file mode 100644 index 0000000..9b1a22a --- /dev/null +++ b/db/db.cpp @@ -0,0 +1,1101 @@ +// db.cpp : Defines the entry point for the console application. +// + +/** +* Copyright (C) 2008 10gen Inc.info +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "db.h" +#include "query.h" +#include "introspect.h" +#include "repl.h" +#include "../util/unittest.h" +#include "../util/file_allocator.h" +#include "../util/background.h" +#include "dbmessage.h" +#include "instance.h" +#include "clientcursor.h" +#include "pdfile.h" +#if !defined(_WIN32) +#include <sys/file.h> +#endif + +#if defined(_WIN32) +#include "../util/ntservice.h" +#endif + +#include "../scripting/engine.h" +#include "module.h" +#include "cmdline.h" + +namespace mongo { + + bool useJNI = true; + + /* only off if --nocursors which is for debugging. */ + extern bool useCursors; + /* only off if --nohints */ + extern bool useHints; + + bool noHttpInterface = false; + + extern string bind_ip; + extern char *appsrvPath; + extern bool autoresync; + extern int diagLogging; + extern int lenForNewNsFiles; + extern int lockFile; + + void setupSignals(); + void closeAllSockets(); + void startReplication(); + void pairWith(const char *remoteEnd, const char *arb); + void setRecCacheSize(unsigned MB); + + const char *ourgetns() { + Client *c = currentClient.get(); + return c ? c->ns() : ""; + } + + struct MyStartupTests { + MyStartupTests() { + assert( sizeof(OID) == 12 ); + } + } mystartupdbcpp; + + QueryResult* emptyMoreResult(long long); + + void testTheDb() { + OpDebug debug; + setClient("sys.unittest.pdfile"); + + /* this is not validly formatted, if you query this namespace bad things will happen */ + theDataFileMgr.insert("sys.unittest.pdfile", (void *) "hello worldx", 13); + theDataFileMgr.insert("sys.unittest.pdfile", (void *) "hello worldx", 13); + + BSONObj j1((const char *) &js1); + deleteObjects("sys.unittest.delete", j1, false); + theDataFileMgr.insert("sys.unittest.delete", &js1, sizeof(js1)); + deleteObjects("sys.unittest.delete", j1, false); + updateObjects("sys.unittest.delete", j1, j1, true,false,true,debug); + updateObjects("sys.unittest.delete", j1, j1, false,false,true,debug); + + auto_ptr<Cursor> c = theDataFileMgr.findAll("sys.unittest.pdfile"); + while ( c->ok() ) { + c->_current(); + c->advance(); + } + out() << endl; + + cc().clearns(); + } + + MessagingPort *connGrab = 0; + void connThread(); + + class OurListener : public Listener { + public: + OurListener(const string &ip, int p) : Listener(ip, p) { } + virtual void accepted(MessagingPort *mp) { + assert( connGrab == 0 ); + if ( ! connTicketHolder.tryAcquire() ){ + log() << "connection refused because too many open connections" << endl; + // TODO: would be nice if we notified them... + mp->shutdown(); + return; + } + connGrab = mp; + try { + boost::thread thr(connThread); + while ( connGrab ) + sleepmillis(1); + } + catch ( boost::thread_resource_error& ){ + log() << "can't create new thread, closing connection" << endl; + mp->shutdown(); + connGrab = 0; + } + catch ( ... ){ + log() << "unkonwn exception starting connThread" << endl; + mp->shutdown(); + connGrab = 0; + } + } + }; + + void webServerThread(); + void pdfileInit(); + + void listen(int port) { + log() << mongodVersion() << endl; + printGitVersion(); + printSysInfo(); + pdfileInit(); + //testTheDb(); + log() << "waiting for connections on port " << port << endl; + OurListener l(bind_ip, port); + startReplication(); + if ( !noHttpInterface ) + boost::thread thr(webServerThread); + if ( l.init() ) { + ListeningSockets::get()->add( l.socket() ); + l.listen(); + } + } + +} // namespace mongo + +#include "client.h" + +namespace mongo { + + void sysRuntimeInfo() { + out() << "sysinfo:\n"; +#if defined(_SC_PAGE_SIZE) + out() << " page size: " << (int) sysconf(_SC_PAGE_SIZE) << endl; +#endif +#if defined(_SC_PHYS_PAGES) + out() << " _SC_PHYS_PAGES: " << sysconf(_SC_PHYS_PAGES) << endl; +#endif +#if defined(_SC_AVPHYS_PAGES) + out() << " _SC_AVPHYS_PAGES: " << sysconf(_SC_AVPHYS_PAGES) << endl; +#endif + } + + /* we create one thread for each connection from an app server database. + app server will open a pool of threads. + */ + void connThread() + { + TicketHolderReleaser connTicketReleaser( &connTicketHolder ); + Client::initThread("conn"); + + /* todo: move to Client object */ + LastError *le = new LastError(); + lastError.reset(le); + + MessagingPort& dbMsgPort = *connGrab; + connGrab = 0; + Client& c = cc(); + + try { + + c.ai->isLocalHost = dbMsgPort.farEnd.isLocalHost(); + + Message m; + while ( 1 ) { + m.reset(); + + if ( !dbMsgPort.recv(m) ) { + if( !cmdLine.quiet ) + log() << "end connection " << dbMsgPort.farEnd.toString() << endl; + dbMsgPort.shutdown(); + break; + } + + lastError.startRequest( m , le ); + + DbResponse dbresponse; + if ( !assembleResponse( m, dbresponse, dbMsgPort.farEnd.sa ) ) { + out() << curTimeMillis() % 10000 << " end msg " << dbMsgPort.farEnd.toString() << endl; + /* todo: we may not wish to allow this, even on localhost: very low priv accounts could stop us. */ + if ( dbMsgPort.farEnd.isLocalHost() ) { + dbMsgPort.shutdown(); + sleepmillis(50); + problem() << "exiting end msg" << endl; + dbexit(EXIT_CLEAN); + } + else { + out() << " (not from localhost, ignoring end msg)" << endl; + } + } + + if ( dbresponse.response ) + dbMsgPort.reply(m, *dbresponse.response, dbresponse.responseTo); + } + + } + catch ( AssertionException& ) { + problem() << "AssertionException in connThread, closing client connection" << endl; + dbMsgPort.shutdown(); + } + catch ( SocketException& ) { + problem() << "SocketException in connThread, closing client connection" << endl; + dbMsgPort.shutdown(); + } + catch ( std::exception &e ) { + problem() << "Uncaught std::exception: " << e.what() << ", terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + catch ( ... ) { + problem() << "Uncaught exception, terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + + // any thread cleanup can happen here + + if ( currentClient.get() ) + currentClient->shutdown(); + globalScriptEngine->threadDone(); + } + + + void msg(const char *m, const char *address, int port, int extras = 0) { + + SockAddr db(address, port); + +// SockAddr db("127.0.0.1", DBPort); +// SockAddr db("192.168.37.1", MessagingPort::DBPort); +// SockAddr db("10.0.21.60", MessagingPort::DBPort); +// SockAddr db("172.16.0.179", MessagingPort::DBPort); + + MessagingPort p; + if ( !p.connect(db) ) + return; + + const int Loops = 1; + for ( int q = 0; q < Loops; q++ ) { + Message send; + Message response; + + send.setData( dbMsg , m); + int len = send.data->dataLen(); + + for ( int i = 0; i < extras; i++ ) + p.say(/*db, */send); + + Timer t; + bool ok = p.call(send, response); + double tm = ((double) t.micros()) + 1; + out() << " ****ok. response.data:" << ok << " time:" << tm / 1000.0 << "ms " << + ((double) len) * 8 / 1000000 / (tm/1000000) << "Mbps" << endl; + if ( q+1 < Loops ) { + out() << "\t\tSLEEP 8 then sending again as a test" << endl; + sleepsecs(8); + } + } + sleepsecs(1); + + p.shutdown(); + } + + void msg(const char *m, int extras = 0) { + msg(m, "127.0.0.1", CmdLine::DefaultDBPort, extras); + } + + bool shouldRepairDatabases = 0; + bool forceRepair = 0; + + bool doDBUpgrade( const string& dbName , string errmsg , MDFHeader * h ){ + static DBDirectClient db; + + if ( h->version == 4 && h->versionMinor == 4 ){ + assert( VERSION == 4 ); + assert( VERSION_MINOR == 5 ); + + list<string> colls = db.getCollectionNames( dbName ); + for ( list<string>::iterator i=colls.begin(); i!=colls.end(); i++){ + string c = *i; + log() << "\t upgrading collection:" << c << endl; + BSONObj out; + bool ok = db.runCommand( dbName , BSON( "reIndex" << c.substr( dbName.size() + 1 ) ) , out ); + if ( ! ok ){ + errmsg = "reindex failed"; + log() << "\t\t reindex failed: " << out << endl; + return false; + } + } + + h->versionMinor = 5; + return true; + } + + // do this in the general case + return repairDatabase( dbName.c_str(), errmsg ); + } + + void repairDatabases() { + log(1) << "enter repairDatabases" << endl; + dblock lk; + vector< string > dbNames; + getDatabaseNames( dbNames ); + for ( vector< string >::iterator i = dbNames.begin(); i != dbNames.end(); ++i ) { + string dbName = *i; + log(1) << "\t" << dbName << endl; + assert( !setClient( dbName.c_str() ) ); + MongoDataFile *p = cc().database()->getFile( 0 ); + MDFHeader *h = p->getHeader(); + if ( !h->currentVersion() || forceRepair ) { + log() << "****" << endl; + log() << "****" << endl; + log() << "need to upgrade database " << dbName << " with pdfile version " << h->version << "." << h->versionMinor << ", " + << "new version: " << VERSION << "." << VERSION_MINOR << endl; + if ( shouldRepairDatabases ){ + // QUESTION: Repair even if file format is higher version than code? + log() << "\t starting upgrade" << endl; + string errmsg; + assert( doDBUpgrade( dbName , errmsg , h ) ); + } + else { + log() << "\t Not upgrading, exiting!" << endl; + log() << "\t run --upgrade to upgrade dbs, then start again" << endl; + log() << "****" << endl; + dbexit( EXIT_NEED_UPGRADE ); + shouldRepairDatabases = 1; + return; + } + } else { + closeDatabase( dbName.c_str() ); + } + } + + log(1) << "done repairDatabases" << endl; + + if ( shouldRepairDatabases ){ + log() << "finished checking dbs" << endl; + cc().shutdown(); + dbexit( EXIT_CLEAN ); + } + } + + void clearTmpFiles() { + boost::filesystem::path path( dbpath ); + for ( boost::filesystem::directory_iterator i( path ); + i != boost::filesystem::directory_iterator(); ++i ) { + string fileName = boost::filesystem::path(*i).leaf(); + if ( boost::filesystem::is_directory( *i ) && + fileName.length() > 2 && fileName.substr( 0, 3 ) == "tmp" ) + boost::filesystem::remove_all( *i ); + } + } + + void clearTmpCollections() { + vector< string > toDelete; + DBDirectClient cli; + auto_ptr< DBClientCursor > c = cli.query( "local.system.namespaces", Query( fromjson( "{name:/^local.temp./}" ) ) ); + while( c->more() ) { + BSONObj o = c->next(); + toDelete.push_back( o.getStringField( "name" ) ); + } + for( vector< string >::iterator i = toDelete.begin(); i != toDelete.end(); ++i ) { + log() << "Dropping old temporary collection: " << *i << endl; + cli.dropCollection( *i ); + } + } + + /** + * does background async flushes of mmapped files + */ + class DataFileSync : public BackgroundJob { + public: + void run(){ + log(1) << "will flush memory every: " << _sleepsecs << " seconds" << endl; + while ( ! inShutdown() ){ + if ( _sleepsecs == 0 ){ + // in case at some point we add an option to change at runtime + sleepsecs(5); + continue; + } + sleepmillis( (int)(_sleepsecs * 1000) ); + MemoryMappedFile::flushAll( false ); + log(1) << "flushing mmmap" << endl; + } + } + + double _sleepsecs; // default value controlled by program options + } dataFileSync; + + void show_32_warning(){ +#if BOOST_VERSION < 103500 + cout << "\nwarning: built with boost version <= 1.34, limited concurrency" << endl; +#endif + + if ( sizeof(int*) != 4 ) + return; + cout << endl; + cout << "** NOTE: when using MongoDB 32 bit, you are limited to about 2 gigabytes of data" << endl; + cout << "** see http://blog.mongodb.org/post/137788967/32-bit-limitations for more" << endl; + cout << endl; + } + + Timer startupSrandTimer; + + void _initAndListen(int listenPort, const char *appserverLoc = null) { + +#if !defined(_WIN32) + pid_t pid = 0; + pid = getpid(); +#else + int pid=0; +#endif + + bool is32bit = sizeof(int*) == 4; + + log() << "Mongo DB : starting : pid = " << pid << " port = " << cmdLine.port << " dbpath = " << dbpath + << " master = " << master << " slave = " << (int) slave << " " << ( is32bit ? "32" : "64" ) << "-bit " << endl; + + show_32_warning(); + + stringstream ss; + ss << "dbpath (" << dbpath << ") does not exist"; + massert( 10296 , ss.str().c_str(), boost::filesystem::exists( dbpath ) ); + + acquirePathLock(); + remove_all( dbpath + "/_tmp/" ); + + theFileAllocator().start(); + + BOOST_CHECK_EXCEPTION( clearTmpFiles() ); + + Client::initThread("initandlisten"); + + clearTmpCollections(); + + _diaglog.init(); + + Module::initAll(); + +#if 0 + { + stringstream indexpath; + indexpath << dbpath << "/indexes.dat"; + RecCache::tempStore.init(indexpath.str().c_str(), BucketSize); + } +#endif + + if ( useJNI ) { + ScriptEngine::setup(); + } + + repairDatabases(); + + /* we didn't want to pre-open all fiels for the repair check above. for regular + operation we do for read/write lock concurrency reasons. + */ + Database::_openAllFiles = true; + + if ( shouldRepairDatabases ) + return; + + /* this is for security on certain platforms (nonce generation) */ + srand((unsigned) (curTimeMicros() ^ startupSrandTimer.micros())); + + listen(listenPort); + + // listen() will return when exit code closes its socket. + while( 1 ) + sleepsecs( 100 ); + } + void initAndListen(int listenPort, const char *appserverLoc = null) { + try { _initAndListen(listenPort, appserverLoc); } + catch ( std::exception &e ) { + problem() << "exception in initAndListen std::exception: " << e.what() << ", terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + catch ( int& n ){ + problem() << "exception in initAndListen int: " << n << ", terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + catch(...) { + log() << " exception in initAndListen, terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + } + + #if defined(_WIN32) + bool initService() { + ServiceController::reportStatus( SERVICE_RUNNING ); + initAndListen( cmdLine.port, appsrvPath ); + return true; + } + #endif + +} // namespace mongo + + +using namespace mongo; + +#include <boost/program_options.hpp> + +namespace po = boost::program_options; + + +void show_help_text(po::options_description options) { + show_32_warning(); + cout << options << endl; +}; + +/* Return error string or "" if no errors. */ +string arg_error_check(int argc, char* argv[]) { + for (int i = 1; i < argc; i++) { + string s = argv[i]; + /* check for inclusion of old-style arbiter setting. */ + if (s == "--pairwith") { + if (argc > i + 2) { + string old_arbiter = argv[i + 2]; + if (old_arbiter == "-" || old_arbiter.substr(0, 1) != "-") { + return "Specifying arbiter using --pairwith is no longer supported, please use --arbiter"; + } + } + } + } + return ""; +} + +int main(int argc, char* argv[], char *envp[] ) +{ + getcurns = ourgetns; + + po::options_description general_options("General options"); + po::options_description replication_options("Replication options"); + po::options_description sharding_options("Sharding options"); + po::options_description visible_options("Allowed options"); + po::options_description hidden_options("Hidden options"); + po::options_description cmdline_options("Command line options"); + + po::positional_options_description positional_options; + + general_options.add_options() + ("help,h", "show this usage information") + ("version", "show version information") + ("config,f", po::value<string>(), "configuration file specifying additional options") + ("port", po::value<int>(&cmdLine.port)/*->default_value(CmdLine::DefaultDBPort)*/, "specify port number") + ("bind_ip", po::value<string>(&bind_ip), + "local ip address to bind listener - all local ips bound by default") + ("verbose,v", "be more verbose (include multiple times for more verbosity e.g. -vvvvv)") + ("dbpath", po::value<string>()->default_value("/data/db/"), "directory for datafiles") + ("quiet", "quieter output") + ("logpath", po::value<string>() , "file to send all output to instead of stdout" ) + ("logappend" , "appnd to logpath instead of over-writing" ) +#ifndef _WIN32 + ("fork" , "fork server process" ) +#endif + ("cpu", "periodically show cpu and iowait utilization") + ("noauth", "run without security") + ("auth", "run with security") + ("objcheck", "inspect client data for validity on receipt") + ("quota", "enable db quota management") + ("quotaFiles", po::value<int>(), "number of files allower per db, requires --quota") + ("appsrvpath", po::value<string>(), "root directory for the babble app server") + ("nocursors", "diagnostic/debugging option") + ("nohints", "ignore query hints") + ("nohttpinterface", "disable http interface") + ("noscripting", "disable scripting engine") + ("noprealloc", "disable data file preallocation") + ("smallfiles", "use a smaller default file size") + ("nssize", po::value<int>()->default_value(16), ".ns file size (in MB) for new databases") + ("diaglog", po::value<int>(), "0=off 1=W 2=R 3=both 7=W+some reads") + ("sysinfo", "print some diagnostic system information") + ("upgrade", "upgrade db if needed") + ("repair", "run repair on all dbs") + ("notablescan", "do not allow table scans") + ("syncdelay",po::value<double>(&dataFileSync._sleepsecs)->default_value(60), "seconds between disk syncs (0 for never)") + ("profile",po::value<int>(), "0=off 1=slow, 2=all") + ("slowms",po::value<int>(&cmdLine.slowMS)->default_value(100), "value of slow for profile and console log" ) + ("maxConns",po::value<int>(), "max number of simultaneous connections") +#if defined(_WIN32) + ("install", "install mongodb service") + ("remove", "remove mongodb service") + ("service", "start mongodb service") +#endif + ; + + replication_options.add_options() + ("master", "master mode") + ("slave", "slave mode") + ("source", po::value<string>(), "when slave: specify master as <server:port>") + ("only", po::value<string>(), "when slave: specify a single database to replicate") + ("pairwith", po::value<string>(), "address of server to pair with") + ("arbiter", po::value<string>(), "address of arbiter server") + ("autoresync", "automatically resync if slave data is stale") + ("oplogSize", po::value<long>(), "size limit (in MB) for op log") + ("opIdMem", po::value<long>(), "size limit (in bytes) for in memory storage of op ids") + ; + + sharding_options.add_options() + ("configsvr", "declare this is a config db of a cluster") + ("shardsvr", "declare this is a shard db of a cluster") + ; + + hidden_options.add_options() + ("command", po::value< vector<string> >(), "command") + ("cacheSize", po::value<long>(), "cache size (in MB) for rec store") + ; + + /* support for -vv -vvvv etc. */ + for (string s = "vv"; s.length() <= 10; s.append("v")) { + hidden_options.add_options()(s.c_str(), "verbose"); + } + + positional_options.add("command", 3); + visible_options.add(general_options); + visible_options.add(replication_options); + visible_options.add(sharding_options); + Module::addOptions( visible_options ); + cmdline_options.add(visible_options); + cmdline_options.add(hidden_options); + + setupSignals(); + + dbExecCommand = argv[0]; + + srand(curTimeMicros()); + boost::filesystem::path::default_name_check( boost::filesystem::no_check ); + + { + unsigned x = 0x12345678; + unsigned char& b = (unsigned char&) x; + if ( b != 0x78 ) { + out() << "big endian cpus not yet supported" << endl; + return 33; + } + } + + DEV out() << "DEV is defined (using _DEBUG), which is slower...\n"; + + UnitTest::runTests(); + + if (argc == 1) { + cout << dbExecCommand << " --help for help and startup options" << endl; + } + + { + bool installService = false; + bool removeService = false; + bool startService = false; + po::variables_map params; + + string error_message = arg_error_check(argc, argv); + if (error_message != "") { + cout << error_message << endl << endl; + show_help_text(visible_options); + return 0; + } + + /* don't allow guessing - creates ambiguities when some options are + * prefixes of others. allow long disguises and don't allow guessing + * to get away with our vvvvvvv trick. */ + int command_line_style = (((po::command_line_style::unix_style ^ + po::command_line_style::allow_guessing) | + po::command_line_style::allow_long_disguise) ^ + po::command_line_style::allow_sticky); + + try { + po::store(po::command_line_parser(argc, argv).options(cmdline_options). + positional(positional_options). + style(command_line_style).run(), params); + + if (params.count("config")) { + ifstream config_file (params["config"].as<string>().c_str()); + if (config_file.is_open()) { + po::store(po::parse_config_file(config_file, cmdline_options), params); + config_file.close(); + } else { + cout << "ERROR: could not read from config file" << endl << endl; + cout << visible_options << endl; + return 0; + } + } + + po::notify(params); + } catch (po::error &e) { + cout << "ERROR: " << e.what() << endl << endl; + cout << visible_options << endl; + return 0; + } + + if (params.count("help")) { + show_help_text(visible_options); + return 0; + } + if (params.count("version")) { + cout << mongodVersion() << endl; + printGitVersion(); + return 0; + } + dbpath = params["dbpath"].as<string>(); + if (params.count("quiet")) { + cmdLine.quiet = true; + } + if (params.count("verbose")) { + logLevel = 1; + } + for (string s = "vv"; s.length() <= 10; s.append("v")) { + if (params.count(s)) { + logLevel = s.length(); + } + } + if (params.count("cpu")) { + cmdLine.cpu = true; + } + if (params.count("noauth")) { + noauth = true; + } + if (params.count("auth")) { + noauth = false; + } + if (params.count("quota")) { + cmdLine.quota = true; + } + if (params.count("quotaFiles")) { + cmdLine.quota = true; + cmdLine.quotaFiles = params["quotaFiles"].as<int>() - 1; + } + if (params.count("objcheck")) { + objcheck = true; + } + if (params.count("appsrvpath")) { + /* casting away the const-ness here */ + appsrvPath = (char*)(params["appsrvpath"].as<string>().c_str()); + } +#ifndef _WIN32 + if (params.count("fork")) { + if ( ! params.count( "logpath" ) ){ + cout << "--fork has to be used with --logpath" << endl; + return -1; + } + pid_t c = fork(); + if ( c ){ + cout << "forked process: " << c << endl; + ::exit(0); + } + setsid(); + setupSignals(); + } +#endif + if (params.count("logpath")) { + string lp = params["logpath"].as<string>(); + uassert( 10033 , "logpath has to be non-zero" , lp.size() ); + initLogging( lp , params.count( "logappend" ) ); + } + if (params.count("nocursors")) { + useCursors = false; + } + if (params.count("nohints")) { + useHints = false; + } + if (params.count("nohttpinterface")) { + noHttpInterface = true; + } + if (params.count("noscripting")) { + useJNI = false; + } + if (params.count("noprealloc")) { + cmdLine.prealloc = false; + } + if (params.count("smallfiles")) { + cmdLine.smallfiles = true; + } + if (params.count("diaglog")) { + int x = params["diaglog"].as<int>(); + if ( x < 0 || x > 7 ) { + out() << "can't interpret --diaglog setting" << endl; + dbexit( EXIT_BADOPTIONS ); + } + _diaglog.level = x; + } + if (params.count("sysinfo")) { + sysRuntimeInfo(); + return 0; + } + if (params.count("repair")) { + shouldRepairDatabases = 1; + forceRepair = 1; + } + if (params.count("upgrade")) { + shouldRepairDatabases = 1; + } + if (params.count("notablescan")) { + cmdLine.notablescan = true; + } + if (params.count("install")) { + installService = true; + } + if (params.count("remove")) { + removeService = true; + } + if (params.count("service")) { + startService = true; + } + if (params.count("master")) { + master = true; + } + if (params.count("slave")) { + slave = SimpleSlave; + } + if (params.count("autoresync")) { + autoresync = true; + } + if (params.count("source")) { + /* specifies what the source in local.sources should be */ + cmdLine.source = params["source"].as<string>().c_str(); + } + if (params.count("only")) { + cmdLine.only = params["only"].as<string>().c_str(); + } + if (params.count("pairwith")) { + string paired = params["pairwith"].as<string>(); + if (params.count("arbiter")) { + string arbiter = params["arbiter"].as<string>(); + pairWith(paired.c_str(), arbiter.c_str()); + } else { + pairWith(paired.c_str(), "-"); + } + } else if (params.count("arbiter")) { + uasserted(10999,"specifying --arbiter without --pairwith"); + } + if( params.count("nssize") ) { + int x = params["nssize"].as<int>(); + uassert( 10034 , "bad --nssize arg", x > 0 && x <= (0x7fffffff/1024/1024)); + lenForNewNsFiles = x * 1024 * 1024; + assert(lenForNewNsFiles > 0); + } + if (params.count("oplogSize")) { + long x = params["oplogSize"].as<long>(); + uassert( 10035 , "bad --oplogSize arg", x > 0); + cmdLine.oplogSize = x * 1024 * 1024; + assert(cmdLine.oplogSize > 0); + } + if (params.count("opIdMem")) { + long x = params["opIdMem"].as<long>(); + uassert( 10036 , "bad --opIdMem arg", x > 0); + opIdMem = x; + assert(opIdMem > 0); + } + if (params.count("cacheSize")) { + long x = params["cacheSize"].as<long>(); + uassert( 10037 , "bad --cacheSize arg", x > 0); + setRecCacheSize(x); + } + if (params.count("port") == 0 ) { + if( params.count("configsvr") ) { + cmdLine.port = CmdLine::ConfigServerPort; + } + if( params.count("shardsvr") ) + cmdLine.port = CmdLine::ShardServerPort; + } + if ( params.count("configsvr" ) && params.count( "diaglog" ) == 0 ){ + _diaglog.level = 1; + } + if ( params.count( "profile" ) ){ + cmdLine.defaultProfile = params["profile"].as<int>(); + } + if ( params.count( "maxConns" ) ){ + int newSize = params["maxConns"].as<int>(); + uassert( 12507 , "maxConns has to be at least 5" , newSize >= 5 ); + uassert( 12508 , "maxConns can't be greater than 10000000" , newSize < 10000000 ); + connTicketHolder.resize( newSize ); + } + + Module::configAll( params ); + dataFileSync.go(); + + if (params.count("command")) { + vector<string> command = params["command"].as< vector<string> >(); + + if (command[0].compare("msg") == 0) { + const char *m; + + if (command.size() < 3) { + cout << "Too few parameters to 'msg' command" << endl; + cout << visible_options << endl; + return 0; + } + + m = command[1].c_str(); + + msg(m, "127.0.0.1", atoi(command[2].c_str())); + return 0; + } + if (command[0].compare("run") == 0) { + if (command.size() > 1) { + cout << "Too many parameters to 'run' command" << endl; + cout << visible_options << endl; + return 0; + } + + initAndListen(cmdLine.port); + return 0; + } + + if (command[0].compare("dbpath") == 0) { + cout << dbpath << endl; + return 0; + } + + cout << "Invalid command: " << command[0] << endl; + cout << visible_options << endl; + return 0; + } + +#if defined(_WIN32) + if ( installService ) { + if ( !ServiceController::installService( L"MongoDB", L"Mongo DB", L"Mongo DB Server", argc, argv ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + dbexit( EXIT_CLEAN ); + } + else if ( removeService ) { + if ( !ServiceController::removeService( L"MongoDB" ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + dbexit( EXIT_CLEAN ); + } + else if ( startService ) { + if ( !ServiceController::startService( L"MongoDB", mongo::initService ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + dbexit( EXIT_CLEAN ); + } +#endif + } + + initAndListen(cmdLine.port, appsrvPath); + dbexit(EXIT_CLEAN); + return 0; +} + +namespace mongo { + + /* we do not use log() below as it uses a mutex and that could cause deadlocks. + */ + + string getDbContext(); + +#undef out + + void exitCleanly() { + goingAway = true; + killCurrentOp.killAll(); + { + dblock lk; + log() << "now exiting" << endl; + dbexit( EXIT_KILL ); + } + } + +#if !defined(_WIN32) + +} // namespace mongo + +#include <signal.h> +#include <string.h> + +namespace mongo { + + void pipeSigHandler( int signal ) { +#ifdef psignal + psignal( signal, "Signal Received : "); +#else + cout << "got pipe signal:" << signal << endl; +#endif + } + + void abruptQuit(int x) { + ostringstream ossSig; + ossSig << "Got signal: " << x << " (" << strsignal( x ) << ")." << endl; + rawOut( ossSig.str() ); + + /* + ostringstream ossOp; + ossOp << "Last op: " << currentOp.infoNoauth() << endl; + rawOut( ossOp.str() ); + */ + + ostringstream oss; + oss << "Backtrace:" << endl; + printStackTrace( oss ); + rawOut( oss.str() ); + dbexit( EXIT_ABRUBT ); + } + + sigset_t asyncSignals; + // The above signals will be processed by this thread only, in order to + // ensure the db and log mutexes aren't held. + void interruptThread() { + int x; + sigwait( &asyncSignals, &x ); + log() << "got kill or ctrl c signal " << x << " (" << strsignal( x ) << "), will terminate after current cmd ends" << endl; + exitCleanly(); + } + + void setupSignals() { + assert( signal(SIGSEGV, abruptQuit) != SIG_ERR ); + assert( signal(SIGFPE, abruptQuit) != SIG_ERR ); + assert( signal(SIGABRT, abruptQuit) != SIG_ERR ); + assert( signal(SIGBUS, abruptQuit) != SIG_ERR ); + assert( signal(SIGPIPE, pipeSigHandler) != SIG_ERR ); + assert( signal(SIGUSR1 , rotateLogs ) != SIG_ERR ); + + setupSIGTRAPforGDB(); + + sigemptyset( &asyncSignals ); + sigaddset( &asyncSignals, SIGINT ); + sigaddset( &asyncSignals, SIGTERM ); + assert( pthread_sigmask( SIG_SETMASK, &asyncSignals, 0 ) == 0 ); + boost::thread it( interruptThread ); + } + +#else +void ctrlCTerminate() { + log() << "got kill or ctrl c signal, will terminate after current cmd ends" << endl; + exitCleanly(); +} +BOOL CtrlHandler( DWORD fdwCtrlType ) +{ + switch( fdwCtrlType ) + { + case CTRL_C_EVENT: + rawOut("Ctrl-C signal\n"); + ctrlCTerminate(); + return( TRUE ); + case CTRL_CLOSE_EVENT: + rawOut("CTRL_CLOSE_EVENT signal\n"); + ctrlCTerminate(); + return( TRUE ); + case CTRL_BREAK_EVENT: + rawOut("CTRL_BREAK_EVENT signal\n"); + ctrlCTerminate(); + return TRUE; + case CTRL_LOGOFF_EVENT: + rawOut("CTRL_LOGOFF_EVENT signal (ignored)\n"); + return FALSE; + case CTRL_SHUTDOWN_EVENT: + rawOut("CTRL_SHUTDOWN_EVENT signal (ignored)\n"); + return FALSE; + default: + return FALSE; + } +} + + void setupSignals() { + if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) ) + ; + else + massert( 10297 , "Couldn't register Windows Ctrl-C handler", false); + } +#endif + +void temptestfoo() { + MongoMutex m; + m.lock(); +// m.lock_upgrade(); + m.lock_shared(); +} + + +} // namespace mongo + +#include "recstore.h" +#include "reccache.h" + @@ -0,0 +1,219 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "../util/message.h" +#include "../util/top.h" +#include "boost/version.hpp" +#include "concurrency.h" +#include "pdfile.h" +#include "client.h" + +namespace mongo { + +// void jniCallback(Message& m, Message& out); + + /* Note the limit here is rather arbitrary and is simply a standard. generally the code works + with any object that fits in ram. + + Also note that the server has some basic checks to enforce this limit but those checks are not exhaustive + for example need to check for size too big after + update $push (append) operation + various db.eval() type operations + + Note also we sometimes do work with objects slightly larger - an object in the replication local.oplog + could be slightly larger. + */ + const int MaxBSONObjectSize = 4 * 1024 * 1024; + + /** + * class to hold path + dbname -> Database + * might be able to optimizer further + */ + class DatabaseHolder { + public: + DatabaseHolder() : _size(0){ + } + + Database * get( const string& ns , const string& path ){ + dbMutex.assertAtLeastReadLocked(); + map<string,Database*>& m = _paths[path]; + + string db = _todb( ns ); + + map<string,Database*>::iterator it = m.find(db); + if ( it != m.end() ) + return it->second; + return 0; + } + + void put( const string& ns , const string& path , Database * db ){ + dbMutex.assertWriteLocked(); + map<string,Database*>& m = _paths[path]; + Database*& d = m[_todb(ns)]; + if ( ! d ) + _size++; + d = db; + } + + void erase( const string& ns , const string& path ){ + dbMutex.assertWriteLocked(); + map<string,Database*>& m = _paths[path]; + _size -= m.erase( _todb( ns ) ); + } + + bool closeAll( const string& path , BSONObjBuilder& result ); + + int size(){ + return _size; + } + + /** + * gets all unique db names, ignoring paths + */ + void getAllShortNames( set<string>& all ) const{ + dbMutex.assertAtLeastReadLocked(); + for ( map<string, map<string,Database*> >::const_iterator i=_paths.begin(); i!=_paths.end(); i++ ){ + map<string,Database*> m = i->second; + for( map<string,Database*>::const_iterator j=m.begin(); j!=m.end(); j++ ){ + all.insert( j->first ); + } + } + } + + private: + + string _todb( const string& ns ){ + size_t i = ns.find( '.' ); + if ( i == string::npos ) + return ns; + return ns.substr( 0 , i ); + } + + map<string, map<string,Database*> > _paths; + int _size; + + }; + + extern DatabaseHolder dbHolder; + + /* returns true if the database ("database") did not exist, and it was created on this call + path - datafiles directory, if not the default, so we can differentiate between db's of the same + name in different places (for example temp ones on repair). + */ + inline bool setClient(const char *ns, const string& path , mongolock *lock ) { + if( logLevel > 5 ) + log() << "setClient: " << ns << endl; + + dbMutex.assertAtLeastReadLocked(); + + Client& c = cc(); + c.top.clientStart( ns ); + + Database * db = dbHolder.get( ns , path ); + if ( db ){ + c.setns(ns, db ); + return false; + } + + if( lock ) + lock->releaseAndWriteLock(); + + assertInWriteLock(); + + char cl[256]; + nsToDatabase(ns, cl); + bool justCreated; + Database *newdb = new Database(cl, justCreated, path); + dbHolder.put(ns,path,newdb); + c.setns(ns, newdb); + + newdb->finishInit(); + + return justCreated; + } + + // shared functionality for removing references to a database from this program instance + // does not delete the files on disk + void closeDatabase( const char *cl, const string& path = dbpath ); + + struct dbtemprelease { + string clientname; + string clientpath; + int locktype; + dbtemprelease() { + Client& client = cc(); + Database *database = client.database(); + if ( database ) { + clientname = database->name; + clientpath = database->path; + } + client.top.clientStop(); + locktype = dbMutex.getState(); + assert( locktype ); + if ( locktype > 0 ) { + massert( 10298 , "can't temprelease nested write lock", locktype == 1); + dbMutex.unlock(); + } + else { + massert( 10299 , "can't temprelease nested read lock", locktype == -1); + dbMutex.unlock_shared(); + } + } + ~dbtemprelease() { + if ( locktype > 0 ) + dbMutex.lock(); + else + dbMutex.lock_shared(); + if ( clientname.empty() ) + cc().setns("", 0); + else + setClient(clientname.c_str(), clientpath.c_str()); + } + }; + + /** + only does a temp release if we're not nested and have a lock + */ + struct dbtempreleasecond { + dbtemprelease * real; + int locktype; + + dbtempreleasecond(){ + real = 0; + locktype = dbMutex.getState(); + if ( locktype == 1 || locktype == -1 ) + real = new dbtemprelease(); + } + + ~dbtempreleasecond(){ + if ( real ){ + delete real; + real = 0; + } + } + + }; + + extern TicketHolder connTicketHolder; + + +} // namespace mongo + +//#include "dbinfo.h" +#include "concurrency.h" diff --git a/db/db.rc b/db/db.rc new file mode 100644 index 0000000..fbfd379 --- /dev/null +++ b/db/db.rc @@ -0,0 +1,61 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+// #include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE 9, 1
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/db/db.sln b/db/db.sln new file mode 100644 index 0000000..35fd85f --- /dev/null +++ b/db/db.sln @@ -0,0 +1,57 @@ +
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongod", "db.vcproj", "{215B2D68-0A70-4D10-8E75-B31010C62A91}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{4082881B-EB00-486F-906C-843B8EC06E18}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dbtests", "dbtests", "{C72EBEDD-342D-4371-8B0D-D7505902FA69}"
+ ProjectSection(SolutionItems) = preProject
+ ..\dbtests\btreetests.cpp = ..\dbtests\btreetests.cpp
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shell", "shell", "{2CABB3B8-C9A6-478D-9463-0B37799ED708}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{2B262D59-9DC7-4BF1-A431-1BD4966899A5}"
+ ProjectSection(SolutionItems) = preProject
+ ..\tools\bridge.cpp = ..\tools\bridge.cpp
+ ..\tools\export.cpp = ..\tools\export.cpp
+ ..\tools\files.cpp = ..\tools\files.cpp
+ ..\tools\sniffer.cpp = ..\tools\sniffer.cpp
+ ..\tools\tool.cpp = ..\tools\tool.cpp
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongos", "..\s\dbgrid.vcproj", "{E03717ED-69B4-4D21-BC55-DF6690B585C6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\dbtests\test.vcproj", "{215B2D68-0A70-4D10-8E75-B33010C62A91}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug Recstore|Win32 = Debug Recstore|Win32
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug|Win32.ActiveCfg = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug|Win32.Build.0 = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.ActiveCfg = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.Build.0 = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.Build.0 = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.ActiveCfg = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.Build.0 = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug|Win32.ActiveCfg = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug|Win32.Build.0 = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Release|Win32.ActiveCfg = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/db/db.vcproj b/db/db.vcproj new file mode 100644 index 0000000..6dc0aae --- /dev/null +++ b/db/db.vcproj @@ -0,0 +1,1891 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongod"
+ ProjectGUID="{215B2D68-0A70-4D10-8E75-B31010C62A91}"
+ RootNamespace="db"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ UseOfATL="0"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib Psapi.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ IgnoreAllDefaultLibraries="false"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="OLDJS;STATIC_JS_API;XP_WIN;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="release_nojni|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0";"c:\program files\java\jdk\include";"c:\program files\java\jdk\include\win32""
+ PreprocessorDefinitions="NOJNI;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug Recstore|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ UseOfATL="0"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="_RECSTORE;OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ IgnoreAllDefaultLibraries="false"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="misc and third party"
+ >
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\config\auto_link.hpp"
+ >
+ </File>
+ <File
+ RelativePath=".\db.rc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\js\js\Debug\js.lib"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\js\js\Release\js.lib"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="C:\Program Files\Java\jdk\lib\jvm.lib"
+ >
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcrecpp.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcrecpp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\SConstruct"
+ >
+ </File>
+ <File
+ RelativePath="..\targetver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\version.hpp"
+ >
+ </File>
+ <Filter
+ Name="pcre"
+ >
+ <File
+ RelativePath="..\pcre-7.4\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre.h"
+ >
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_chartables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_compile.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_config.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_dfa_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_fullinfo.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_get.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_globals.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_info.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_maketables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_newline.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_ord2utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_refcount.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_scanner.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_stringpiece.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_study.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_tables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_try_flipped.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_ucp_searchfuncs.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_valid_utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_version.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_xclass.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcreposix.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="storage related"
+ >
+ <File
+ RelativePath=".\rec.h"
+ >
+ </File>
+ <File
+ RelativePath=".\reccache.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\reccache.h"
+ >
+ </File>
+ <File
+ RelativePath=".\reci.h"
+ >
+ </File>
+ <File
+ RelativePath=".\recstore.h"
+ >
+ </File>
+ <File
+ RelativePath=".\storage.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\storage.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\client\connpool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\connpool.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\model.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\quorum.cpp"
+ >
+ </File>
+ <Filter
+ Name="btree related"
+ >
+ <File
+ RelativePath=".\btree.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\btree.h"
+ >
+ </File>
+ <File
+ RelativePath=".\btreecursor.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="db"
+ >
+ <File
+ RelativePath=".\client.h"
+ >
+ </File>
+ <File
+ RelativePath=".\clientcursor.h"
+ >
+ </File>
+ <File
+ RelativePath=".\cmdline.h"
+ >
+ </File>
+ <File
+ RelativePath=".\commands.h"
+ >
+ </File>
+ <File
+ RelativePath=".\concurrency.h"
+ >
+ </File>
+ <File
+ RelativePath=".\curop.h"
+ >
+ </File>
+ <File
+ RelativePath=".\cursor.h"
+ >
+ </File>
+ <File
+ RelativePath=".\database.h"
+ >
+ </File>
+ <File
+ RelativePath=".\db.h"
+ >
+ </File>
+ <File
+ RelativePath=".\dbhelpers.h"
+ >
+ </File>
+ <File
+ RelativePath=".\dbinfo.h"
+ >
+ </File>
+ <File
+ RelativePath=".\dbmessage.h"
+ >
+ </File>
+ <File
+ RelativePath=".\introspect.h"
+ >
+ </File>
+ <File
+ RelativePath=".\jsobj.h"
+ >
+ </File>
+ <File
+ RelativePath=".\json.h"
+ >
+ </File>
+ <File
+ RelativePath=".\matcher.h"
+ >
+ </File>
+ <File
+ RelativePath="..\grid\message.h"
+ >
+ </File>
+ <File
+ RelativePath=".\minilex.h"
+ >
+ </File>
+ <File
+ RelativePath=".\namespace.h"
+ >
+ </File>
+ <File
+ RelativePath=".\pdfile.h"
+ >
+ </File>
+ <File
+ RelativePath="..\grid\protocol.h"
+ >
+ </File>
+ <File
+ RelativePath=".\query.h"
+ >
+ </File>
+ <File
+ RelativePath=".\queryoptimizer.h"
+ >
+ </File>
+ <File
+ RelativePath=".\queryutil.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\repl.h"
+ >
+ </File>
+ <File
+ RelativePath=".\replset.h"
+ >
+ </File>
+ <File
+ RelativePath=".\resource.h"
+ >
+ </File>
+ <File
+ RelativePath=".\scanandorder.h"
+ >
+ </File>
+ <File
+ RelativePath=".\security.h"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.h"
+ >
+ </File>
+ <Filter
+ Name="cpp"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\client.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\clientcursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\cloner.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\cursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\s\d_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\database.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\db.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbcommands.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbcommands_admin.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbeval.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbhelpers.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbstats.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\extsort.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\index.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\instance.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\introspect.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\jsobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\json.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\lasterror.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\matcher.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap_win.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\modules\mms.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\module.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\mr.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\namespace.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\nonce.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\parallel.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\pdfile.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\query.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\queryoptimizer.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\repl.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\security.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\security_commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\tests.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\top.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\update.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="util"
+ >
+ <File
+ RelativePath="..\util\assert_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\builder.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\file.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\goodies.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\hashtab.h"
+ >
+ </File>
+ <File
+ RelativePath=".\lasterror.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\log.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\lruishmap.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.hpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\miniwebserver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\sock.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\unittest.h"
+ >
+ </File>
+ <Filter
+ Name="cpp"
+ >
+ <File
+ RelativePath="..\util\assert_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\base64.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\httpclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ PrecompiledHeaderThrough=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\util\md5main.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="2"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\util\message.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\miniwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\ntservice.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\processinfo_win32.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\sock.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\util.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="shard"
+ >
+ <File
+ RelativePath="..\s\d_logic.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="scripting"
+ >
+ <File
+ RelativePath="..\scripting\engine.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\scripting\engine_spidermonkey.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\shell\mongo_vstudio.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/db/db.vcxproj b/db/db.vcxproj new file mode 100644 index 0000000..878b52c --- /dev/null +++ b/db/db.vcxproj @@ -0,0 +1,489 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug Recstore|Win32">
+ <Configuration>Debug Recstore</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>mongod</ProjectName>
+ <ProjectGuid>{215B2D68-0A70-4D10-8E75-B31010C62A91}</ProjectGuid>
+ <RootNamespace>db</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <UseOfAtl>false</UseOfAtl>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <UseOfAtl>false</UseOfAtl>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.21006.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">true</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>OLDJS;STATIC_JS_API;XP_WIN;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_RECSTORE;OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="..\pcre-7.4\pcrecpp.h" />
+ <ClInclude Include="..\targetver.h" />
+ <ClInclude Include="..\pcre-7.4\config.h" />
+ <ClInclude Include="..\pcre-7.4\pcre.h" />
+ <ClInclude Include="rec.h" />
+ <ClInclude Include="reccache.h" />
+ <ClInclude Include="reci.h" />
+ <ClInclude Include="recstore.h" />
+ <ClInclude Include="storage.h" />
+ <ClInclude Include="..\client\connpool.h" />
+ <ClInclude Include="..\client\dbclient.h" />
+ <ClInclude Include="..\client\model.h" />
+ <ClInclude Include="btree.h" />
+ <ClInclude Include="client.h" />
+ <ClInclude Include="clientcursor.h" />
+ <ClInclude Include="cmdline.h" />
+ <ClInclude Include="commands.h" />
+ <ClInclude Include="concurrency.h" />
+ <ClInclude Include="curop.h" />
+ <ClInclude Include="cursor.h" />
+ <ClInclude Include="database.h" />
+ <ClInclude Include="db.h" />
+ <ClInclude Include="dbhelpers.h" />
+ <ClInclude Include="dbinfo.h" />
+ <ClInclude Include="dbmessage.h" />
+ <ClInclude Include="introspect.h" />
+ <ClInclude Include="jsobj.h" />
+ <ClInclude Include="json.h" />
+ <ClInclude Include="matcher.h" />
+ <ClInclude Include="..\grid\message.h" />
+ <ClInclude Include="minilex.h" />
+ <ClInclude Include="namespace.h" />
+ <ClInclude Include="pdfile.h" />
+ <ClInclude Include="..\grid\protocol.h" />
+ <ClInclude Include="query.h" />
+ <ClInclude Include="queryoptimizer.h" />
+ <ClInclude Include="repl.h" />
+ <ClInclude Include="replset.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="scanandorder.h" />
+ <ClInclude Include="security.h" />
+ <ClInclude Include="..\stdafx.h" />
+ <ClInclude Include="..\util\builder.h" />
+ <ClInclude Include="..\util\file.h" />
+ <ClInclude Include="..\util\goodies.h" />
+ <ClInclude Include="..\util\hashtab.h" />
+ <ClInclude Include="lasterror.h" />
+ <ClInclude Include="..\util\log.h" />
+ <ClInclude Include="..\util\lruishmap.h" />
+ <ClInclude Include="..\util\md5.h" />
+ <ClInclude Include="..\util\md5.hpp" />
+ <ClInclude Include="..\util\miniwebserver.h" />
+ <ClInclude Include="..\util\mmap.h" />
+ <ClInclude Include="..\util\sock.h" />
+ <ClInclude Include="..\util\unittest.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="db.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <CustomBuild Include="..\..\js\js\Release\js.lib">
+ <FileType>Document</FileType>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+ </CustomBuild>
+ <CustomBuild Include="..\..\js\js\Debug\js.lib">
+ <FileType>Document</FileType>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+ </CustomBuild>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\pcre-7.4\pcrecpp.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_chartables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_compile.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_config.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_dfa_exec.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_exec.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_fullinfo.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_get.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_globals.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_info.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_maketables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_newline.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_ord2utf8.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_refcount.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_scanner.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_stringpiece.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_study.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_tables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_try_flipped.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_ucp_searchfuncs.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_valid_utf8.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_version.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_xclass.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcreposix.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="reccache.cpp" />
+ <ClCompile Include="storage.cpp" />
+ <ClCompile Include="..\client\connpool.cpp" />
+ <ClCompile Include="..\client\dbclient.cpp" />
+ <ClCompile Include="btree.cpp" />
+ <ClCompile Include="btreecursor.cpp" />
+ <ClCompile Include="queryutil.cpp" />
+ <ClCompile Include="client.cpp" />
+ <ClCompile Include="clientcursor.cpp" />
+ <ClCompile Include="cloner.cpp" />
+ <ClCompile Include="commands.cpp" />
+ <ClCompile Include="cursor.cpp" />
+ <ClCompile Include="..\s\d_util.cpp" />
+ <ClCompile Include="db.cpp" />
+ <ClCompile Include="dbcommands.cpp" />
+ <ClCompile Include="dbeval.cpp" />
+ <ClCompile Include="dbhelpers.cpp" />
+ <ClCompile Include="dbinfo.cpp" />
+ <ClCompile Include="dbwebserver.cpp" />
+ <ClCompile Include="extsort.cpp" />
+ <ClCompile Include="instance.cpp" />
+ <ClCompile Include="introspect.cpp" />
+ <ClCompile Include="jsobj.cpp" />
+ <ClCompile Include="json.cpp" />
+ <ClCompile Include="lasterror.cpp" />
+ <ClCompile Include="matcher.cpp" />
+ <ClCompile Include="..\util\mmap_win.cpp" />
+ <ClCompile Include="modules\mms.cpp" />
+ <ClCompile Include="module.cpp" />
+ <ClCompile Include="mr.cpp" />
+ <ClCompile Include="namespace.cpp" />
+ <ClCompile Include="nonce.cpp" />
+ <ClCompile Include="..\client\parallel.cpp" />
+ <ClCompile Include="pdfile.cpp" />
+ <ClCompile Include="query.cpp" />
+ <ClCompile Include="queryoptimizer.cpp" />
+ <ClCompile Include="repl.cpp" />
+ <ClCompile Include="security.cpp" />
+ <ClCompile Include="security_commands.cpp" />
+ <ClCompile Include="..\stdafx.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="tests.cpp" />
+ <ClCompile Include="update.cpp" />
+ <ClCompile Include="..\util\assert_util.cpp" />
+ <ClCompile Include="..\util\background.cpp" />
+ <ClCompile Include="..\util\base64.cpp" />
+ <ClCompile Include="..\util\httpclient.cpp" />
+ <ClCompile Include="..\util\md5.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeaderFile>
+ </ClCompile>
+ <ClCompile Include="..\util\md5main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\util\message.cpp" />
+ <ClCompile Include="..\util\miniwebserver.cpp" />
+ <ClCompile Include="..\util\mmap.cpp" />
+ <ClCompile Include="..\util\ntservice.cpp" />
+ <ClCompile Include="..\util\processinfo_none.cpp" />
+ <ClCompile Include="..\util\sock.cpp" />
+ <ClCompile Include="..\util\util.cpp" />
+ <ClCompile Include="..\s\d_logic.cpp" />
+ <ClCompile Include="..\scripting\engine.cpp" />
+ <ClCompile Include="..\scripting\engine_spidermonkey.cpp" />
+ <ClCompile Include="..\shell\mongo_vstudio.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\SConstruct" />
+ <None Include="ClassDiagram1.cd" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/db/db_10.sln b/db/db_10.sln new file mode 100644 index 0000000..76e8fe9 --- /dev/null +++ b/db/db_10.sln @@ -0,0 +1,45 @@ +
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongod", "db.vcxproj", "{215B2D68-0A70-4D10-8E75-B31010C62A91}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongos", "..\s\dbgrid.vcxproj", "{E03717ED-69B4-4D21-BC55-DF6690B585C6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\dbtests\test.vcxproj", "{215B2D68-0A70-4D10-8E75-B33010C62A91}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug Recstore|Win32 = Debug Recstore|Win32
+ Debug|Win32 = Debug|Win32
+ release_nojni|Win32 = release_nojni|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug|Win32.ActiveCfg = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug|Win32.Build.0 = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.release_nojni|Win32.ActiveCfg = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.ActiveCfg = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.Build.0 = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.Build.0 = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.release_nojni|Win32.ActiveCfg = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.release_nojni|Win32.Build.0 = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.ActiveCfg = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.Build.0 = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug Recstore|Win32.ActiveCfg = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug Recstore|Win32.Build.0 = Debug Recstore|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug|Win32.ActiveCfg = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Debug|Win32.Build.0 = Debug|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.release_nojni|Win32.ActiveCfg = release_nojni|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.release_nojni|Win32.Build.0 = release_nojni|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Release|Win32.ActiveCfg = Release|Win32
+ {215B2D68-0A70-4D10-8E75-B33010C62A91}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/db/dbcommands.cpp b/db/dbcommands.cpp new file mode 100644 index 0000000..ff072a1 --- /dev/null +++ b/db/dbcommands.cpp @@ -0,0 +1,1465 @@ +// dbcommands.cpp + +/** +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "query.h" +#include "pdfile.h" +#include "jsobj.h" +#include "../util/builder.h" +#include <time.h> +#include "introspect.h" +#include "btree.h" +#include "../util/lruishmap.h" +#include "../util/md5.hpp" +#include "../util/processinfo.h" +#include "json.h" +#include "repl.h" +#include "replset.h" +#include "commands.h" +#include "db.h" +#include "instance.h" +#include "lasterror.h" +#include "security.h" +#include "queryoptimizer.h" +#include "../scripting/engine.h" +#include "dbstats.h" + +namespace mongo { + + TicketHolder connTicketHolder( 20000 ); + + extern int otherTraceLevel; + void flushOpLog( stringstream &ss ); + + class CmdShutdown : public Command { + public: + virtual bool requiresAuth() { return true; } + virtual bool adminOnly() { return true; } + virtual bool localHostOnlyIfNoAuth(const BSONObj& cmdObj) { return true; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "shutdown the database. must be ran against admin db and either (1) ran from localhost or (2) authenticated.\n"; + } + CmdShutdown() : Command("shutdown") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + log() << "terminating, shutdown command received" << endl; + dbexit( EXIT_CLEAN ); + return true; + } + } cmdShutdown; + + /* reset any errors so that getlasterror comes back clean. + + useful before performing a long series of operations where we want to + see if any of the operations triggered an error, but don't want to check + after each op as that woudl be a client/server turnaround. + */ + class CmdResetError : public Command { + public: + virtual bool readOnly() { return true; } + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "reset error state (used with getpreverror)"; + } + CmdResetError() : Command("reseterror") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + LastError *le = lastError.get(); + assert( le ); + le->reset(); + return true; + } + } cmdResetError; + + /* for diagnostic / testing purposes. */ + class CmdSleep : public Command { + public: + virtual bool readOnly() { return true; } + virtual bool adminOnly() { return true; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "internal / make db block for 100 seconds"; + } + CmdSleep() : Command("sleep") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + sleepsecs(100); + return true; + } + } cmdSleep; + + class CmdGetLastError : public Command { + public: + virtual bool readOnly() { return true; } + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "return error status of the last operation"; + } + CmdGetLastError() : Command("getlasterror") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + LastError *le = lastError.disableForCommand(); + if ( le->nPrev != 1 ) + LastError::noError.appendSelf( result ); + else + le->appendSelf( result ); + + if ( cmdObj["fsync"].trueValue() ){ + log() << "fsync from getlasterror" << endl; + result.append( "fsyncFiles" , MemoryMappedFile::flushAll( true ) ); + } + + return true; + } + } cmdGetLastError; + + /* for testing purposes only */ + class CmdForceError : public Command { + public: + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + CmdForceError() : Command("forceerror") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + uassert( 10038 , "forced error", false); + return true; + } + } cmdForceError; + + class CmdGetPrevError : public Command { + public: + virtual bool readOnly() { return true; } + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual void help( stringstream& help ) const { + help << "check for errors since last reseterror commandcal"; + } + virtual bool slaveOk() { + return true; + } + CmdGetPrevError() : Command("getpreverror") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + LastError *le = lastError.disableForCommand(); + le->appendSelf( result ); + if ( le->valid ) + result.append( "nPrev", le->nPrev ); + else + result.append( "nPrev", -1 ); + return true; + } + } cmdGetPrevError; + + class CmdSwitchToClientErrors : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual void help( stringstream& help ) const { + help << "convert to id based errors rather than connection based"; + } + virtual bool slaveOk() { + return true; + } + CmdSwitchToClientErrors() : Command("switchtoclienterrors") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + if ( lastError.getID() ){ + errmsg = "already in client id mode"; + return false; + } + LastError *le = lastError.disableForCommand(); + le->overridenById = true; + result << "ok" << 1; + return true; + } + } cmdSwitchToClientErrors; + + class CmdDropDatabase : public Command { + public: + virtual bool logTheOp() { + return true; + } + virtual void help( stringstream& help ) const { + help << "drop (delete) this database"; + } + virtual bool slaveOk() { + return false; + } + CmdDropDatabase() : Command("dropDatabase") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + BSONElement e = cmdObj.findElement(name); + log() << "dropDatabase " << ns << endl; + int p = (int) e.number(); + if ( p != 1 ) + return false; + dropDatabase(ns); + result.append( "dropped" , ns ); + return true; + } + } cmdDropDatabase; + + class CmdRepairDatabase : public Command { + public: + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "repair database. also compacts. note: slow."; + } + CmdRepairDatabase() : Command("repairDatabase") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + BSONElement e = cmdObj.findElement(name); + log() << "repairDatabase " << ns << endl; + int p = (int) e.number(); + if ( p != 1 ) + return false; + e = cmdObj.findElement( "preserveClonedFilesOnFailure" ); + bool preserveClonedFilesOnFailure = e.isBoolean() && e.boolean(); + e = cmdObj.findElement( "backupOriginalFiles" ); + bool backupOriginalFiles = e.isBoolean() && e.boolean(); + return repairDatabase( ns, errmsg, preserveClonedFilesOnFailure, backupOriginalFiles ); + } + } cmdRepairDatabase; + + /* set db profiling level + todo: how do we handle profiling information put in the db with replication? + sensibly or not? + */ + class CmdProfile : public Command { + public: + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "enable or disable performance profiling"; + } + CmdProfile() : Command("profile") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + BSONElement e = cmdObj.findElement(name); + result.append("was", (double) cc().database()->profile); + int p = (int) e.number(); + bool ok = false; + if ( p == -1 ) + ok = true; + else if ( p >= 0 && p <= 2 ) { + ok = cc().database()->setProfilingLevel( p , errmsg ); + } + + BSONElement slow = cmdObj["slowms"]; + if ( slow.isNumber() ) + cmdLine.slowMS = slow.numberInt(); + + return ok; + } + } cmdProfile; + + class CmdServerStatus : public Command { + public: + virtual bool slaveOk() { + return true; + } + CmdServerStatus() : Command("serverStatus") { + started = time(0); + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + + result.append("uptime",(double) (time(0)-started)); + + { + BSONObjBuilder t; + + unsigned long long last, start, timeLocked; + dbMutex.info().getTimingInfo(start, timeLocked); + last = curTimeMicros64(); + double tt = (double) last-start; + double tl = (double) timeLocked; + t.append("totalTime", tt); + t.append("lockTime", tl); + t.append("ratio", tl/tt); + + result.append( "globalLock" , t.obj() ); + } + + { + + BSONObjBuilder t( result.subobjStart( "mem" ) ); + + ProcessInfo p; + if ( p.supported() ){ + t.append( "resident" , p.getResidentSize() ); + t.append( "virtual" , p.getVirtualMemorySize() ); + t.appendBool( "supported" , true ); + } + else { + result.append( "note" , "not all mem info support on this platform" ); + t.appendBool( "supported" , false ); + } + + t.append( "mapped" , MemoryMappedFile::totalMappedLength() / ( 1024 * 1024 ) ); + + t.done(); + + } + + { + BSONObjBuilder bb( result.subobjStart( "connections" ) ); + bb.append( "current" , connTicketHolder.used() ); + bb.append( "available" , connTicketHolder.available() ); + bb.done(); + } + { + BSONObjBuilder bb( result.subobjStart( "extra_info" ) ); + bb.append("note", "fields vary by platform"); + ProcessInfo p; + p.getExtraInfo(bb); + bb.done(); + } + + result.append( "opcounters" , globalOpCounters.getObj() ); + + return true; + } + time_t started; + } cmdServerStatus; + + /* just to check if the db has asserted */ + class CmdAssertInfo : public Command { + public: + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "check if any asserts have occurred on the server"; + } + CmdAssertInfo() : Command("assertinfo") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + result.appendBool("dbasserted", lastAssert[0].isSet() || lastAssert[1].isSet() || lastAssert[2].isSet()); + result.appendBool("asserted", lastAssert[0].isSet() || lastAssert[1].isSet() || lastAssert[2].isSet() || lastAssert[3].isSet()); + result.append("assert", lastAssert[AssertRegular].toString()); + result.append("assertw", lastAssert[AssertW].toString()); + result.append("assertmsg", lastAssert[AssertMsg].toString()); + result.append("assertuser", lastAssert[AssertUser].toString()); + return true; + } + } cmdAsserts; + + class CmdGetOpTime : public Command { + public: + virtual bool slaveOk() { + return true; + } + CmdGetOpTime() : Command("getoptime") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + result.appendDate("optime", OpTime::now().asDate()); + return true; + } + } cmdgetoptime; + + /* + class Cmd : public Command { + public: + Cmd() : Command("") { } + bool adminOnly() { return true; } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result) { + return true; + } + } cmd; + */ + + class CmdDiagLogging : public Command { + public: + virtual bool slaveOk() { + return true; + } + CmdDiagLogging() : Command("diagLogging") { } + bool adminOnly() { + return true; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + int was = _diaglog.setLevel( cmdObj.firstElement().numberInt() ); + stringstream ss; + flushOpLog( ss ); + out() << ss.str() << endl; + if ( !cmdLine.quiet ) + log() << "CMD: diagLogging set to " << _diaglog.level << " from: " << was << endl; + result.append( "was" , was ); + return true; + } + } cmddiaglogging; + + /* remove bit from a bit array - actually remove its slot, not a clear + note: this function does not work with x == 63 -- that is ok + but keep in mind in the future if max indexes were extended to + exactly 64 it would be a problem + */ + unsigned long long removeBit(unsigned long long b, int x) { + unsigned long long tmp = b; + return + (tmp & ((((unsigned long long) 1) << x)-1)) | + ((tmp >> (x+1)) << x); + } + + struct DBCommandsUnitTest { + DBCommandsUnitTest() { + assert( removeBit(1, 0) == 0 ); + assert( removeBit(2, 0) == 1 ); + assert( removeBit(2, 1) == 0 ); + assert( removeBit(255, 1) == 127 ); + assert( removeBit(21, 2) == 9 ); + assert( removeBit(0x4000000000000001ULL, 62) == 1 ); + } + } dbc_unittest; + + bool deleteIndexes( NamespaceDetails *d, const char *ns, const char *name, string &errmsg, BSONObjBuilder &anObjBuilder, bool mayDeleteIdIndex ) { + + d->aboutToDeleteAnIndex(); + + /* there may be pointers pointing at keys in the btree(s). kill them. */ + ClientCursor::invalidate(ns); + + // delete a specific index or all? + if ( *name == '*' && name[1] == 0 ) { + log(4) << " d->nIndexes was " << d->nIndexes << '\n'; + anObjBuilder.append("nIndexesWas", (double)d->nIndexes); + IndexDetails *idIndex = 0; + if( d->nIndexes ) { + for ( int i = 0; i < d->nIndexes; i++ ) { + if ( !mayDeleteIdIndex && d->idx(i).isIdIndex() ) { + idIndex = &d->idx(i); + } else { + d->idx(i).kill_idx(); + } + } + d->nIndexes = 0; + } + if ( idIndex ) { + d->addIndex(ns) = *idIndex; + wassert( d->nIndexes == 1 ); + } + /* assuming here that id index is not multikey: */ + d->multiKeyIndexBits = 0; + anObjBuilder.append("msg", "all indexes deleted for collection"); + } + else { + // delete just one index + int x = d->findIndexByName(name); + if ( x >= 0 ) { + log(4) << " d->nIndexes was " << d->nIndexes << endl; + anObjBuilder.append("nIndexesWas", (double)d->nIndexes); + + /* note it is important we remove the IndexDetails with this + call, otherwise, on recreate, the old one would be reused, and its + IndexDetails::info ptr would be bad info. + */ + IndexDetails *id = &d->idx(x); + if ( !mayDeleteIdIndex && id->isIdIndex() ) { + errmsg = "may not delete _id index"; + return false; + } + id->kill_idx(); + d->multiKeyIndexBits = removeBit(d->multiKeyIndexBits, x); + d->nIndexes--; + for ( int i = x; i < d->nIndexes; i++ ) + d->idx(i) = d->idx(i+1); + } else { + log() << "deleteIndexes: " << name << " not found" << endl; + errmsg = "index not found"; + return false; + } + } + return true; + } + + /* drop collection */ + class CmdDrop : public Command { + public: + CmdDrop() : Command("drop") { } + virtual bool logTheOp() { + return true; + } + virtual bool slaveOk() { + return false; + } + virtual bool adminOnly() { + return false; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + string nsToDrop = cc().database()->name + '.' + cmdObj.findElement(name).valuestr(); + NamespaceDetails *d = nsdetails(nsToDrop.c_str()); + if ( !cmdLine.quiet ) + log() << "CMD: drop " << nsToDrop << endl; + if ( d == 0 ) { + errmsg = "ns not found"; + return false; + } + uassert( 10039 , "can't drop collection with reserved $ character in name", strchr(nsToDrop.c_str(), '$') == 0 ); + dropCollection( nsToDrop, errmsg, result ); + return true; + } + } cmdDrop; + + /* select count(*) */ + class CmdCount : public Command { + public: + virtual bool readOnly() { return true; } + CmdCount() : Command("count") { } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + // ok on --slave setups, not ok for nonmaster of a repl pair (unless override) + return slave == SimpleSlave; + } + virtual bool slaveOverrideOk() { + return true; + } + virtual bool adminOnly() { + return false; + } + virtual bool run(const char *_ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + string ns = cc().database()->name + '.' + cmdObj.findElement(name).valuestr(); + string err; + long long n = runCount(ns.c_str(), cmdObj, err); + long long nn = n; + bool ok = true; + if ( n == -1 ){ + nn = 0; + result.appendBool( "missing" , true ); + } + else if ( n < 0 ) { + nn = 0; + ok = false; + if ( !err.empty() ) + errmsg = err; + } + result.append("n", (double) nn); + return ok; + } + } cmdCount; + + /* create collection */ + class CmdCreate : public Command { + public: + CmdCreate() : Command("create") { } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return false; + } + virtual bool adminOnly() { + return false; + } + virtual void help( stringstream& help ) const { + help << "create a collection"; + } + virtual bool run(const char *_ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + string ns = cc().database()->name + '.' + cmdObj.findElement(name).valuestr(); + string err; + bool ok = userCreateNS(ns.c_str(), cmdObj, err, true); + if ( !ok && !err.empty() ) + errmsg = err; + return ok; + } + } cmdCreate; + + class CmdDeleteIndexes : public Command { + public: + virtual bool logTheOp() { + return true; + } + virtual bool slaveOk() { + return false; + } + virtual void help( stringstream& help ) const { + help << "delete indexes for a collection"; + } + CmdDeleteIndexes() : Command("deleteIndexes") { } + bool run(const char *ns, BSONObj& jsobj, string& errmsg, BSONObjBuilder& anObjBuilder, bool /*fromRepl*/) { + /* note: temp implementation. space not reclaimed! */ + BSONElement e = jsobj.findElement(name.c_str()); + string toDeleteNs = cc().database()->name + '.' + e.valuestr(); + NamespaceDetails *d = nsdetails(toDeleteNs.c_str()); + if ( !cmdLine.quiet ) + log() << "CMD: deleteIndexes " << toDeleteNs << endl; + if ( d ) { + BSONElement f = jsobj.findElement("index"); + if ( f.type() == String ) { + return deleteIndexes( d, toDeleteNs.c_str(), f.valuestr(), errmsg, anObjBuilder, false ); + } + else { + errmsg = "invalid index name spec"; + return false; + } + } + else { + errmsg = "ns not found"; + return false; + } + } + } cmdDeleteIndexes; + + class CmdReIndex : public Command { + public: + virtual bool logTheOp() { + return true; + } + virtual bool slaveOk() { + return false; + } + virtual void help( stringstream& help ) const { + help << "re-index a collection"; + } + CmdReIndex() : Command("reIndex") { } + bool run(const char *ns, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { + static DBDirectClient db; + + BSONElement e = jsobj.findElement(name.c_str()); + string toDeleteNs = cc().database()->name + '.' + e.valuestr(); + NamespaceDetails *d = nsdetails(toDeleteNs.c_str()); + log() << "CMD: reIndex " << toDeleteNs << endl; + + if ( ! d ){ + errmsg = "ns not found"; + return false; + } + + list<BSONObj> all; + auto_ptr<DBClientCursor> i = db.getIndexes( toDeleteNs ); + BSONObjBuilder b; + while ( i->more() ){ + BSONObj o = i->next().getOwned(); + b.append( BSONObjBuilder::numStr( all.size() ) , o ); + all.push_back( o ); + } + + + bool ok = deleteIndexes( d, toDeleteNs.c_str(), "*" , errmsg, result, true ); + if ( ! ok ){ + errmsg = "deleteIndexes failed"; + return false; + } + + for ( list<BSONObj>::iterator i=all.begin(); i!=all.end(); i++ ){ + BSONObj o = *i; + db.insert( Namespace( toDeleteNs.c_str() ).getSisterNS( "system.indexes" ).c_str() , o ); + } + + result.append( "ok" , 1 ); + result.append( "nIndexes" , (int)all.size() ); + result.appendArray( "indexes" , b.obj() ); + return true; + } + } cmdReIndex; + + + + class CmdListDatabases : public Command { + public: + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + virtual bool slaveOverrideOk() { + return true; + } + virtual bool adminOnly() { + return true; + } + CmdListDatabases() : Command("listDatabases") {} + bool run(const char *ns, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { + vector< string > dbNames; + getDatabaseNames( dbNames ); + vector< BSONObj > dbInfos; + + set<string> seen; + boost::intmax_t totalSize = 0; + for ( vector< string >::iterator i = dbNames.begin(); i != dbNames.end(); ++i ) { + BSONObjBuilder b; + b.append( "name", i->c_str() ); + boost::intmax_t size = dbSize( i->c_str() ); + b.append( "sizeOnDisk", (double) size ); + setClient( i->c_str() ); + b.appendBool( "empty", cc().database()->isEmpty() ); + totalSize += size; + dbInfos.push_back( b.obj() ); + + seen.insert( i->c_str() ); + } + + // TODO: erh 1/1/2010 I think this is broken where path != dbpath ?? + set<string> allShortNames; + dbHolder.getAllShortNames( allShortNames ); + for ( set<string>::iterator i = allShortNames.begin(); i != allShortNames.end(); i++ ){ + string name = *i; + + if ( seen.count( name ) ) + continue; + + BSONObjBuilder b; + b << "name" << name << "sizeOnDisk" << double( 1 ); + setClient( name.c_str() ); + b.appendBool( "empty", cc().database()->isEmpty() ); + + dbInfos.push_back( b.obj() ); + } + + result.append( "databases", dbInfos ); + result.append( "totalSize", double( totalSize ) ); + return true; + } + } cmdListDatabases; + + class CmdCloseAllDatabases : public Command { + public: + virtual bool adminOnly() { return true; } + virtual bool slaveOk() { return false; } + CmdCloseAllDatabases() : Command( "closeAllDatabases" ) {} + bool run(const char *ns, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { + return dbHolder.closeAll( dbpath , result ); + } + } cmdCloseAllDatabases; + + class CmdFileMD5 : public Command { + public: + CmdFileMD5() : Command( "filemd5" ){} + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << " example: { filemd5 : ObjectId(aaaaaaa) , key : { ts : 1 } }"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + static DBDirectClient db; + + string ns = nsToDatabase( dbname ); + ns += "."; + { + string root = jsobj.getStringField( "root" ); + if ( root.size() == 0 ) + root = "fs"; + ns += root; + } + ns += ".chunks"; // make this an option in jsobj + + BSONObjBuilder query; + query.appendAs( jsobj["filemd5"] , "files_id" ); + Query q( query.obj() ); + q.sort( BSON( "files_id" << 1 << "n" << 1 ) ); + + md5digest d; + md5_state_t st; + md5_init(&st); + + dbtemprelease temp; + + auto_ptr<DBClientCursor> cursor = db.query( ns.c_str() , q ); + int n = 0; + while ( cursor->more() ){ + BSONObj c = cursor->next(); + int myn = c.getIntField( "n" ); + if ( n != myn ){ + log() << "should have chunk: " << n << " have:" << myn << endl; + uassert( 10040 , "chunks out of order" , n == myn ); + } + + int len; + const char * data = c["data"].binData( len ); + md5_append( &st , (const md5_byte_t*)(data + 4) , len - 4 ); + + n++; + } + md5_finish(&st, d); + + result.append( "md5" , digestToString( d ) ); + return true; + } + } cmdFileMD5; + + IndexDetails *cmdIndexDetailsForRange( const char *ns, string &errmsg, BSONObj &min, BSONObj &max, BSONObj &keyPattern ) { + if ( ns[ 0 ] == '\0' || min.isEmpty() || max.isEmpty() ) { + errmsg = "invalid command syntax (note: min and max are required)"; + return 0; + } + return indexDetailsForRange( ns, errmsg, min, max, keyPattern ); + } + + class CmdMedianKey : public Command { + public: + CmdMedianKey() : Command( "medianKey" ) {} + virtual bool slaveOk() { return true; } + virtual void help( stringstream &help ) const { + help << " example: { medianKey:\"blog.posts\", keyPattern:{x:1}, min:{x:10}, max:{x:55} }\n" + "NOTE: This command may take awhile to run"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + const char *ns = jsobj.getStringField( "medianKey" ); + BSONObj min = jsobj.getObjectField( "min" ); + BSONObj max = jsobj.getObjectField( "max" ); + BSONObj keyPattern = jsobj.getObjectField( "keyPattern" ); + + IndexDetails *id = cmdIndexDetailsForRange( ns, errmsg, min, max, keyPattern ); + if ( id == 0 ) + return false; + + Timer t; + int num = 0; + NamespaceDetails *d = nsdetails(ns); + int idxNo = d->idxNo(*id); + for( BtreeCursor c( d, idxNo, *id, min, max, false, 1 ); c.ok(); c.advance(), ++num ); + num /= 2; + BtreeCursor c( d, idxNo, *id, min, max, false, 1 ); + for( ; num; c.advance(), --num ); + int ms = t.millis(); + if ( ms > cmdLine.slowMS ) { + out() << "Finding median for index: " << keyPattern << " between " << min << " and " << max << " took " << ms << "ms." << endl; + } + + if ( !c.ok() ) { + errmsg = "no index entries in the specified range"; + return false; + } + + result.append( "median", c.prettyKey( c.currKey() ) ); + return true; + } + } cmdMedianKey; + + class CmdDatasize : public Command { + public: + CmdDatasize() : Command( "datasize" ) {} + virtual bool slaveOk() { return true; } + virtual void help( stringstream &help ) const { + help << + "\ndetermine data size for a set of data in a certain range" + "\nexample: { datasize:\"blog.posts\", keyPattern:{x:1}, min:{x:10}, max:{x:55} }" + "\nkeyPattern, min, and max parameters are optional." + "\nnot: This command may take a while to run"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + const char *ns = jsobj.getStringField( "datasize" ); + BSONObj min = jsobj.getObjectField( "min" ); + BSONObj max = jsobj.getObjectField( "max" ); + BSONObj keyPattern = jsobj.getObjectField( "keyPattern" ); + + auto_ptr< Cursor > c; + if ( min.isEmpty() && max.isEmpty() ) { + setClient( ns ); + c = theDataFileMgr.findAll( ns ); + } else if ( min.isEmpty() || max.isEmpty() ) { + errmsg = "only one of min or max specified"; + return false; + } else { + IndexDetails *idx = cmdIndexDetailsForRange( ns, errmsg, min, max, keyPattern ); + if ( idx == 0 ) + return false; + NamespaceDetails *d = nsdetails(ns); + c.reset( new BtreeCursor( d, d->idxNo(*idx), *idx, min, max, false, 1 ) ); + } + + Timer t; + long long size = 0; + long long numObjects = 0; + while( c->ok() ) { + size += c->current().objsize(); + c->advance(); + numObjects++; + } + int ms = t.millis(); + if ( ms > cmdLine.slowMS ) { + if ( min.isEmpty() ) { + out() << "Finding size for ns: " << ns << " took " << ms << "ms." << endl; + } else { + out() << "Finding size for ns: " << ns << " between " << min << " and " << max << " took " << ms << "ms." << endl; + } + } + + result.append( "size", (double)size ); + result.append( "numObjects" , (double)numObjects ); + return true; + } + } cmdDatasize; + + class CollectionStats : public Command { + public: + CollectionStats() : Command( "collstats" ) {} + virtual bool slaveOk() { return true; } + virtual void help( stringstream &help ) const { + help << " example: { collstats:\"blog.posts\" } "; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + string ns = dbname; + if ( ns.find( "." ) != string::npos ) + ns = ns.substr( 0 , ns.find( "." ) ); + ns += "."; + ns += jsobj.firstElement().valuestr(); + + NamespaceDetails * nsd = nsdetails( ns.c_str() ); + if ( ! nsd ){ + errmsg = "ns not found"; + return false; + } + + result.append( "ns" , ns.c_str() ); + + result.append( "count" , nsd->nrecords ); + result.append( "size" , nsd->datasize ); + result.append( "storageSize" , nsd->storageSize() ); + result.append( "nindexes" , nsd->nIndexes ); + + if ( nsd->capped ){ + result.append( "capped" , nsd->capped ); + result.append( "max" , nsd->max ); + } + + return true; + } + } cmdCollectionStatis; + + class CmdBuildInfo : public Command { + public: + CmdBuildInfo() : Command( "buildinfo" ) {} + virtual bool slaveOk() { return true; } + virtual bool adminOnly() { return true; } + virtual void help( stringstream &help ) const { + help << "example: { buildinfo:1 }"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + result << "version" << versionString << "gitVersion" << gitVersion() << "sysInfo" << sysInfo(); + result << "bits" << ( sizeof( int* ) == 4 ? 32 : 64 ); + return true; + } + } cmdBuildInfo; + + class CmdCloneCollectionAsCapped : public Command { + public: + CmdCloneCollectionAsCapped() : Command( "cloneCollectionAsCapped" ) {} + virtual bool slaveOk() { return false; } + virtual void help( stringstream &help ) const { + help << "example: { cloneCollectionAsCapped:<fromName>, toCollection:<toName>, size:<sizeInBytes> }"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + string from = jsobj.getStringField( "cloneCollectionAsCapped" ); + string to = jsobj.getStringField( "toCollection" ); + long long size = (long long)jsobj.getField( "size" ).number(); + + if ( from.empty() || to.empty() || size == 0 ) { + errmsg = "invalid command spec"; + return false; + } + + char realDbName[256]; + nsToDatabase( dbname, realDbName ); + + string fromNs = string( realDbName ) + "." + from; + string toNs = string( realDbName ) + "." + to; + massert( 10300 , "source collection " + fromNs + " does not exist", !setClient( fromNs.c_str() ) ); + NamespaceDetails *nsd = nsdetails( fromNs.c_str() ); + massert( 10301 , "source collection " + fromNs + " does not exist", nsd ); + long long excessSize = nsd->datasize - size * 2; + DiskLoc extent = nsd->firstExtent; + for( ; excessSize > 0 && extent != nsd->lastExtent; extent = extent.ext()->xnext ) { + excessSize -= extent.ext()->length; + if ( excessSize > 0 ) + log( 2 ) << "cloneCollectionAsCapped skipping extent of size " << extent.ext()->length << endl; + log( 6 ) << "excessSize: " << excessSize << endl; + } + DiskLoc startLoc = extent.ext()->firstRecord; + + CursorId id; + { + auto_ptr< Cursor > c = theDataFileMgr.findAll( fromNs.c_str(), startLoc ); + ClientCursor *cc = new ClientCursor(); + cc->c = c; + cc->ns = fromNs; + cc->matcher.reset( new CoveredIndexMatcher( BSONObj(), fromjson( "{$natural:1}" ) ) ); + id = cc->cursorid; + } + + DBDirectClient client; + setClient( toNs.c_str() ); + BSONObjBuilder spec; + spec.appendBool( "capped", true ); + spec.append( "size", double( size ) ); + if ( !userCreateNS( toNs.c_str(), spec.done(), errmsg, true ) ) + return false; + + auto_ptr< DBClientCursor > c = client.getMore( fromNs, id ); + while( c->more() ) { + BSONObj obj = c->next(); + theDataFileMgr.insertAndLog( toNs.c_str(), obj, true ); + } + + return true; + } + } cmdCloneCollectionAsCapped; + + class CmdConvertToCapped : public Command { + public: + CmdConvertToCapped() : Command( "convertToCapped" ) {} + virtual bool slaveOk() { return false; } + virtual void help( stringstream &help ) const { + help << "example: { convertToCapped:<fromCollectionName>, size:<sizeInBytes> }"; + } + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + string from = jsobj.getStringField( "convertToCapped" ); + long long size = (long long)jsobj.getField( "size" ).number(); + + if ( from.empty() || size == 0 ) { + errmsg = "invalid command spec"; + return false; + } + + char realDbName[256]; + nsToDatabase( dbname, realDbName ); + + DBDirectClient client; + client.dropCollection( string( realDbName ) + "." + from + ".$temp_convertToCapped" ); + + BSONObj info; + if ( !client.runCommand( realDbName, + BSON( "cloneCollectionAsCapped" << from << "toCollection" << ( from + ".$temp_convertToCapped" ) << "size" << double( size ) ), + info ) ) { + errmsg = "cloneCollectionAsCapped failed: " + string(info); + return false; + } + + if ( !client.dropCollection( string( realDbName ) + "." + from ) ) { + errmsg = "failed to drop original collection"; + return false; + } + + if ( !client.runCommand( "admin", + BSON( "renameCollection" << ( string( realDbName ) + "." + from + ".$temp_convertToCapped" ) << "to" << ( string( realDbName ) + "." + from ) ), + info ) ) { + errmsg = "renameCollection failed: " + string(info); + return false; + } + + return true; + } + } cmdConvertToCapped; + + class GroupCommand : public Command { + public: + GroupCommand() : Command("group"){} + virtual bool slaveOk() { return true; } + virtual void help( stringstream &help ) const { + help << "see http://www.mongodb.org/display/DOCS/Aggregation"; + } + + BSONObj getKey( const BSONObj& obj , const BSONObj& keyPattern , ScriptingFunction func , double avgSize , Scope * s ){ + if ( func ){ + BSONObjBuilder b( obj.objsize() + 32 ); + b.append( "0" , obj ); + int res = s->invoke( func , b.obj() ); + uassert( 10041 , (string)"invoke failed in $keyf: " + s->getError() , res == 0 ); + int type = s->type("return"); + uassert( 10042 , "return of $key has to be an object" , type == Object ); + return s->getObject( "return" ); + } + return obj.extractFields( keyPattern , true ); + } + + bool group( string realdbname , auto_ptr<DBClientCursor> cursor , + BSONObj keyPattern , string keyFunctionCode , string reduceCode , const char * reduceScope , + BSONObj initial , string finalize , + string& errmsg , BSONObjBuilder& result ){ + + + auto_ptr<Scope> s = globalScriptEngine->getPooledScope( realdbname ); + s->localConnect( realdbname.c_str() ); + + if ( reduceScope ) + s->init( reduceScope ); + + s->setObject( "$initial" , initial , true ); + + s->exec( "$reduce = " + reduceCode , "reduce setup" , false , true , true , 100 ); + s->exec( "$arr = [];" , "reduce setup 2" , false , true , true , 100 ); + ScriptingFunction f = s->createFunction( + "function(){ " + " if ( $arr[n] == null ){ " + " next = {}; " + " Object.extend( next , $key ); " + " Object.extend( next , $initial , true ); " + " $arr[n] = next; " + " next = null; " + " } " + " $reduce( obj , $arr[n] ); " + "}" ); + + ScriptingFunction keyFunction = 0; + if ( keyFunctionCode.size() ){ + keyFunction = s->createFunction( keyFunctionCode.c_str() ); + } + + + double keysize = keyPattern.objsize() * 3; + double keynum = 1; + + map<BSONObj,int,BSONObjCmp> map; + list<BSONObj> blah; + + while ( cursor->more() ){ + BSONObj obj = cursor->next(); + BSONObj key = getKey( obj , keyPattern , keyFunction , keysize / keynum , s.get() ); + keysize += key.objsize(); + keynum++; + + int& n = map[key]; + if ( n == 0 ){ + n = map.size(); + s->setObject( "$key" , key , true ); + + uassert( 10043 , "group() can't handle more than 10000 unique keys" , n <= 10000 ); + } + + s->setObject( "obj" , obj , true ); + s->setNumber( "n" , n - 1 ); + if ( s->invoke( f , BSONObj() , 0 , true ) ){ + throw UserException( 9010 , (string)"reduce invoke failed: " + s->getError() ); + } + } + + if (!finalize.empty()){ + s->exec( "$finalize = " + finalize , "finalize define" , false , true , true , 100 ); + ScriptingFunction g = s->createFunction( + "function(){ " + " for(var i=0; i < $arr.length; i++){ " + " var ret = $finalize($arr[i]); " + " if (ret !== undefined) " + " $arr[i] = ret; " + " } " + "}" ); + s->invoke( g , BSONObj() , 0 , true ); + } + + result.appendArray( "retval" , s->getObject( "$arr" ) ); + result.append( "count" , keynum - 1 ); + result.append( "keys" , (int)(map.size()) ); + s->exec( "$arr = [];" , "reduce setup 2" , false , true , true , 100 ); + s->gc(); + + return true; + } + + bool run(const char *dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + static DBDirectClient db; + + /* db.$cmd.findOne( { group : <p> } ) */ + const BSONObj& p = jsobj.firstElement().embeddedObjectUserCheck(); + + BSONObj q; + if ( p["cond"].type() == Object ) + q = p["cond"].embeddedObject(); + else if ( p["condition"].type() == Object ) + q = p["condition"].embeddedObject(); + else + q = getQuery( p ); + + string ns = dbname; + ns = ns.substr( 0 , ns.size() - 4 ); + string realdbname = ns.substr( 0 , ns.size() - 1 ); + + if ( p["ns"].type() != String ){ + errmsg = "ns has to be set"; + return false; + } + + ns += p["ns"].valuestr(); + + auto_ptr<DBClientCursor> cursor = db.query( ns , q ); + + BSONObj key; + string keyf; + if ( p["key"].type() == Object ){ + key = p["key"].embeddedObjectUserCheck(); + if ( ! p["$keyf"].eoo() ){ + errmsg = "can't have key and $keyf"; + return false; + } + } + else if ( p["$keyf"].type() ){ + keyf = p["$keyf"].ascode(); + } + else { + // no key specified, will use entire object as key + } + + BSONElement reduce = p["$reduce"]; + if ( reduce.eoo() ){ + errmsg = "$reduce has to be set"; + return false; + } + + BSONElement initial = p["initial"]; + if ( initial.type() != Object ){ + errmsg = "initial has to be an object"; + return false; + } + + + string finalize; + if (p["finalize"].type()) + finalize = p["finalize"].ascode(); + + return group( realdbname , cursor , + key , keyf , reduce.ascode() , reduce.type() != CodeWScope ? 0 : reduce.codeWScopeScopeData() , + initial.embeddedObject() , finalize , + errmsg , result ); + } + + } cmdGroup; + + + class DistinctCommand : public Command { + public: + DistinctCommand() : Command("distinct"){} + virtual bool slaveOk() { return true; } + + virtual void help( stringstream &help ) const { + help << "{ distinct : 'collection name' , key : 'a.b' }"; + } + + bool run(const char *dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + static DBDirectClient db; + + string ns = cc().database()->name + '.' + cmdObj.findElement(name).valuestr(); + string key = cmdObj["key"].valuestrsafe(); + + BSONObj keyPattern = BSON( key << 1 ); + + set<BSONObj,BSONObjCmp> map; + + long long size = 0; + + auto_ptr<DBClientCursor> cursor = db.query( ns , getQuery( cmdObj ) , 0 , 0 , &keyPattern ); + while ( cursor->more() ){ + BSONObj o = cursor->next(); + BSONObj value = o.extractFields( keyPattern ); + if ( value.isEmpty() ) + continue; + if ( map.insert( value ).second ){ + size += o.objsize() + 20; + uassert( 10044 , "distinct too big, 4mb cap" , size < 4 * 1024 * 1024 ); + } + } + + assert( size <= 0x7fffffff ); + BSONObjBuilder b( (int) size ); + int n=0; + for ( set<BSONObj,BSONObjCmp>::iterator i = map.begin() ; i != map.end(); i++ ){ + b.appendAs( i->firstElement() , b.numStr( n++ ).c_str() ); + } + + result.appendArray( "values" , b.obj() ); + + return true; + } + + } distinctCmd; + + /* Find and Modify an object returning either the old (default) or new value*/ + class CmdFindAndModify : public Command { + public: + /* {findandmodify: "collection", query: {processed:false}, update: {$set: {processed:true}}, new: true} + * {findandmodify: "collection", query: {processed:false}, remove: true, sort: {priority:-1}} + * + * either update or remove is required, all other fields have default values + * output is in the "value" field + */ + CmdFindAndModify() : Command("findandmodify") { } + virtual bool logTheOp() { + return false; // the modification will be logged directly + } + virtual bool slaveOk() { + return false; + } + virtual bool run(const char *dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + static DBDirectClient db; + + string ns = nsToDatabase(dbname) + '.' + cmdObj.firstElement().valuestr(); + + Query q (cmdObj.getObjectField("query")); // defaults to {} + BSONElement sort = cmdObj["sort"]; + if (!sort.eoo()) + q.sort(sort.embeddedObjectUserCheck()); + + BSONObj out = db.findOne(ns, q); + if (out.firstElement().eoo()){ + errmsg = "No matching object found"; + return false; + } + + q = QUERY( "_id" << out["_id"]); + + if (cmdObj["remove"].trueValue()){ + uassert(12515, "can't remove and update", cmdObj["update"].eoo()); + db.remove(ns, q, 1); + } else { + BSONElement update = cmdObj["update"]; + uassert(12516, "must specify remove or update", !update.eoo()); + db.update(ns, q, update.embeddedObjectUserCheck()); + + if (cmdObj["new"].trueValue()) + out = db.findOne(ns, q); + } + + result.append("value", out); + + return true; + } + } cmdFindAndModify; + + bool commandIsReadOnly(BSONObj& _cmdobj) { + BSONObj jsobj; + { + BSONElement e = _cmdobj.firstElement(); + if ( e.type() == Object && string("query") == e.fieldName() ) { + jsobj = e.embeddedObject(); + } + else { + jsobj = _cmdobj; + } + } + BSONElement e = jsobj.firstElement(); + if ( ! e.type() ) + return false; + return Command::readOnly( e.fieldName() ); + } + + /* TODO make these all command objects -- legacy stuff here + + usage: + abc.$cmd.findOne( { ismaster:1 } ); + + returns true if ran a cmd + */ + bool _runCommands(const char *ns, BSONObj& _cmdobj, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) { + if( logLevel >= 1 ) + log() << "run command " << ns << ' ' << _cmdobj << endl; + + const char *p = strchr(ns, '.'); + if ( !p ) return false; + if ( strcmp(p, ".$cmd") != 0 ) return false; + + BSONObj jsobj; + { + BSONElement e = _cmdobj.firstElement(); + if ( e.type() == Object && string("query") == e.fieldName() ) { + jsobj = e.embeddedObject(); + } + else { + jsobj = _cmdobj; + } + } + + bool ok = false; + + BSONElement e = jsobj.firstElement(); + + Command * c = e.type() ? Command::findCommand( e.fieldName() ) : 0; + if ( c ){ + string errmsg; + AuthenticationInfo *ai = currentClient.get()->ai; + uassert( 10045 , "unauthorized", ai->isAuthorized(cc().database()->name.c_str()) || !c->requiresAuth()); + + bool admin = c->adminOnly(); + + if( admin && c->localHostOnlyIfNoAuth(jsobj) && noauth && !ai->isLocalHost ) { + ok = false; + errmsg = "unauthorized: this command must run from localhost when running db without auth"; + log() << "command denied: " << jsobj.toString() << endl; + } + else if ( admin && !fromRepl && strncmp(ns, "admin", 5) != 0 ) { + ok = false; + errmsg = "access denied"; + log() << "command denied: " << jsobj.toString() << endl; + } + else if ( isMaster() || + c->slaveOk() || + ( c->slaveOverrideOk() && ( queryOptions & QueryOption_SlaveOk ) ) || + fromRepl ){ + if ( jsobj.getBoolField( "help" ) ) { + stringstream help; + help << "help for: " << e.fieldName() << " "; + c->help( help ); + anObjBuilder.append( "help" , help.str() ); + } + else { + if( admin ) + log( 2 ) << "command: " << jsobj << endl; + try { + ok = c->run(ns, jsobj, errmsg, anObjBuilder, fromRepl); + } + catch ( AssertionException& e ){ + ok = false; + errmsg = "assertion: "; + errmsg += e.what(); + } + if ( ok && c->logTheOp() && !fromRepl ) + logOp("c", ns, jsobj); + } + } + else { + ok = false; + errmsg = "not master"; + } + if ( !ok ) + anObjBuilder.append("errmsg", errmsg); + } + else { + anObjBuilder.append("errmsg", "no such cmd"); + anObjBuilder.append("bad cmd" , _cmdobj ); + } + anObjBuilder.append("ok", ok?1.0:0.0); + BSONObj x = anObjBuilder.done(); + b.append((void*) x.objdata(), x.objsize()); + return true; + } + +} // namespace mongo diff --git a/db/dbcommands_admin.cpp b/db/dbcommands_admin.cpp new file mode 100644 index 0000000..91052bf --- /dev/null +++ b/db/dbcommands_admin.cpp @@ -0,0 +1,356 @@ +// dbcommands_admin.cpp + +/** + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + this file has dbcommands that are for dba type administration + mostly around dbs and collections + NOT system stuff +*/ + + +#include "stdafx.h" +#include "jsobj.h" +#include "pdfile.h" +#include "namespace.h" +#include "commands.h" +#include "cmdline.h" +#include "btree.h" +#include "curop.h" +#include "../util/background.h" + +namespace mongo { + + class CleanCmd : public Command { + public: + CleanCmd() : Command( "clean" ){} + + virtual bool slaveOk(){ return true; } + + bool run(const char *nsRaw, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + string dropns = cc().database()->name + "." + cmdObj.firstElement().valuestrsafe(); + + if ( !cmdLine.quiet ) + log() << "CMD: clean " << dropns << endl; + + NamespaceDetails *d = nsdetails(dropns.c_str()); + + if ( ! d ){ + errmsg = "ns not found"; + return 0; + } + + for ( int i = 0; i < Buckets; i++ ) + d->deletedList[i].Null(); + + result.append("ns", dropns.c_str()); + return 1; + } + + } cleanCmd; + + class ValidateCmd : public Command { + public: + ValidateCmd() : Command( "validate" ){} + + virtual bool slaveOk(){ + return true; + } + + //{ validate: "collectionnamewithoutthedbpart" [, scandata: <bool>] } */ + + bool run(const char *nsRaw, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + string ns = cc().database()->name + "." + cmdObj.firstElement().valuestrsafe(); + NamespaceDetails * d = nsdetails( ns.c_str() ); + if ( !cmdLine.quiet ) + log() << "CMD: validate " << ns << endl; + + if ( ! d ){ + errmsg = "ns not found"; + return 0; + } + + result.append( "ns", ns ); + result.append( "result" , validateNS( ns.c_str() , d, &cmdObj ) ); + return 1; + } + + + string validateNS(const char *ns, NamespaceDetails *d, BSONObj *cmdObj) { + bool scanData = true; + if( cmdObj && cmdObj->hasElement("scandata") && !cmdObj->getBoolField("scandata") ) + scanData = false; + bool valid = true; + stringstream ss; + ss << "\nvalidate\n"; + ss << " details: " << hex << d << " ofs:" << nsindex(ns)->detailsOffset(d) << dec << endl; + if ( d->capped ) + ss << " capped:" << d->capped << " max:" << d->max << '\n'; + + ss << " firstExtent:" << d->firstExtent.toString() << " ns:" << d->firstExtent.ext()->nsDiagnostic.buf << '\n'; + ss << " lastExtent:" << d->lastExtent.toString() << " ns:" << d->lastExtent.ext()->nsDiagnostic.buf << '\n'; + try { + d->firstExtent.ext()->assertOk(); + d->lastExtent.ext()->assertOk(); + + DiskLoc el = d->firstExtent; + int ne = 0; + while( !el.isNull() ) { + Extent *e = el.ext(); + e->assertOk(); + el = e->xnext; + ne++; + killCurrentOp.checkForInterrupt(); + } + ss << " # extents:" << ne << '\n'; + } catch (...) { + valid=false; + ss << " extent asserted "; + } + + ss << " datasize?:" << d->datasize << " nrecords?:" << d->nrecords << " lastExtentSize:" << d->lastExtentSize << '\n'; + ss << " padding:" << d->paddingFactor << '\n'; + try { + + try { + ss << " first extent:\n"; + d->firstExtent.ext()->dump(ss); + valid = valid && d->firstExtent.ext()->validates(); + } + catch (...) { + ss << "\n exception firstextent\n" << endl; + } + + set<DiskLoc> recs; + if( scanData ) { + auto_ptr<Cursor> c = theDataFileMgr.findAll(ns); + int n = 0; + long long len = 0; + long long nlen = 0; + int outOfOrder = 0; + DiskLoc cl_last; + while ( c->ok() ) { + n++; + + DiskLoc cl = c->currLoc(); + if ( n < 1000000 ) + recs.insert(cl); + if ( d->capped ) { + if ( cl < cl_last ) + outOfOrder++; + cl_last = cl; + } + + Record *r = c->_current(); + len += r->lengthWithHeaders; + nlen += r->netLength(); + c->advance(); + } + if ( d->capped ) { + ss << " capped outOfOrder:" << outOfOrder; + if ( outOfOrder > 1 ) { + valid = false; + ss << " ???"; + } + else ss << " (OK)"; + ss << '\n'; + } + ss << " " << n << " objects found, nobj:" << d->nrecords << "\n"; + ss << " " << len << " bytes data w/headers\n"; + ss << " " << nlen << " bytes data wout/headers\n"; + } + + ss << " deletedList: "; + for ( int i = 0; i < Buckets; i++ ) { + ss << (d->deletedList[i].isNull() ? '0' : '1'); + } + ss << endl; + int ndel = 0; + long long delSize = 0; + int incorrect = 0; + for ( int i = 0; i < Buckets; i++ ) { + DiskLoc loc = d->deletedList[i]; + try { + int k = 0; + while ( !loc.isNull() ) { + if ( recs.count(loc) ) + incorrect++; + ndel++; + + if ( loc.questionable() ) { + if( d->capped && !loc.isValid() && i == 1 ) { + /* the constructor for NamespaceDetails intentionally sets deletedList[1] to invalid + see comments in namespace.h + */ + break; + } + + if ( loc.a() <= 0 || strstr(ns, "hudsonSmall") == 0 ) { + ss << " ?bad deleted loc: " << loc.toString() << " bucket:" << i << " k:" << k << endl; + valid = false; + break; + } + } + + DeletedRecord *d = loc.drec(); + delSize += d->lengthWithHeaders; + loc = d->nextDeleted; + k++; + killCurrentOp.checkForInterrupt(); + } + } catch (...) { + ss <<" ?exception in deleted chain for bucket " << i << endl; + valid = false; + } + } + ss << " deleted: n: " << ndel << " size: " << delSize << endl; + if ( incorrect ) { + ss << " ?corrupt: " << incorrect << " records from datafile are in deleted list\n"; + valid = false; + } + + int idxn = 0; + try { + ss << " nIndexes:" << d->nIndexes << endl; + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + IndexDetails& id = i.next(); + ss << " " << id.indexNamespace() << " keys:" << + id.head.btree()->fullValidate(id.head, id.keyPattern()) << endl; + } + } + catch (...) { + ss << "\n exception during index validate idxn:" << idxn << endl; + valid=false; + } + + } + catch (AssertionException) { + ss << "\n exception during validate\n" << endl; + valid = false; + } + + if ( !valid ) + ss << " ns corrupt, requires dbchk\n"; + + return ss.str(); + } + } validateCmd; + + extern bool unlockRequested; + extern unsigned lockedForWriting; + extern boost::mutex lockedForWritingMutex; + +/* + class UnlockCommand : public Command { + public: + UnlockCommand() : Command( "unlock" ) { } + virtual bool readOnly() { return true; } + virtual bool slaveOk(){ return true; } + virtual bool adminOnly(){ return true; } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + if( lockedForWriting ) { + log() << "command: unlock requested" << endl; + errmsg = "unlock requested"; + unlockRequested = true; + } + else { + errmsg = "not locked, so cannot unlock"; + return 0; + } + return 1; + } + + } unlockCommand; +*/ + /* see unlockFsync() for unlocking: + db.$cmd.sys.unlock.findOne() + */ + class FSyncCommand : public Command { + class LockDBJob : public BackgroundJob { + protected: + void run() { + { + boostlock lk(lockedForWritingMutex); + lockedForWriting++; + } + readlock lk(""); + MemoryMappedFile::flushAll(true); + log() << "db is now locked for snapshotting, no writes allowed. use db.$cmd.sys.unlock.findOne() to unlock" << endl; + _ready = true; + while( 1 ) { + if( unlockRequested ) { + unlockRequested = false; + break; + } + sleepmillis(20); + } + { + boostlock lk(lockedForWritingMutex); + lockedForWriting--; + } + } + public: + bool& _ready; + LockDBJob(bool& ready) : _ready(ready) { + deleteSelf = true; + _ready = false; + } + }; + public: + FSyncCommand() : Command( "fsync" ){} + + virtual bool slaveOk(){ return true; } + virtual bool adminOnly(){ return true; } + /*virtual bool localHostOnlyIfNoAuth(const BSONObj& cmdObj) { + string x = cmdObj["exec"].valuestrsafe(); + return !x.empty(); + }*/ + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + /* async means do an fsync, but return immediately */ + bool sync = ! cmdObj["async"].trueValue(); + bool lock = cmdObj["lock"].trueValue(); + log() << "CMD fsync: sync:" << sync << " lock:" << lock << endl; + + if( lock ) { + uassert(12034, "fsync: can't lock while an unlock is pending", !unlockRequested); + uassert(12032, "fsync: sync option must be true when using lock", sync); + /* With releaseEarly(), we must be extremely careful we don't do anything + where we would have assumed we were locked. profiling is one of those things. + Perhaps at profile time we could check if we released early -- however, + we need to be careful to keep that code very fast it's a very common code path when on. + */ + uassert(12033, "fsync: profiling must be off to enter locked mode", cc().database()->profile == 0); + bool ready = false; + LockDBJob *l = new LockDBJob(ready); + dbMutex.releaseEarly(); + l->go(); + // don't return until background thread has acquired the write lock + while( !ready ) { + sleepmillis(10); + } + result.append("info", "now locked against writes, use db.$cmd.sys.unlock.findOne() to unlock"); + } + else { + result.append( "numFiles" , MemoryMappedFile::flushAll( sync ) ); + } + return 1; + } + + } fsyncCmd; + +} + diff --git a/db/dbeval.cpp b/db/dbeval.cpp new file mode 100644 index 0000000..e729135 --- /dev/null +++ b/db/dbeval.cpp @@ -0,0 +1,120 @@ +/* commands.cpp + db "commands" (sent via db.$cmd.findOne(...)) + */ + +/** +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "query.h" +#include "pdfile.h" +#include "jsobj.h" +#include "../util/builder.h" +#include <time.h> +#include "introspect.h" +#include "btree.h" +#include "../util/lruishmap.h" +#include "json.h" +#include "repl.h" +#include "commands.h" +#include "cmdline.h" + +#include "../scripting/engine.h" + +namespace mongo { + + const int edebug=0; + + bool dbEval(const char *ns, BSONObj& cmd, BSONObjBuilder& result, string& errmsg) { + BSONElement e = cmd.firstElement(); + uassert( 10046 , "eval needs Code" , e.type() == Code || e.type() == CodeWScope || e.type() == String ); + + const char *code = 0; + switch ( e.type() ) { + case String: + case Code: + code = e.valuestr(); + break; + case CodeWScope: + code = e.codeWScopeCode(); + break; + default: + assert(0); + } + assert( code ); + + if ( ! globalScriptEngine ) { + errmsg = "db side execution is disabled"; + return false; + } + + auto_ptr<Scope> s = globalScriptEngine->getPooledScope( ns ); + ScriptingFunction f = s->createFunction(code); + if ( f == 0 ) { + errmsg = (string)"compile failed: " + s->getError(); + return false; + } + + if ( e.type() == CodeWScope ) + s->init( e.codeWScopeScopeData() ); + s->localConnect( cc().database()->name.c_str() ); + + BSONObj args; + { + BSONElement argsElement = cmd.findElement("args"); + if ( argsElement.type() == Array ) { + args = argsElement.embeddedObject(); + if ( edebug ) { + out() << "args:" << args.toString() << endl; + out() << "code:\n" << code << endl; + } + } + } + + int res; + { + Timer t; + res = s->invoke(f,args, cmdLine.quota ? 10 * 60 * 1000 : 0 ); + int m = t.millis(); + if ( m > cmdLine.slowMS ) { + out() << "dbeval slow, time: " << dec << m << "ms " << ns << endl; + if ( m >= 1000 ) log() << code << endl; + else OCCASIONALLY log() << code << endl; + } + } + if ( res ) { + result.append("errno", (double) res); + errmsg = "invoke failed: "; + errmsg += s->getError(); + return false; + } + + s->append( result , "retval" , "return" ); + + return true; + } + + class CmdEval : public Command { + public: + virtual bool slaveOk() { + return false; + } + CmdEval() : Command("$eval") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + return dbEval(ns, cmdObj, result, errmsg); + } + } cmdeval; + +} // namespace mongo diff --git a/db/dbhelpers.cpp b/db/dbhelpers.cpp new file mode 100644 index 0000000..ee221ab --- /dev/null +++ b/db/dbhelpers.cpp @@ -0,0 +1,253 @@ +// dbhelpers.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "db.h" +#include "dbhelpers.h" +#include "query.h" +#include "json.h" +#include "queryoptimizer.h" +#include "btree.h" +#include "pdfile.h" + +namespace mongo { + + CursorIterator::CursorIterator( auto_ptr<Cursor> c , BSONObj filter ) + : _cursor( c ){ + if ( ! filter.isEmpty() ) + _matcher.reset( new CoveredIndexMatcher( filter , BSONObj() ) ); + _advance(); + } + + BSONObj CursorIterator::next(){ + BSONObj o = _o; + _advance(); + return o; + } + + bool CursorIterator::hasNext(){ + return ! _o.isEmpty(); + } + + void CursorIterator::_advance(){ + if ( ! _cursor->ok() ){ + _o = BSONObj(); + return; + } + + while ( _cursor->ok() ){ + _o = _cursor->current(); + _cursor->advance(); + if ( _matcher.get() == 0 || _matcher->matches( _o ) ) + return; + } + + _o = BSONObj(); + } + + void Helpers::ensureIndex(const char *ns, BSONObj keyPattern, bool unique, const char *name) { + NamespaceDetails *d = nsdetails(ns); + if( d == 0 ) + return; + + { + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + if( i.next().keyPattern().woCompare(keyPattern) == 0 ) + return; + } + } + + if( d->nIndexes >= NamespaceDetails::NIndexesMax ) { + problem() << "Helper::ensureIndex fails, MaxIndexes exceeded " << ns << '\n'; + return; + } + + string system_indexes = cc().database()->name + ".system.indexes"; + + BSONObjBuilder b; + b.append("name", name); + b.append("ns", ns); + b.append("key", keyPattern); + b.appendBool("unique", unique); + BSONObj o = b.done(); + + theDataFileMgr.insert(system_indexes.c_str(), o.objdata(), o.objsize()); + } + + class FindOne : public QueryOp { + public: + FindOne( bool requireIndex ) : requireIndex_( requireIndex ) {} + virtual void init() { + if ( requireIndex_ && strcmp( qp().indexKey().firstElement().fieldName(), "$natural" ) == 0 ) + throw MsgAssertionException( 9011 , "Not an index cursor" ); + c_ = qp().newCursor(); + if ( !c_->ok() ) + setComplete(); + else + matcher_.reset( new CoveredIndexMatcher( qp().query(), qp().indexKey() ) ); + } + virtual void next() { + if ( !c_->ok() ) { + setComplete(); + return; + } + if ( matcher_->matches( c_->currKey(), c_->currLoc() ) ) { + one_ = c_->current(); + setComplete(); + } else { + c_->advance(); + } + } + virtual bool mayRecordPlan() const { return false; } + virtual QueryOp *clone() const { return new FindOne( requireIndex_ ); } + BSONObj one() const { return one_; } + private: + bool requireIndex_; + auto_ptr< Cursor > c_; + auto_ptr< CoveredIndexMatcher > matcher_; + BSONObj one_; + }; + + /* fetch a single object from collection ns that matches query + set your db SavedContext first + */ + bool Helpers::findOne(const char *ns, BSONObj query, BSONObj& result, bool requireIndex) { + QueryPlanSet s( ns, query, BSONObj(), 0, !requireIndex ); + FindOne original( requireIndex ); + shared_ptr< FindOne > res = s.runOp( original ); + massert( 10302 , res->exceptionMessage(), res->complete() ); + if ( res->one().isEmpty() ) + return false; + result = res->one(); + return true; + } + + auto_ptr<CursorIterator> Helpers::find( const char *ns , BSONObj query , bool requireIndex ){ + uassert( 10047 , "requireIndex not supported in Helpers::find yet" , ! requireIndex ); + auto_ptr<CursorIterator> i; + i.reset( new CursorIterator( DataFileMgr::findAll( ns ) , query ) ); + return i; + } + + + bool Helpers::findById(Client& c, const char *ns, BSONObj query, BSONObj& result , + bool * nsFound , bool * indexFound ){ + Database *database = c.database(); + assert( database ); + NamespaceDetails *d = database->namespaceIndex.details(ns); + if ( ! d ) + return false; + if ( nsFound ) + *nsFound = 1; + + int idxNo = d->findIdIndex(); + if ( idxNo < 0 ) + return false; + if ( indexFound ) + *indexFound = 1; + + IndexDetails& i = d->idx( idxNo ); + + BSONObj key = i.getKeyFromQuery( query ); + + DiskLoc loc = i.head.btree()->findSingle( i , i.head , key ); + if ( loc.isNull() ) + return false; + result = loc.obj(); + return true; + } + + /* Get the first object from a collection. Generally only useful if the collection + only ever has a single object -- which is a "singleton collection. + + Returns: true if object exists. + */ + bool Helpers::getSingleton(const char *ns, BSONObj& result) { + Client::Context context(ns); + + auto_ptr<Cursor> c = DataFileMgr::findAll(ns); + if ( !c->ok() ) + return false; + + result = c->current(); + return true; + } + + void Helpers::putSingleton(const char *ns, BSONObj obj) { + OpDebug debug; + Client::Context context(ns); + updateObjects(ns, obj, /*pattern=*/BSONObj(), /*upsert=*/true, /*multi=*/false , true , debug ); + } + + void Helpers::emptyCollection(const char *ns) { + Client::Context context(ns); + deleteObjects(ns, BSONObj(), false); + } + + DbSet::~DbSet() { + if ( name_.empty() ) + return; + try { + Client::Context c( name_.c_str() ); + if ( nsdetails( name_.c_str() ) ) { + string errmsg; + BSONObjBuilder result; + dropCollection( name_, errmsg, result ); + } + } catch ( ... ) { + problem() << "exception cleaning up DbSet" << endl; + } + } + + void DbSet::reset( const string &name, const BSONObj &key ) { + if ( !name.empty() ) + name_ = name; + if ( !key.isEmpty() ) + key_ = key.getOwned(); + Client::Context c( name_.c_str() ); + if ( nsdetails( name_.c_str() ) ) { + Helpers::emptyCollection( name_.c_str() ); + } else { + string err; + massert( 10303 , err, userCreateNS( name_.c_str(), fromjson( "{autoIndexId:false}" ), err, false ) ); + } + Helpers::ensureIndex( name_.c_str(), key_, true, "setIdx" ); + } + + bool DbSet::get( const BSONObj &obj ) const { + Client::Context c( name_.c_str() ); + BSONObj temp; + return Helpers::findOne( name_.c_str(), obj, temp, true ); + } + + void DbSet::set( const BSONObj &obj, bool val ) { + Client::Context c( name_.c_str() ); + if ( val ) { + try { + BSONObj k = obj; + theDataFileMgr.insert( name_.c_str(), k, false ); + } catch ( DBException& ) { + // dup key - already in set + } + } else { + deleteObjects( name_.c_str(), obj, true, false, false ); + } + } + +} // namespace mongo diff --git a/db/dbhelpers.h b/db/dbhelpers.h new file mode 100644 index 0000000..3c223d8 --- /dev/null +++ b/db/dbhelpers.h @@ -0,0 +1,122 @@ +// dbhelpers.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* db helpers are helper functions and classes that let us easily manipulate the local + database instance. +*/ + +#pragma once + +#include "../stdafx.h" +#include "client.h" +#include "db.h" + +namespace mongo { + + class Cursor; + class CoveredIndexMatcher; + + class CursorIterator { + public: + CursorIterator( auto_ptr<Cursor> c , BSONObj filter = BSONObj() ); + BSONObj next(); + bool hasNext(); + + private: + void _advance(); + + auto_ptr<Cursor> _cursor; + auto_ptr<CoveredIndexMatcher> _matcher; + BSONObj _o; + }; + + /** + all helpers assume locking is handled above them + */ + struct Helpers { + + /* ensure the specified index exists. + + @param keyPattern key pattern, e.g., { ts : 1 } + @param name index name, e.g., "name_1" + + This method can be a little (not much) cpu-slow, so you may wish to use + OCCASIONALLY ensureIndex(...); + + Note: use ensureHaveIdIndex() for the _id index: it is faster. + Note: does nothing if collection does not yet exist. + */ + static void ensureIndex(const char *ns, BSONObj keyPattern, bool unique, const char *name); + + /* fetch a single object from collection ns that matches query. + set your db SavedContext first. + + @param requireIndex if true, complain if no index for the query. a way to guard against + writing a slow query. + + @return true if object found + */ + static bool findOne(const char *ns, BSONObj query, BSONObj& result, bool requireIndex = false); + + + /** + * @param foundIndex if passed in will be set to 1 if ns and index found + * @return true if object found + */ + static bool findById(Client&, const char *ns, BSONObj query, BSONObj& result , + bool * nsFound = 0 , bool * indexFound = 0 ); + + static auto_ptr<CursorIterator> find( const char *ns , BSONObj query = BSONObj() , bool requireIndex = false ); + + /* Get/put the first object from a collection. Generally only useful if the collection + only ever has a single object -- which is a "singleton collection". + + You do not need to set the database before calling. + + Returns: true if object exists. + */ + static bool getSingleton(const char *ns, BSONObj& result); + static void putSingleton(const char *ns, BSONObj obj); + + + /* Remove all objects from a collection. + You do not need to set the database before calling. + */ + static void emptyCollection(const char *ns); + + }; + + class Database; + + // manage a set using collection backed storage + class DbSet { + public: + DbSet( const string &name = "", const BSONObj &key = BSONObj() ) : + name_( name ), + key_( key.getOwned() ) { + } + ~DbSet(); + void reset( const string &name = "", const BSONObj &key = BSONObj() ); + bool get( const BSONObj &obj ) const; + void set( const BSONObj &obj, bool val ); + private: + string name_; + BSONObj key_; + }; + +} // namespace mongo diff --git a/db/dbmessage.h b/db/dbmessage.h new file mode 100644 index 0000000..54a2ac3 --- /dev/null +++ b/db/dbmessage.h @@ -0,0 +1,267 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "storage.h" +#include "jsobj.h" +#include "namespace.h" +#include "../util/message.h" + +namespace mongo { + + /* db response format + + Query or GetMore: // see struct QueryResult + int resultFlags; + int64 cursorID; + int startingFrom; + int nReturned; + list of marshalled JSObjects; + */ + + extern bool objcheck; + +#pragma pack(1) + struct QueryResult : public MsgData { + enum ResultFlagType { + /* returned, with zero results, when getMore is called but the cursor id + is not valid at the server. */ + ResultFlag_CursorNotFound = 1, + + /* { $err : ... } is being returned */ + ResultFlag_ErrSet = 2, + + /* Have to update config from the server, usually $err is also set */ + ResultFlag_ShardConfigStale = 4, + + /* for backward compatability: this let's us know the server supports + the QueryOption_AwaitData option. if it doesn't, a repl slave client should sleep + a little between getMore's. + */ + ResultFlag_AwaitCapable = 8 + }; + + long long cursorId; + int startingFrom; + int nReturned; + const char *data() { + return (char *) (((int *)&nReturned)+1); + } + int resultFlags() { + return dataAsInt(); + } + int& _resultFlags() { + return dataAsInt(); + } + void setResultFlagsToOk() { + _resultFlags() = 0; // ResultFlag_AwaitCapable + } + }; +#pragma pack() + + /* For the database/server protocol, these objects and functions encapsulate + the various messages transmitted over the connection. + */ + + class DbMessage { + public: + DbMessage(const Message& _m) : m(_m) { + theEnd = _m.data->_data + _m.data->dataLen(); + int *r = (int *) _m.data->_data; + reserved = *r; + r++; + data = (const char *) r; + nextjsobj = data; + } + + const char * getns() { + return data; + } + void getns(Namespace& ns) { + ns = data; + } + + + void resetPull(){ + nextjsobj = data; + } + int pullInt() { + if ( nextjsobj == data ) + nextjsobj += strlen(data) + 1; // skip namespace + int i = *((int *)nextjsobj); + nextjsobj += 4; + return i; + } + long long pullInt64() const { + return pullInt64(); + } + long long &pullInt64() { + if ( nextjsobj == data ) + nextjsobj += strlen(data) + 1; // skip namespace + long long &i = *((long long *)nextjsobj); + nextjsobj += 8; + return i; + } + + OID* getOID() { + return (OID *) (data + strlen(data) + 1); // skip namespace + } + + void getQueryStuff(const char *&query, int& ntoreturn) { + int *i = (int *) (data + strlen(data) + 1); + ntoreturn = *i; + i++; + query = (const char *) i; + } + + /* for insert and update msgs */ + bool moreJSObjs() { + return nextjsobj != 0; + } + BSONObj nextJsObj() { + if ( nextjsobj == data ) + nextjsobj += strlen(data) + 1; // skip namespace + massert( 10304 , "Remaining data too small for BSON object", theEnd - nextjsobj > 3 ); + BSONObj js(nextjsobj); + massert( 10305 , "Invalid object size", js.objsize() > 3 ); + massert( 10306 , "Next object larger than available space", + js.objsize() < ( theEnd - data ) ); + if ( objcheck && !js.valid() ) { + massert( 10307 , "bad object in message", false); + } + nextjsobj += js.objsize(); + if ( nextjsobj >= theEnd ) + nextjsobj = 0; + return js; + } + + const Message& msg() { + return m; + } + + void markSet(){ + mark = nextjsobj; + } + + void markReset(){ + nextjsobj = mark; + } + + private: + const Message& m; + int reserved; + const char *data; + const char *nextjsobj; + const char *theEnd; + + const char * mark; + }; + + + /* a request to run a query, received from the database */ + class QueryMessage { + public: + const char *ns; + int ntoskip; + int ntoreturn; + int queryOptions; + BSONObj query; + auto_ptr< FieldMatcher > fields; + + /* parses the message into the above fields */ + QueryMessage(DbMessage& d) { + ns = d.getns(); + ntoskip = d.pullInt(); + ntoreturn = d.pullInt(); + query = d.nextJsObj(); + if ( d.moreJSObjs() ) { + BSONObj o = d.nextJsObj(); + if (!o.isEmpty()){ + fields = auto_ptr< FieldMatcher >(new FieldMatcher() ); + fields->add( o ); + } + } + queryOptions = d.msg().data->dataAsInt(); + } + }; + +} // namespace mongo + +#include "../client/dbclient.h" + +namespace mongo { + + inline void replyToQuery(int queryResultFlags, + AbstractMessagingPort* p, Message& requestMsg, + void *data, int size, + int nReturned, int startingFrom = 0, + long long cursorId = 0 + ) { + BufBuilder b(32768); + b.skip(sizeof(QueryResult)); + b.append(data, size); + QueryResult *qr = (QueryResult *) b.buf(); + qr->_resultFlags() = queryResultFlags; + qr->len = b.len(); + qr->setOperation(opReply); + qr->cursorId = cursorId; + qr->startingFrom = startingFrom; + qr->nReturned = nReturned; + b.decouple(); + Message *resp = new Message(); + resp->setData(qr, true); // transport will free + p->reply(requestMsg, *resp, requestMsg.data->id); + } + +} // namespace mongo + +//#include "bsonobj.h" +#include "instance.h" + +namespace mongo { + + /* object reply helper. */ + inline void replyToQuery(int queryResultFlags, + AbstractMessagingPort* p, Message& requestMsg, + BSONObj& responseObj) + { + replyToQuery(queryResultFlags, + p, requestMsg, + (void *) responseObj.objdata(), responseObj.objsize(), 1); + } + + /* helper to do a reply using a DbResponse object */ + inline void replyToQuery(int queryResultFlags, Message &m, DbResponse &dbresponse, BSONObj obj) { + BufBuilder b; + b.skip(sizeof(QueryResult)); + b.append((void*) obj.objdata(), obj.objsize()); + QueryResult* msgdata = (QueryResult *) b.buf(); + b.decouple(); + QueryResult *qr = msgdata; + qr->_resultFlags() = queryResultFlags; + qr->len = b.len(); + qr->setOperation(opReply); + qr->cursorId = 0; + qr->startingFrom = 0; + qr->nReturned = 1; + Message *resp = new Message(); + resp->setData(msgdata, true); // transport will free + dbresponse.response = resp; + dbresponse.responseTo = m.data->id; + } + +} // namespace mongo diff --git a/db/dbstats.cpp b/db/dbstats.cpp new file mode 100644 index 0000000..902b57b --- /dev/null +++ b/db/dbstats.cpp @@ -0,0 +1,43 @@ +// dbstats.cpp + +#include "stdafx.h" +#include "dbstats.h" + +namespace mongo { + + OpCounters::OpCounters(){ + int zero = 0; + + BSONObjBuilder b; + b.append( "insert" , zero ); + b.append( "query" , zero ); + b.append( "update" , zero ); + b.append( "delete" , zero ); + b.append( "getmore" , zero ); + _obj = b.obj(); + + _insert = (int*)_obj["insert"].value(); + _query = (int*)_obj["query"].value(); + _update = (int*)_obj["update"].value(); + _delete = (int*)_obj["delete"].value(); + _getmore = (int*)_obj["getmore"].value(); + } + + void OpCounters::gotOp( int op ){ + switch ( op ){ + case dbInsert: gotInsert(); break; + case dbQuery: gotQuery(); break; + case dbUpdate: gotUpdate(); break; + case dbDelete: gotDelete(); break; + case dbGetMore: gotGetMore(); break; + case dbKillCursors: + case opReply: + case dbMsg: + break; + default: log() << "OpCounters::gotOp unknown op: " << op << endl; + } + } + + + OpCounters globalOpCounters; +} diff --git a/db/dbstats.h b/db/dbstats.h new file mode 100644 index 0000000..c7d6340 --- /dev/null +++ b/db/dbstats.h @@ -0,0 +1,44 @@ +// dbstats.h + +#include "../stdafx.h" +#include "jsobj.h" +#include "../util/message.h" + +namespace mongo { + + /** + * for storing operation counters + * note: not thread safe. ok with that for speed + */ + class OpCounters { + public: + + OpCounters(); + + int * getInsert(){ return _insert; } + int * getQuery(){ return _query; } + int * getUpdate(){ return _update; } + int * getDelete(){ return _delete; } + int * getGetGore(){ return _getmore; } + + void gotInsert(){ _insert[0]++; } + void gotQuery(){ _query[0]++; } + void gotUpdate(){ _update[0]++; } + void gotDelete(){ _delete[0]++; } + void gotGetMore(){ _getmore[0]++; } + + void gotOp( int op ); + + BSONObj& getObj(){ return _obj; } + private: + BSONObj _obj; + int * _insert; + int * _query; + int * _update; + int * _delete; + int * _getmore; + }; + + extern OpCounters globalOpCounters; + +} diff --git a/db/dbwebserver.cpp b/db/dbwebserver.cpp new file mode 100644 index 0000000..0e1483c --- /dev/null +++ b/db/dbwebserver.cpp @@ -0,0 +1,499 @@ +/* dbwebserver.cpp + + This is the administrative web page displayed on port 28017. +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "../util/miniwebserver.h" +#include "../util/md5.hpp" +#include "db.h" +#include "repl.h" +#include "replset.h" +#include "instance.h" +#include "security.h" + +#include <pcrecpp.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert xassert + +namespace mongo { + + extern string bind_ip; + extern const char *replInfo; + + bool getInitialSyncCompleted(); + + time_t started = time(0); + + /* + string toString() { + stringstream ss; + unsigned long long dt = last - start; + ss << dt/1000; + ss << '\t'; + ss << timeLocked/1000 << '\t'; + if( dt ) + ss << (timeLocked*100)/dt << '%'; + return ss.str(); + } + */ + + struct Timing { + Timing() { + start = timeLocked = 0; + } + unsigned long long start, timeLocked; + }; + Timing tlast; + const int NStats = 32; + string lockStats[NStats]; + unsigned q = 0; + + void statsThread() { + /*cout << "TEMP disabled statsthread" << endl; + if( 1 ) + return;*/ + Client::initThread("stats"); + unsigned long long timeLastPass = 0; + while ( 1 ) { + { + /* todo: do we even need readlock here? if so for what? */ + readlock lk(""); + Top::completeSnapshot(); + q = (q+1)%NStats; + Timing timing; + dbMutex.info().getTimingInfo(timing.start, timing.timeLocked); + unsigned long long now = curTimeMicros64(); + if ( timeLastPass ) { + unsigned long long dt = now - timeLastPass; + unsigned long long dlocked = timing.timeLocked - tlast.timeLocked; + { + stringstream ss; + ss << dt / 1000 << '\t'; + ss << dlocked / 1000 << '\t'; + if ( dt ) + ss << (dlocked*100)/dt << '%'; + string s = ss.str(); + if ( cmdLine.cpu ) + log() << "cpu: " << s << endl; + lockStats[q] = s; + ClientCursor::idleTimeReport( (unsigned) ((dt - dlocked)/1000) ); + } + } + timeLastPass = now; + tlast = timing; + } + sleepsecs(4); + } + } + + bool _bold; + string bold(bool x) { + _bold = x; + return x ? "<b>" : ""; + } + string bold() { + return _bold ? "</b>" : ""; + } + + class DbWebServer : public MiniWebServer { + public: + // caller locks + void doLockedStuff(stringstream& ss) { + ss << "# databases: " << dbHolder.size() << '\n'; + if ( cc().database() ) { + ss << "curclient: " << cc().database()->name; // TODO: isn't this useless? + ss << '\n'; + } + ss << bold(ClientCursor::byLocSize()>10000) << "Cursors byLoc.size(): " << ClientCursor::byLocSize() << bold() << '\n'; + ss << "\n<b>replication</b>\n"; + ss << "master: " << master << '\n'; + ss << "slave: " << slave << '\n'; + if ( replPair ) { + ss << "replpair:\n"; + ss << replPair->getInfo(); + } + bool seemCaughtUp = getInitialSyncCompleted(); + if ( !seemCaughtUp ) ss << "<b>"; + ss << "initialSyncCompleted: " << seemCaughtUp; + if ( !seemCaughtUp ) ss << "</b>"; + ss << '\n'; + + ss << "\n<b>DBTOP</b>\n"; + ss << "<table border=1><tr align='left'><th>Namespace</th><th>%</th><th>Reads</th><th>Writes</th><th>Calls</th><th>Time</th>"; + vector< Top::Usage > usage; + Top::usage( usage ); + for( vector< Top::Usage >::iterator i = usage.begin(); i != usage.end(); ++i ) + ss << setprecision( 2 ) << fixed << "<tr><td>" << i->ns << "</td><td>" << i->pct << "</td><td>" + << i->reads << "</td><td>" << i->writes << "</td><td>" << i->calls << "</td><td>" << i->time << "</td></tr>\n"; + ss << "</table>"; + + ss << "\n<b>dt\ttlocked</b>\n"; + unsigned i = q; + while ( 1 ) { + ss << lockStats[i] << '\n'; + i = (i-1)%NStats; + if ( i == q ) + break; + } + } + + void doUnlockedStuff(stringstream& ss) { + /* this is in the header already ss << "port: " << port << '\n'; */ + ss << mongodVersion() << "\n"; + ss << "git hash: " << gitVersion() << "\n"; + ss << "sys info: " << sysInfo() << "\n"; + ss << "\n"; + ss << "dbwritelocked: " << dbMutex.info().isLocked() << " (initial)\n"; + ss << "uptime: " << time(0)-started << " seconds\n"; + if ( replAllDead ) + ss << "<b>replication replAllDead=" << replAllDead << "</b>\n"; + ss << "\nassertions:\n"; + for ( int i = 0; i < 4; i++ ) { + if ( lastAssert[i].isSet() ) { + ss << "<b>"; + if ( i == 3 ) ss << "usererr"; + else ss << i; + ss << "</b>" << ' ' << lastAssert[i].toString(); + } + } + + ss << "\nreplInfo: " << replInfo << "\n\n"; + + ss << "Clients:\n"; + ss << "<table border=1><tr align='left'><th>Thread</th><th>Current op</th>\n"; + { + boostlock bl(Client::clientsMutex); + for( set<Client*>::iterator i = Client::clients.begin(); i != Client::clients.end(); i++ ) { + Client *c = *i; + CurOp& co = *(c->curop()); + ss << "<tr><td>" << c->desc() << "</td><td"; + BSONObj info = co.infoNoauth(); + /* + if( info.getIntField("inLock") > 0 ) + ss << "style='color:red'"; + else if( info.getIntField("inLock") < 0 ) + ss << "style='color:green'"; + */ + ss << ">" << info << "</td></tr>\n"; + } + } + ss << "</table>\n"; + } + + bool allowed( const char * rq , vector<string>& headers, const SockAddr &from ){ + + if ( from.localhost() ) + return true; + + if ( db.findOne( "admin.system.users" , BSONObj() ).isEmpty() ) + return true; + + string auth = getHeader( rq , "Authorization" ); + + if ( auth.size() > 0 && auth.find( "Digest " ) == 0 ){ + auth = auth.substr( 7 ) + ", "; + + map<string,string> parms; + pcrecpp::StringPiece input( auth ); + + string name, val; + pcrecpp::RE re("(\\w+)=\"?(.*?)\"?, "); + while ( re.Consume( &input, &name, &val) ){ + parms[name] = val; + } + + BSONObj user = db.findOne( "admin.system.users" , BSON( "user" << parms["username"] ) ); + if ( ! user.isEmpty() ){ + string ha1 = user["pwd"].str(); + string ha2 = md5simpledigest( (string)"GET" + ":" + parms["uri"] ); + + string r = ha1 + ":" + parms["nonce"]; + if ( parms["nc"].size() && parms["cnonce"].size() && parms["qop"].size() ){ + r += ":"; + r += parms["nc"]; + r += ":"; + r += parms["cnonce"]; + r += ":"; + r += parms["qop"]; + } + r += ":"; + r += ha2; + r = md5simpledigest( r ); + + if ( r == parms["response"] ) + return true; + } + + + } + + stringstream authHeader; + authHeader + << "WWW-Authenticate: " + << "Digest realm=\"mongo\", " + << "nonce=\"abc\", " + << "algorithm=MD5, qop=\"auth\" " + ; + + headers.push_back( authHeader.str() ); + return 0; + } + + virtual void doRequest( + const char *rq, // the full request + string url, + // set these and return them: + string& responseMsg, + int& responseCode, + vector<string>& headers, // if completely empty, content-type: text/html will be added + const SockAddr &from + ) + { + //out() << "url [" << url << "]" << endl; + + if ( url.size() > 1 ) { + if ( ! allowed( rq , headers, from ) ){ + responseCode = 401; + responseMsg = "not allowed\n"; + return; + } + handleRESTRequest( rq , url , responseMsg , responseCode , headers ); + return; + } + + + responseCode = 200; + stringstream ss; + ss << "<html><head><title>"; + + string dbname; + { + stringstream z; + z << "mongodb " << getHostName() << ':' << mongo::cmdLine.port << ' '; + dbname = z.str(); + } + ss << dbname << "</title></head><body><h2>" << dbname << "</h2><p>\n<pre>"; + + doUnlockedStuff(ss); + + int n = 2000; + Timer t; + while ( 1 ) { + if ( !dbMutex.info().isLocked() ) { + { + readlock lk(""); + ss << "time to get dblock: " << t.millis() << "ms\n"; + doLockedStuff(ss); + } + break; + } + sleepmillis(1); + if ( --n < 0 ) { + ss << "\n<b>timed out getting dblock</b>\n"; + break; + } + } + + ss << "</pre></body></html>"; + responseMsg = ss.str(); + + // we want to return SavedContext from before the authentication was performed + if ( ! allowed( rq , headers, from ) ){ + responseCode = 401; + responseMsg = "not allowed\n"; + return; + } + } + + void handleRESTRequest( const char *rq, // the full request + string url, + string& responseMsg, + int& responseCode, + vector<string>& headers // if completely empty, content-type: text/html will be added + ) { + + string::size_type first = url.find( "/" , 1 ); + if ( first == string::npos ) { + responseCode = 400; + return; + } + + string method = parseMethod( rq ); + string dbname = url.substr( 1 , first - 1 ); + string coll = url.substr( first + 1 ); + string action = ""; + + map<string,string> params; + if ( coll.find( "?" ) != string::npos ) { + parseParams( params , coll.substr( coll.find( "?" ) + 1 ) ); + coll = coll.substr( 0 , coll.find( "?" ) ); + } + + string::size_type last = coll.find_last_of( "/" ); + if ( last == string::npos ) { + action = coll; + coll = "_defaultCollection"; + } + else { + action = coll.substr( last + 1 ); + coll = coll.substr( 0 , last ); + } + + for ( string::size_type i=0; i<coll.size(); i++ ) + if ( coll[i] == '/' ) + coll[i] = '.'; + + string fullns = dbname + "." + coll; + + headers.push_back( (string)"x-action: " + action ); + headers.push_back( (string)"x-ns: " + fullns ); + headers.push_back( "Content-Type: text/plain;charset=utf-8" ); + + stringstream ss; + + if ( method == "GET" ) { + responseCode = 200; + handleRESTQuery( fullns , action , params , responseCode , ss ); + } + else if ( method == "POST" ) { + responseCode = 201; + handlePost( fullns , body( rq ) , params , responseCode , ss ); + } + else { + responseCode = 400; + headers.push_back( "X_err: bad request" ); + ss << "don't know how to handle a [" << method << "]"; + out() << "don't know how to handle a [" << method << "]" << endl; + } + + responseMsg = ss.str(); + } + + void handleRESTQuery( string ns , string action , map<string,string> & params , int & responseCode , stringstream & out ) { + Timer t; + + int skip = _getOption( params["skip"] , 0 ); + int num = _getOption( params["limit"] , _getOption( params["count" ] , 1000 ) ); // count is old, limit is new + + int one = 0; + if ( params["one"].size() > 0 && tolower( params["one"][0] ) == 't' ) { + num = 1; + one = 1; + } + + BSONObjBuilder queryBuilder; + + for ( map<string,string>::iterator i = params.begin(); i != params.end(); i++ ) { + if ( ! i->first.find( "filter_" ) == 0 ) + continue; + + const char * field = i->first.substr( 7 ).c_str(); + const char * val = i->second.c_str(); + + char * temp; + + // TODO: this is how i guess if something is a number. pretty lame right now + double number = strtod( val , &temp ); + if ( temp != val ) + queryBuilder.append( field , number ); + else + queryBuilder.append( field , val ); + } + + BSONObj query = queryBuilder.obj(); + + auto_ptr<DBClientCursor> cursor = db.query( ns.c_str() , query, num , skip ); + + if ( one ) { + if ( cursor->more() ) { + BSONObj obj = cursor->next(); + out << obj.jsonString() << "\n"; + } + else { + responseCode = 404; + } + return; + } + + out << "{\n"; + out << " \"offset\" : " << skip << ",\n"; + out << " \"rows\": [\n"; + + int howMany = 0; + while ( cursor->more() ) { + if ( howMany++ ) + out << " ,\n"; + BSONObj obj = cursor->next(); + out << " " << obj.jsonString(); + + } + out << "\n ],\n\n"; + + out << " \"total_rows\" : " << howMany << " ,\n"; + out << " \"query\" : " << query.jsonString() << " ,\n"; + out << " \"millis\" : " << t.millis() << "\n"; + out << "}\n"; + } + + // TODO Generate id and revision per couch POST spec + void handlePost( string ns, const char *body, map<string,string> & params, int & responseCode, stringstream & out ) { + try { + BSONObj obj = fromjson( body ); + db.insert( ns.c_str(), obj ); + } catch ( ... ) { + responseCode = 400; // Bad Request. Seems reasonable for now. + out << "{ \"ok\" : false }"; + return; + } + + responseCode = 201; + out << "{ \"ok\" : true }"; + } + + int _getOption( string val , int def ) { + if ( val.size() == 0 ) + return def; + return atoi( val.c_str() ); + } + + private: + static DBDirectClient db; + }; + + DBDirectClient DbWebServer::db; + + void webServerThread() { + boost::thread thr(statsThread); + Client::initThread("websvr"); + DbWebServer mini; + int p = cmdLine.port + 1000; + if ( mini.init(bind_ip, p) ) { + ListeningSockets::get()->add( mini.socket() ); + log() << "web admin interface listening on port " << p << endl; + mini.run(); + } + else { + log() << "warning: web admin interface failed to initialize on port " << p << endl; + } + cc().shutdown(); + } + +} // namespace mongo diff --git a/db/extsort.cpp b/db/extsort.cpp new file mode 100644 index 0000000..08b343a --- /dev/null +++ b/db/extsort.cpp @@ -0,0 +1,227 @@ +// extsort.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +#include "extsort.h" +#include "namespace.h" +#include "../util/file.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace mongo { + + unsigned long long BSONObjExternalSorter::_compares = 0; + + BSONObjExternalSorter::BSONObjExternalSorter( const BSONObj & order , long maxFileSize ) + : _order( order.getOwned() ) , _maxFilesize( maxFileSize ) , + _cur(0), _curSizeSoFar(0), _sorted(0){ + + stringstream rootpath; + rootpath << dbpath; + if ( dbpath[dbpath.size()-1] != '/' ) + rootpath << "/"; + rootpath << "_tmp/esort." << time(0) << "." << rand() << "/"; + _root = rootpath.str(); + + log(1) << "external sort root: " << _root.string() << endl; + + create_directories( _root ); + _compares = 0; + } + + BSONObjExternalSorter::~BSONObjExternalSorter(){ + if ( _cur ){ + delete _cur; + _cur = 0; + } + + unsigned long removed = remove_all( _root ); + wassert( removed == 1 + _files.size() ); + } + + void BSONObjExternalSorter::sort(){ + uassert( 10048 , "already sorted" , ! _sorted ); + + _sorted = true; + + if ( _cur && _files.size() == 0 ){ + _cur->sort( MyCmp( _order ) ); + log(1) << "\t\t not using file. size:" << _curSizeSoFar << " _compares:" << _compares << endl; + return; + } + + if ( _cur ){ + finishMap(); + } + + if ( _cur ){ + delete _cur; + _cur = 0; + } + + if ( _files.size() == 0 ) + return; + + } + + void BSONObjExternalSorter::add( const BSONObj& o , const DiskLoc & loc ){ + uassert( 10049 , "sorted already" , ! _sorted ); + + if ( ! _cur ){ + _cur = new InMemory(); + } + + _cur->push_back( pair<BSONObj,DiskLoc>( o.getOwned() , loc ) ); + + long size = o.objsize(); + _curSizeSoFar += size + sizeof( DiskLoc ); + + if ( _curSizeSoFar > _maxFilesize ) + finishMap(); + + } + + void BSONObjExternalSorter::finishMap(){ + uassert( 10050 , "bad" , _cur ); + + _curSizeSoFar = 0; + if ( _cur->size() == 0 ) + return; + + _cur->sort( MyCmp( _order ) ); + + stringstream ss; + ss << _root.string() << "/file." << _files.size(); + string file = ss.str(); + + ofstream out; + out.open( file.c_str() , ios_base::out | ios_base::binary ); + uassert( 10051 , (string)"couldn't open file: " + file , out.good() ); + + int num = 0; + for ( InMemory::iterator i=_cur->begin(); i != _cur->end(); i++ ){ + Data p = *i; + out.write( p.first.objdata() , p.first.objsize() ); + out.write( (char*)(&p.second) , sizeof( DiskLoc ) ); + num++; + } + + _cur->clear(); + + _files.push_back( file ); + out.close(); + + log(2) << "Added file: " << file << " with " << num << "objects for external sort" << endl; + } + + // --------------------------------- + + BSONObjExternalSorter::Iterator::Iterator( BSONObjExternalSorter * sorter ) : + _cmp( sorter->_order ) , _in( 0 ){ + + for ( list<string>::iterator i=sorter->_files.begin(); i!=sorter->_files.end(); i++ ){ + _files.push_back( new FileIterator( *i ) ); + _stash.push_back( pair<Data,bool>( Data( BSONObj() , DiskLoc() ) , false ) ); + } + + if ( _files.size() == 0 && sorter->_cur ){ + _in = sorter->_cur; + _it = sorter->_cur->begin(); + } + + + } + + BSONObjExternalSorter::Iterator::~Iterator(){ + for ( vector<FileIterator*>::iterator i=_files.begin(); i!=_files.end(); i++ ) + delete *i; + _files.clear(); + } + + bool BSONObjExternalSorter::Iterator::more(){ + + if ( _in ) + return _it != _in->end(); + + for ( vector<FileIterator*>::iterator i=_files.begin(); i!=_files.end(); i++ ) + if ( (*i)->more() ) + return true; + for ( vector< pair<Data,bool> >::iterator i=_stash.begin(); i!=_stash.end(); i++ ) + if ( i->second ) + return true; + return false; + } + + pair<BSONObj,DiskLoc> BSONObjExternalSorter::Iterator::next(){ + + if ( _in ){ + return *(_it++); + } + + Data best; + int slot = -1; + + for ( unsigned i=0; i<_stash.size(); i++ ){ + + if ( ! _stash[i].second ){ + if ( _files[i]->more() ) + _stash[i] = pair<Data,bool>( _files[i]->next() , true ); + else + continue; + } + + if ( slot == -1 || _cmp( best , _stash[i].first ) == 0 ){ + best = _stash[i].first; + slot = i; + } + + } + + assert( slot >= 0 ); + _stash[slot].second = false; + + return best; + } + + // ----------------------------------- + + BSONObjExternalSorter::FileIterator::FileIterator( string file ){ + long length; + _buf = (char*)_file.map( file.c_str() , length ); + massert( 10308 , "mmap failed" , _buf ); + assert( (unsigned long)length == file_size( file ) ); + _end = _buf + length; + } + BSONObjExternalSorter::FileIterator::~FileIterator(){ + } + + bool BSONObjExternalSorter::FileIterator::more(){ + return _buf < _end; + } + + pair<BSONObj,DiskLoc> BSONObjExternalSorter::FileIterator::next(){ + BSONObj o( _buf ); + _buf += o.objsize(); + DiskLoc * l = (DiskLoc*)_buf; + _buf += 8; + return Data( o , *l ); + } + +} diff --git a/db/extsort.h b/db/extsort.h new file mode 100644 index 0000000..5bfa86f --- /dev/null +++ b/db/extsort.h @@ -0,0 +1,123 @@ +// extsort.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" +#include "namespace.h" +#include "curop.h" + +namespace mongo { + + /** + for sorting by BSONObj and attaching a value + */ + class BSONObjExternalSorter : boost::noncopyable { + public: + + typedef pair<BSONObj,DiskLoc> Data; + + private: + class FileIterator : boost::noncopyable { + public: + FileIterator( string file ); + ~FileIterator(); + bool more(); + Data next(); + private: + MemoryMappedFile _file; + char * _buf; + char * _end; + }; + + class MyCmp { + public: + MyCmp( const BSONObj & order = BSONObj() ) : _order( order ){} + bool operator()( const Data &l, const Data &r ) const { + RARELY killCurrentOp.checkForInterrupt(); + _compares++; + int x = l.first.woCompare( r.first , _order ); + if ( x ) + return x < 0; + return l.second.compare( r.second ) < 0; + }; + private: + BSONObj _order; + }; + + public: + + typedef list<Data> InMemory; + + class Iterator : boost::noncopyable { + public: + + Iterator( BSONObjExternalSorter * sorter ); + ~Iterator(); + bool more(); + Data next(); + + private: + MyCmp _cmp; + vector<FileIterator*> _files; + vector< pair<Data,bool> > _stash; + + InMemory * _in; + InMemory::iterator _it; + + }; + + BSONObjExternalSorter( const BSONObj & order = BSONObj() , long maxFileSize = 1024 * 1024 * 100 ); + ~BSONObjExternalSorter(); + + void add( const BSONObj& o , const DiskLoc & loc ); + void add( const BSONObj& o , int a , int b ){ + add( o , DiskLoc( a , b ) ); + } + + /* call after adding values, and before fetching the iterator */ + void sort(); + + auto_ptr<Iterator> iterator(){ + uassert( 10052 , "not sorted" , _sorted ); + return auto_ptr<Iterator>( new Iterator( this ) ); + } + + int numFiles(){ + return _files.size(); + } + + private: + + void sort( string file ); + void finishMap(); + + BSONObj _order; + long _maxFilesize; + path _root; + + InMemory * _cur; + long _curSizeSoFar; + + list<string> _files; + bool _sorted; + + static unsigned long long _compares; + }; +} diff --git a/db/filever.h b/db/filever.h new file mode 100644 index 0000000..4aa18d4 --- /dev/null +++ b/db/filever.h @@ -0,0 +1,30 @@ +/* filever.h */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + +inline void checkDataFileVersion(NamespaceDetails& d) { +} + +inline void checkIndexFileVersion(NamespaceDetails& d) { +} + +} + diff --git a/db/flushtest.cpp b/db/flushtest.cpp new file mode 100644 index 0000000..a301e0e --- /dev/null +++ b/db/flushtest.cpp @@ -0,0 +1,134 @@ +#include "stdafx.h" +#include <stdio.h> +#include "../util/goodies.h" +#include <fcntl.h> + +namespace mongo { + +#if defined(F_FULLFSYNC) + void fullsync(int f) { + fcntl( f, F_FULLFSYNC ); + } +#else + void fullsync(int f) { + fdatasync(f); + } +#endif + + int main(int argc, char* argv[], char *envp[] ) { + cout << "hello" << endl; + + FILE *f = fopen("/data/db/temptest", "a"); + + if ( f == 0 ) { + cout << "can't open file\n"; + return 1; + } + + { + Timer t; + for ( int i = 0; i < 50000; i++ ) + fwrite("abc", 3, 1, f); + cout << "small writes: " << t.millis() << "ms" << endl; + } + + { + Timer t; + for ( int i = 0; i < 10000; i++ ) { + fwrite("abc", 3, 1, f); + fflush(f); + fsync( fileno( f ) ); + } + int ms = t.millis(); + cout << "flush: " << ms << "ms, " << ms / 10000.0 << "ms/request" << endl; + } + + { + Timer t; + for ( int i = 0; i < 500; i++ ) { + fwrite("abc", 3, 1, f); + fflush(f); + fsync( fileno( f ) ); + sleepmillis(2); + } + int ms = t.millis() - 500 * 2; + cout << "flush with sleeps: " << ms << "ms, " << ms / 500.0 << "ms/request" << endl; + } + + char buf[8192]; + for ( int pass = 0; pass < 2; pass++ ) { + cout << "pass " << pass << endl; + { + Timer t; + int n = 500; + for ( int i = 0; i < n; i++ ) { + if ( pass == 0 ) + fwrite("abc", 3, 1, f); + else + fwrite(buf, 8192, 1, f); + buf[0]++; + fflush(f); + fullsync(fileno(f)); + } + int ms = t.millis(); + cout << "fullsync: " << ms << "ms, " << ms / ((double) n) << "ms/request" << endl; + } + + { + Timer t; + for ( int i = 0; i < 500; i++ ) { + if ( pass == 0 ) + fwrite("abc", 3, 1, f); + else + fwrite(buf, 8192, 1, f); + buf[0]++; + fflush(f); + fullsync(fileno(f)); + sleepmillis(2); + } + int ms = t.millis() - 2 * 500; + cout << "fullsync with sleeps: " << ms << "ms, " << ms / 500.0 << "ms/request" << endl; + } + } + + // without growing + { + fclose(f); + /* try from beginning of the file, where we aren't appending and changing the file length, + to see if this is faster as the directory entry then doesn't have to be flushed (if noatime in effect). + */ + f = fopen("/data/db/temptest", "r+"); + Timer t; + int n = 500; + for ( int i = 0; i < n; i++ ) { + fwrite("xyz", 3, 1, f); + fflush(f); + fullsync(fileno(f)); + } + int ms = t.millis(); + cout << "fullsync without growing: " << ms << "ms, " << ms / ((double) n) << "ms/request" << endl; + } + + // without growing, with delay + { + fclose(f); + /* try from beginning of the file, where we aren't appending and changing the file length, + to see if this is faster as the directory entry then doesn't have to be flushed (if noatime in effect). + */ + f = fopen("/data/db/temptest", "r+"); + Timer t; + int n = 500; + for ( int i = 0; i < n; i++ ) { + fwrite("xyz", 3, 1, f); + fflush(f); + fullsync(fileno(f)); + sleepmillis(2); + } + int ms = t.millis() - 2 * 500; + cout << "fullsync without growing with sleeps: " << ms << "ms, " << ms / ((double) n) << "ms/request" << endl; + } + + return 0; + } + +} // namespace mongo diff --git a/db/index.cpp b/db/index.cpp new file mode 100644 index 0000000..fab6918 --- /dev/null +++ b/db/index.cpp @@ -0,0 +1,306 @@ +// index.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "namespace.h" +#include "index.h" +#include "btree.h" +#include "query.h" + +namespace mongo { + + /* delete this index. does NOT clean up the system catalog + (system.indexes or system.namespaces) -- only NamespaceIndex. + */ + void IndexDetails::kill_idx() { + string ns = indexNamespace(); // e.g. foo.coll.$ts_1 + + // clean up parent namespace index cache + NamespaceDetailsTransient::get_w( parentNS().c_str() ).deletedIndex(); + + BSONObjBuilder b; + b.append("name", indexName().c_str()); + b.append("ns", parentNS().c_str()); + BSONObj cond = b.done(); // e.g.: { name: "ts_1", ns: "foo.coll" } + + /* important to catch exception here so we can finish cleanup below. */ + try { + btreeStore->drop(ns.c_str()); + } + catch(DBException& ) { + log(2) << "IndexDetails::kill(): couldn't drop ns " << ns << endl; + } + head.setInvalid(); + info.setInvalid(); + + // clean up in system.indexes. we do this last on purpose. note we have + // to make the cond object before the drop() above though. + string system_indexes = cc().database()->name + ".system.indexes"; + int n = deleteObjects(system_indexes.c_str(), cond, false, false, true); + wassert( n == 1 ); + } + + void IndexSpec::_init(){ + assert( keys.objsize() ); + + BSONObjIterator i( keys ); + BSONObjBuilder nullKeyB; + while( i.more() ) { + _fieldNames.push_back( i.next().fieldName() ); + _fixed.push_back( BSONElement() ); + nullKeyB.appendNull( "" ); + } + + _nullKey = nullKeyB.obj(); + + BSONObjBuilder b; + b.appendNull( "" ); + _nullObj = b.obj(); + _nullElt = _nullObj.firstElement(); + } + + + void IndexSpec::getKeys( const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const { + vector<const char*> fieldNames( _fieldNames ); + vector<BSONElement> fixed( _fixed ); + _getKeys( fieldNames , fixed , obj, keys ); + if ( keys.empty() ) + keys.insert( _nullKey ); + } + + void IndexSpec::_getKeys( vector<const char*> fieldNames , vector<BSONElement> fixed , const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const { + BSONElement arrElt; + unsigned arrIdx = ~0; + for( unsigned i = 0; i < fieldNames.size(); ++i ) { + if ( *fieldNames[ i ] == '\0' ) + continue; + BSONElement e = obj.getFieldDottedOrArray( fieldNames[ i ] ); + if ( e.eoo() ) + e = _nullElt; // no matching field + if ( e.type() != Array ) + fieldNames[ i ] = ""; // no matching field or non-array match + if ( *fieldNames[ i ] == '\0' ) + fixed[ i ] = e; // no need for further object expansion (though array expansion still possible) + if ( e.type() == Array && arrElt.eoo() ) { // we only expand arrays on a single path -- track the path here + arrIdx = i; + arrElt = e; + } + // enforce single array path here + uassert( 10088 , "cannot index parallel arrays", e.type() != Array || e.rawdata() == arrElt.rawdata() ); + } + + bool allFound = true; // have we found elements for all field names in the key spec? + for( vector<const char*>::const_iterator i = fieldNames.begin(); i != fieldNames.end(); ++i ){ + if ( **i != '\0' ){ + allFound = false; + break; + } + } + + if ( allFound ) { + if ( arrElt.eoo() ) { + // no terminal array element to expand + BSONObjBuilder b; + for( vector< BSONElement >::iterator i = fixed.begin(); i != fixed.end(); ++i ) + b.appendAs( *i, "" ); + keys.insert( b.obj() ); + } + else { + // terminal array element to expand, so generate all keys + BSONObjIterator i( arrElt.embeddedObject() ); + if ( i.more() ){ + while( i.more() ) { + BSONObjBuilder b; + for( unsigned j = 0; j < fixed.size(); ++j ) { + if ( j == arrIdx ) + b.appendAs( i.next(), "" ); + else + b.appendAs( fixed[ j ], "" ); + } + keys.insert( b.obj() ); + } + } + else if ( fixed.size() > 1 ){ + // x : [] - need to insert undefined + BSONObjBuilder b; + for( unsigned j = 0; j < fixed.size(); ++j ) { + if ( j == arrIdx ) + b.appendUndefined( "" ); + else + b.appendAs( fixed[ j ], "" ); + } + keys.insert( b.obj() ); + } + } + } else { + // nonterminal array element to expand, so recurse + assert( !arrElt.eoo() ); + BSONObjIterator i( arrElt.embeddedObject() ); + while( i.more() ) { + BSONElement e = i.next(); + if ( e.type() == Object ) + _getKeys( fieldNames, fixed, e.embeddedObject(), keys ); + } + } + } + + /* Pull out the relevant key objects from obj, so we + can index them. Note that the set is multiple elements + only when it's a "multikey" array. + Keys will be left empty if key not found in the object. + */ + void IndexDetails::getKeysFromObject( const BSONObj& obj, BSONObjSetDefaultOrder& keys) const { + NamespaceDetailsTransient::get_w( info.obj()["ns"].valuestr() ).getIndexSpec( this ).getKeys( obj, keys ); + } + + void setDifference(BSONObjSetDefaultOrder &l, BSONObjSetDefaultOrder &r, vector<BSONObj*> &diff) { + BSONObjSetDefaultOrder::iterator i = l.begin(); + BSONObjSetDefaultOrder::iterator j = r.begin(); + while ( 1 ) { + if ( i == l.end() ) + break; + while ( j != r.end() && j->woCompare( *i ) < 0 ) + j++; + if ( j == r.end() || i->woCompare(*j) != 0 ) { + const BSONObj *jo = &*i; + diff.push_back( (BSONObj *) jo ); + } + i++; + } + } + + void getIndexChanges(vector<IndexChanges>& v, NamespaceDetails& d, BSONObj newObj, BSONObj oldObj) { + v.resize(d.nIndexes); + NamespaceDetails::IndexIterator i = d.ii(); + while( i.more() ) { + int j = i.pos(); + IndexDetails& idx = i.next(); + BSONObj idxKey = idx.info.obj().getObjectField("key"); // eg { ts : 1 } + IndexChanges& ch = v[j]; + idx.getKeysFromObject(oldObj, ch.oldkeys); + idx.getKeysFromObject(newObj, ch.newkeys); + if( ch.newkeys.size() > 1 ) + d.setIndexIsMultikey(j); + setDifference(ch.oldkeys, ch.newkeys, ch.removed); + setDifference(ch.newkeys, ch.oldkeys, ch.added); + } + } + + void dupCheck(vector<IndexChanges>& v, NamespaceDetails& d) { + NamespaceDetails::IndexIterator i = d.ii(); + while( i.more() ) { + int j = i.pos(); + v[j].dupCheck(i.next()); + } + } + + // should be { <something> : <simpletype[1|-1]>, .keyp.. } + static bool validKeyPattern(BSONObj kp) { + BSONObjIterator i(kp); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if( e.type() == Object || e.type() == Array ) + return false; + } + return true; + } + + /* Prepare to build an index. Does not actually build it (except for a special _id case). + - We validate that the params are good + - That the index does not already exist + - Creates the source collection if it DNE + + example of 'io': + { ns : 'test.foo', name : 'z', key : { z : 1 } } + + throws DBException + + @return + true if ok to continue. when false we stop/fail silently (index already exists) + sourceNS - source NS we are indexing + sourceCollection - its details ptr + */ + bool prepareToBuildIndex(const BSONObj& io, bool god, string& sourceNS, NamespaceDetails *&sourceCollection) { + sourceCollection = 0; + + // logical name of the index. todo: get rid of the name, we don't need it! + const char *name = io.getStringField("name"); + uassert(12523, "no index name specified", *name); + + // the collection for which we are building an index + sourceNS = io.getStringField("ns"); + uassert(10096, "invalid ns to index", sourceNS.find( '.' ) != string::npos); + uassert(10097, "bad table to index name on add index attempt", + cc().database()->name == nsToDatabase(sourceNS.c_str())); + + BSONObj key = io.getObjectField("key"); + uassert(12524, "index key pattern too large", key.objsize() <= 2048); + if( !validKeyPattern(key) ) { + string s = string("bad index key pattern ") + key.toString(); + uasserted(10098 , s.c_str()); + } + + if ( sourceNS.empty() || key.isEmpty() ) { + log(2) << "bad add index attempt name:" << (name?name:"") << "\n ns:" << + sourceNS << "\n idxobj:" << io.toString() << endl; + string s = "bad add index attempt " + sourceNS + " key:" + key.toString(); + uasserted(12504, s); + } + + sourceCollection = nsdetails(sourceNS.c_str()); + if( sourceCollection == 0 ) { + // try to create it + string err; + if ( !userCreateNS(sourceNS.c_str(), BSONObj(), err, false) ) { + problem() << "ERROR: failed to create collection while adding its index. " << sourceNS << endl; + return false; + } + sourceCollection = nsdetails(sourceNS.c_str()); + log() << "info: creating collection " << sourceNS << " on add index\n"; + assert( sourceCollection ); + } + + if ( sourceCollection->findIndexByName(name) >= 0 ) { + // index already exists. + return false; + } + if( sourceCollection->findIndexByKeyPattern(key) >= 0 ) { + log(2) << "index already exists with diff name " << name << ' ' << key.toString() << endl; + return false; + } + + if ( sourceCollection->nIndexes >= NamespaceDetails::NIndexesMax ) { + stringstream ss; + ss << "add index fails, too many indexes for " << sourceNS << " key:" << key.toString(); + string s = ss.str(); + log() << s << '\n'; + uasserted(12505,s); + } + + /* this is because we want key patterns like { _id : 1 } and { _id : <someobjid> } to + all be treated as the same pattern. + */ + if ( !god && IndexDetails::isIdIndexPattern(key) ) { + ensureHaveIdIndex( sourceNS.c_str() ); + return false; + } + + return true; + } + +} diff --git a/db/index.h b/db/index.h new file mode 100644 index 0000000..696e84d --- /dev/null +++ b/db/index.h @@ -0,0 +1,198 @@ +// index.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" + +namespace mongo { + + class IndexSpec { + public: + BSONObj keys; + BSONObj meta; + + IndexSpec(){ + } + + IndexSpec( const BSONObj& k , const BSONObj& m = BSONObj() ) + : keys(k) , meta(m){ + _init(); + } + + /** + this is a DickLock of an IndexDetails info + should have a key field + */ + IndexSpec( const DiskLoc& loc ){ + reset( loc ); + } + + void reset( const DiskLoc& loc ){ + meta = loc.obj(); + keys = meta["key"].embeddedObjectUserCheck(); + if ( keys.objsize() == 0 ) { + out() << meta.toString() << endl; + assert(false); + + } + _init(); + } + + void getKeys( const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const; + + private: + + void _getKeys( vector<const char*> fieldNames , vector<BSONElement> fixed , const BSONObj &obj, BSONObjSetDefaultOrder &keys ) const; + + vector<const char*> _fieldNames; + vector<BSONElement> _fixed; + BSONObj _nullKey; + + BSONObj _nullObj; + BSONElement _nullElt; + + void _init(); + }; + + /* Details about a particular index. There is one of these effectively for each object in + system.namespaces (although this also includes the head pointer, which is not in that + collection). + + ** MemoryMapped Record ** + */ + class IndexDetails { + public: + DiskLoc head; /* btree head disk location */ + + /* Location of index info object. Format: + + { name:"nameofindex", ns:"parentnsname", key: {keypattobject} + [, unique: <bool>, background: <bool>] + } + + This object is in the system.indexes collection. Note that since we + have a pointer to the object here, the object in system.indexes MUST NEVER MOVE. + */ + DiskLoc info; + + /* extract key value from the query object + e.g., if key() == { x : 1 }, + { x : 70, y : 3 } -> { x : 70 } + */ + BSONObj getKeyFromQuery(const BSONObj& query) const { + BSONObj k = keyPattern(); + BSONObj res = query.extractFieldsUnDotted(k); + return res; + } + + /* pull out the relevant key objects from obj, so we + can index them. Note that the set is multiple elements + only when it's a "multikey" array. + keys will be left empty if key not found in the object. + */ + void getKeysFromObject( const BSONObj& obj, BSONObjSetDefaultOrder& keys) const; + + /* get the key pattern for this object. + e.g., { lastname:1, firstname:1 } + */ + BSONObj keyPattern() const { + return info.obj().getObjectField("key"); + } + + /* true if the specified key is in the index */ + bool hasKey(const BSONObj& key); + + // returns name of this index's storage area + // database.table.$index + string indexNamespace() const { + BSONObj io = info.obj(); + string s; + s.reserve(Namespace::MaxNsLen); + s = io.getStringField("ns"); + assert( !s.empty() ); + s += ".$"; + s += io.getStringField("name"); + return s; + } + + string indexName() const { // e.g. "ts_1" + BSONObj io = info.obj(); + return io.getStringField("name"); + } + + static bool isIdIndexPattern( const BSONObj &pattern ) { + BSONObjIterator i(pattern); + BSONElement e = i.next(); + if( strcmp(e.fieldName(), "_id") != 0 ) return false; + return i.next().eoo(); + } + + /* returns true if this is the _id index. */ + bool isIdIndex() const { + return isIdIndexPattern( keyPattern() ); + } + + /* gets not our namespace name (indexNamespace for that), + but the collection we index, its name. + */ + string parentNS() const { + BSONObj io = info.obj(); + return io.getStringField("ns"); + } + + bool unique() const { + BSONObj io = info.obj(); + return io["unique"].trueValue() || + /* temp: can we juse make unique:true always be there for _id and get rid of this? */ + isIdIndex(); + } + + /* if set, when building index, if any duplicates, drop the duplicating object */ + bool dropDups() const { + return info.obj().getBoolField( "dropDups" ); + } + + /* delete this index. does NOT clean up the system catalog + (system.indexes or system.namespaces) -- only NamespaceIndex. + */ + void kill_idx(); + + operator string() const { + return info.obj().toString(); + } + }; + + struct IndexChanges/*on an update*/ { + BSONObjSetDefaultOrder oldkeys; + BSONObjSetDefaultOrder newkeys; + vector<BSONObj*> removed; // these keys were removed as part of the change + vector<BSONObj*> added; // these keys were added as part of the change + + void dupCheck(IndexDetails& idx) { + if( added.empty() || !idx.unique() ) + return; + for( vector<BSONObj*>::iterator i = added.begin(); i != added.end(); i++ ) + uassert( 11001 , "E11001 duplicate key on update", !idx.hasKey(**i)); + } + }; + + class NamespaceDetails; + void getIndexChanges(vector<IndexChanges>& v, NamespaceDetails& d, BSONObj newObj, BSONObj oldObj); + void dupCheck(vector<IndexChanges>& v, NamespaceDetails& d); +} // namespace mongo diff --git a/db/instance.cpp b/db/instance.cpp new file mode 100644 index 0000000..e8515c4 --- /dev/null +++ b/db/instance.cpp @@ -0,0 +1,767 @@ +// instance.cpp : Global state variables and functions. +// + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "db.h" +#include "query.h" +#include "introspect.h" +#include "repl.h" +#include "dbmessage.h" +#include "instance.h" +#include "lasterror.h" +#include "security.h" +#include "json.h" +#include "reccache.h" +#include "replset.h" +#include "../s/d_logic.h" +#include "../util/file_allocator.h" +#include "cmdline.h" +#if !defined(_WIN32) +#include <sys/file.h> +#endif +#include "dbstats.h" + +namespace mongo { + + void receivedKillCursors(Message& m); + void receivedUpdate(Message& m, CurOp& op); + void receivedDelete(Message& m, CurOp& op); + void receivedInsert(Message& m, CurOp& op); + bool receivedGetMore(DbResponse& dbresponse, Message& m, CurOp& curop ); + + CmdLine cmdLine; + + int nloggedsome = 0; +#define LOGSOME if( ++nloggedsome < 1000 || nloggedsome % 100 == 0 ) + + SlaveTypes slave = NotSlave; + bool master = false; // true means keep an op log + bool autoresync = false; + + /* we use new here so we don't have to worry about destructor orders at program shutdown */ + MongoMutex &dbMutex( *(new MongoMutex) ); +// MutexInfo dbMutexInfo; + + string dbExecCommand; + + string bind_ip = ""; + + char *appsrvPath = null; + + DiagLog _diaglog; + + int opIdMem = 100000000; + + bool useCursors = true; + bool useHints = true; + + void closeAllSockets(); + void flushOpLog( stringstream &ss ) { + if( _diaglog.f && _diaglog.f->is_open() ) { + ss << "flushing op log and files\n"; + _diaglog.flush(); + } + } + + int ctr = 0; + + KillCurrentOp killCurrentOp; + + int lockFile = 0; + + // see FSyncCommand: + unsigned lockedForWriting; + boost::mutex lockedForWritingMutex; + bool unlockRequested = false; + + void inProgCmd( Message &m, DbResponse &dbresponse ) { + BSONObjBuilder b; + + AuthenticationInfo *ai = cc().ai; + if( !ai->isAuthorized("admin") ) { + BSONObjBuilder b; + b.append("err", "unauthorized"); + } + else { + vector<BSONObj> vals; + { + boostlock bl(Client::clientsMutex); + for( set<Client*>::iterator i = Client::clients.begin(); i != Client::clients.end(); i++ ) { + Client *c = *i; + CurOp& co = *(c->curop()); + if( co.active() ) + vals.push_back( co.infoNoauth() ); + } + } + b.append("inprog", vals); + unsigned x = lockedForWriting; + if( x ) { + b.append("fsyncLock", x); + b.append("info", "use command {unlock:0} to terminate the fsync write/snapshot lock"); + } + } + + replyToQuery(0, m, dbresponse, b.obj()); + } + + void killOp( Message &m, DbResponse &dbresponse ) { + BSONObj obj; + AuthenticationInfo *ai = currentClient.get()->ai; + if( !ai->isAuthorized("admin") ) { + obj = fromjson("{\"err\":\"unauthorized\"}"); + } + /*else if( !dbMutexInfo.isLocked() ) + obj = fromjson("{\"info\":\"no op in progress/not locked\"}"); + */ + else { + DbMessage d(m); + QueryMessage q(d); + BSONElement e = q.query.getField("op"); + if( !e.isNumber() ) { + obj = fromjson("{\"err\":\"no op number field specified?\"}"); + } + else { + obj = fromjson("{\"info\":\"attempting to kill op\"}"); + killCurrentOp.kill( (unsigned) e.number() ); + } + } + replyToQuery(0, m, dbresponse, obj); + } + + void unlockFsync(const char *ns, Message& m, DbResponse &dbresponse) { + BSONObj obj; + AuthenticationInfo *ai = currentClient.get()->ai; + if( !ai->isAuthorized("admin") || strncmp(ns, "admin.", 6) != 0 ) { + obj = fromjson("{\"err\":\"unauthorized\"}"); + } + else { + if( lockedForWriting ) { + log() << "command: unlock requested" << endl; + obj = fromjson("{ok:1,\"info\":\"unlock requested\"}"); + unlockRequested = true; + } + else { + obj = fromjson("{ok:0,\"errmsg\":\"not locked\"}"); + } + } + replyToQuery(0, m, dbresponse, obj); + } + + static bool receivedQuery(DbResponse& dbresponse, Message& m, + CurOp& op, bool logit, + mongolock& lock + ) { + bool ok = true; + MSGID responseTo = m.data->id; + + DbMessage d(m); + QueryMessage q(d); + QueryResult* msgdata; + + Client& c = cc(); + + try { + if (q.fields.get() && q.fields->errmsg) + uassert( 10053 , q.fields->errmsg, false); + + /* note these are logged BEFORE authentication -- which is sort of ok */ + if ( _diaglog.level && logit ) { + if ( strstr(q.ns, ".$cmd") ) { + /* $cmd queries are "commands" and usually best treated as write operations */ + OPWRITE; + } + else { + OPREAD; + } + } + + setClient( q.ns, dbpath, &lock ); + c.top.setRead(); + c.curop()->setNS(q.ns); + msgdata = runQuery(m, q, op ).release(); + } + catch ( AssertionException& e ) { + ok = false; + op.debug().str << " exception "; + LOGSOME problem() << " Caught Assertion in runQuery ns:" << q.ns << ' ' << e.toString() << '\n'; + log() << " ntoskip:" << q.ntoskip << " ntoreturn:" << q.ntoreturn << '\n'; + if ( q.query.valid() ) + log() << " query:" << q.query.toString() << endl; + else + log() << " query object is not valid!" << endl; + + BSONObjBuilder err; + err.append("$err", e.msg.empty() ? "assertion during query" : e.msg); + BSONObj errObj = err.done(); + + BufBuilder b; + b.skip(sizeof(QueryResult)); + b.append((void*) errObj.objdata(), errObj.objsize()); + + // todo: call replyToQuery() from here instead of this!!! see dbmessage.h + msgdata = (QueryResult *) b.buf(); + b.decouple(); + QueryResult *qr = msgdata; + qr->_resultFlags() = QueryResult::ResultFlag_ErrSet; + qr->len = b.len(); + qr->setOperation(opReply); + qr->cursorId = 0; + qr->startingFrom = 0; + qr->nReturned = 1; + + } + Message *resp = new Message(); + resp->setData(msgdata, true); // transport will free + dbresponse.response = resp; + dbresponse.responseTo = responseTo; + Database *database = c.database(); + if ( database ) { + if ( database->profile ) + op.debug().str << " bytes:" << resp->data->dataLen(); + } + else { + if ( strstr(q.ns, "$cmd") == 0 ) // (this condition is normal for $cmd dropDatabase) + log() << "ERROR: receiveQuery: database is null; ns=" << q.ns << endl; + } + + return ok; + } + + bool commandIsReadOnly(BSONObj& _cmdobj); + + // Returns false when request includes 'end' + bool assembleResponse( Message &m, DbResponse &dbresponse, const sockaddr_in &client ) { + + bool writeLock = true; + + // before we lock... + int op = m.data->operation(); + globalOpCounters.gotOp( op ); + const char *ns = m.data->_data + 4; + if ( op == dbQuery ) { + if( strstr(ns, ".$cmd") ) { + if( strstr(ns, ".$cmd.sys.") ) { + if( strstr(ns, "$cmd.sys.inprog") ) { + inProgCmd(m, dbresponse); + return true; + } + if( strstr(ns, "$cmd.sys.killop") ) { + killOp(m, dbresponse); + return true; + } + if( strstr(ns, "$cmd.sys.unlock") ) { + unlockFsync(ns, m, dbresponse); + return true; + } + } + DbMessage d( m ); + QueryMessage q( d ); + writeLock = !commandIsReadOnly(q.query); + } + else + writeLock = false; + } + else if( op == dbGetMore ) { + writeLock = false; + } + + if ( handlePossibleShardedMessage( m , dbresponse ) ){ + /* important to do this before we lock + so if a message has to be forwarded, doesn't block for that + */ + return true; + } + + Client& c = cc(); + c.clearns(); + + auto_ptr<CurOp> nestedOp; + CurOp* currentOpP = c.curop(); + if ( currentOpP->active() ){ + nestedOp.reset( new CurOp() ); + currentOpP = nestedOp.get(); + } + CurOp& currentOp = *currentOpP; + currentOp.reset(client); + currentOp.setOp(op); + + OpDebug& debug = currentOp.debug(); + StringBuilder& ss = debug.str; + + int logThreshold = cmdLine.slowMS; + bool log = logLevel >= 1; + + Timer t( currentOp.startTime() ); + + mongolock lk(writeLock); + +#if 0 + /* use this if you only want to process operations for a particular namespace. + maybe add to cmd line parms or something fancier. + */ + DbMessage ddd(m); + if ( strncmp(ddd.getns(), "clusterstock", 12) != 0 ) { + static int q; + if ( ++q < 20 ) + out() << "TEMP skip " << ddd.getns() << endl; + goto skip; + } +#endif + + if ( op == dbQuery ) { + // receivedQuery() does its own authorization processing. + if ( ! receivedQuery(dbresponse, m, currentOp, true, lk) ) + log = true; + } + else if ( op == dbGetMore ) { + // does its own authorization processing. + OPREAD; + DEV log = true; + ss << "getmore "; + if ( ! receivedGetMore(dbresponse, m, currentOp) ) + log = true; + } + else if ( op == dbMsg ) { + /* deprecated / rarely used. intended for connection diagnostics. */ + ss << "msg "; + char *p = m.data->_data; + int len = strlen(p); + if ( len > 400 ) + out() << curTimeMillis() % 10000 << + " long msg received, len:" << len << + " ends with: " << p + len - 10 << endl; + bool end = false; //strcmp("end", p) == 0; + Message *resp = new Message(); + resp->setData(opReply, "i am fine"); + dbresponse.response = resp; + dbresponse.responseTo = m.data->id; + //dbMsgPort.reply(m, resp); + if ( end ) + return false; + } + else { + const char *ns = m.data->_data + 4; + char cl[256]; + nsToDatabase(ns, cl); + currentOp.setNS(ns); + AuthenticationInfo *ai = currentClient.get()->ai; + if( !ai->isAuthorized(cl) ) { + uassert_nothrow("unauthorized"); + } + else if ( op == dbInsert ) { + OPWRITE; + try { + ss << "insert "; + receivedInsert(m, currentOp); + } + catch ( AssertionException& e ) { + LOGSOME problem() << " Caught Assertion insert, continuing\n"; + ss << " exception " << e.toString(); + log = true; + } + } + else if ( op == dbUpdate ) { + OPWRITE; + try { + ss << "update "; + receivedUpdate(m, currentOp); + } + catch ( AssertionException& e ) { + LOGSOME problem() << " Caught Assertion update, continuing" << endl; + ss << " exception " << e.toString(); + log = true; + } + } + else if ( op == dbDelete ) { + OPWRITE; + try { + ss << "remove "; + receivedDelete(m, currentOp); + } + catch ( AssertionException& e ) { + LOGSOME problem() << " Caught Assertion receivedDelete, continuing" << endl; + ss << " exception " << e.toString(); + log = true; + } + } + else if ( op == dbKillCursors ) { + OPREAD; + try { + logThreshold = 10; + ss << "killcursors "; + receivedKillCursors(m); + } + catch ( AssertionException& e ) { + problem() << " Caught Assertion in kill cursors, continuing" << endl; + ss << " exception " + e.toString(); + log = true; + } + } + else { + out() << " operation isn't supported: " << op << endl; + currentOp.setActive(false); + assert(false); + } + } + int ms = t.millis(); + log = log || (logLevel >= 2 && ++ctr % 512 == 0); + DEV log = true; + if ( log || ms > logThreshold ) { + ss << ' ' << ms << "ms"; + mongo::log() << ss.str() << endl; + } + Database *database = c.database(); + if ( database && database->profile >= 1 ) { + if ( database->profile >= 2 || ms >= cmdLine.slowMS ) { + // performance profiling is on + if ( dbMutex.getState() > 1 || dbMutex.getState() < -1 ){ + out() << "warning: not profiling because recursive lock" << endl; + } + else { + string old_ns = c.ns(); + Database * old_db = c.database(); + lk.releaseAndWriteLock(); + Client::Context c( old_ns , old_db ); + profile(ss.str().c_str(), ms); + } + } + } + + currentOp.setActive(false); + return true; + } /* assembleResponse() */ + + void killCursors(int n, long long *ids); + void receivedKillCursors(Message& m) { + int *x = (int *) m.data->_data; + x++; // reserved + int n = *x++; + assert( n >= 1 ); + if ( n > 2000 ) { + problem() << "Assertion failure, receivedKillCursors, n=" << n << endl; + assert( n < 30000 ); + } + killCursors(n, (long long *) x); + } + + /* cl - database name + path - db directory + */ + void closeDatabase( const char *cl, const string& path ) { + Database *database = cc().database(); + assert( database ); + assert( database->name == cl ); + /* + if ( string("local") != cl ) { + DBInfo i(cl); + i.dbDropped(); + }*/ + + /* important: kill all open cursors on the database */ + string prefix(cl); + prefix += '.'; + ClientCursor::invalidate(prefix.c_str()); + + NamespaceDetailsTransient::clearForPrefix( prefix.c_str() ); + + dbHolder.erase( cl, path ); + delete database; // closes files + cc().clearns(); + } + + void receivedUpdate(Message& m, CurOp& op) { + DbMessage d(m); + const char *ns = d.getns(); + assert(*ns); + uassert( 10054 , "not master", isMasterNs( ns ) ); + setClient(ns); + Client& client = cc(); + client.top.setWrite(); + op.debug().str << ns << ' '; + int flags = d.pullInt(); + BSONObj query = d.nextJsObj(); + + assert( d.moreJSObjs() ); + assert( query.objsize() < m.data->dataLen() ); + BSONObj toupdate = d.nextJsObj(); + uassert( 10055 , "update object too large", toupdate.objsize() <= MaxBSONObjectSize); + assert( toupdate.objsize() < m.data->dataLen() ); + assert( query.objsize() + toupdate.objsize() < m.data->dataLen() ); + bool upsert = flags & UpdateOption_Upsert; + bool multi = flags & UpdateOption_Multi; + { + string s = query.toString(); + /* todo: we shouldn't do all this ss stuff when we don't need it, it will slow us down. */ + op.debug().str << " query: " << s; + CurOp& currentOp = *client.curop(); + currentOp.setQuery(query); + } + UpdateResult res = updateObjects(ns, toupdate, query, upsert, multi, true, op.debug() ); + /* TODO FIX: recordUpdate should take a long int for parm #2 */ + recordUpdate( res.existing , (int) res.num ); // for getlasterror + } + + void receivedDelete(Message& m, CurOp& op) { + DbMessage d(m); + const char *ns = d.getns(); + assert(*ns); + uassert( 10056 , "not master", isMasterNs( ns ) ); + setClient(ns); + Client& client = cc(); + client.top.setWrite(); + int flags = d.pullInt(); + bool justOne = flags & 1; + assert( d.moreJSObjs() ); + BSONObj pattern = d.nextJsObj(); + { + string s = pattern.toString(); + op.debug().str << " query: " << s; + CurOp& currentOp = *client.curop(); + currentOp.setQuery(pattern); + } + int n = deleteObjects(ns, pattern, justOne, true); + recordDelete( n ); + } + + QueryResult* emptyMoreResult(long long); + + bool receivedGetMore(DbResponse& dbresponse, Message& m, CurOp& curop ) { + bool ok = true; + DbMessage d(m); + const char *ns = d.getns(); + StringBuilder& ss = curop.debug().str; + ss << ns; + setClient(ns); + cc().top.setRead(); + int ntoreturn = d.pullInt(); + long long cursorid = d.pullInt64(); + ss << " cid:" << cursorid; + ss << " ntoreturn:" << ntoreturn; + QueryResult* msgdata; + try { + AuthenticationInfo *ai = currentClient.get()->ai; + uassert( 10057 , "unauthorized", ai->isAuthorized(cc().database()->name.c_str())); + msgdata = getMore(ns, ntoreturn, cursorid, curop); + } + catch ( AssertionException& e ) { + ss << " exception " + e.toString(); + msgdata = emptyMoreResult(cursorid); + ok = false; + } + Message *resp = new Message(); + resp->setData(msgdata, true); + ss << " bytes:" << resp->data->dataLen(); + ss << " nreturned:" << msgdata->nReturned; + dbresponse.response = resp; + dbresponse.responseTo = m.data->id; + //dbMsgPort.reply(m, resp); + return ok; + } + + void receivedInsert(Message& m, CurOp& op) { + DbMessage d(m); + const char *ns = d.getns(); + assert(*ns); + uassert( 10058 , "not master", isMasterNs( ns ) ); + setClient(ns); + cc().top.setWrite(); + op.debug().str << ns; + + while ( d.moreJSObjs() ) { + BSONObj js = d.nextJsObj(); + uassert( 10059 , "object to insert too large", js.objsize() <= MaxBSONObjectSize); + theDataFileMgr.insert(ns, js, false); + logOp("i", ns, js); + } + } + + class JniMessagingPort : public AbstractMessagingPort { + public: + JniMessagingPort(Message& _container) : container(_container) { } + void reply(Message& received, Message& response, MSGID) { + container = response; + } + void reply(Message& received, Message& response) { + container = response; + } + unsigned remotePort(){ + return 1; + } + Message & container; + }; + + void getDatabaseNames( vector< string > &names ) { + boost::filesystem::path path( dbpath ); + for ( boost::filesystem::directory_iterator i( path ); + i != boost::filesystem::directory_iterator(); ++i ) { + string fileName = boost::filesystem::path(*i).leaf(); + if ( fileName.length() > 3 && fileName.substr( fileName.length() - 3, 3 ) == ".ns" ) + names.push_back( fileName.substr( 0, fileName.length() - 3 ) ); + } + } + + bool DBDirectClient::call( Message &toSend, Message &response, bool assertOk ) { + SavedContext c; + if ( lastError._get() ) + lastError.startRequest( toSend, lastError._get() ); + DbResponse dbResponse; + assembleResponse( toSend, dbResponse ); + assert( dbResponse.response ); + response = *dbResponse.response; + return true; + } + + void DBDirectClient::say( Message &toSend ) { + SavedContext c; + if ( lastError._get() ) + lastError.startRequest( toSend, lastError._get() ); + DbResponse dbResponse; + assembleResponse( toSend, dbResponse ); + } + + auto_ptr<DBClientCursor> DBDirectClient::query(const string &ns, Query query, int nToReturn , int nToSkip , + const BSONObj *fieldsToReturn , int queryOptions ){ + + //if ( ! query.obj.isEmpty() || nToReturn != 0 || nToSkip != 0 || fieldsToReturn || queryOptions ) + return DBClientBase::query( ns , query , nToReturn , nToSkip , fieldsToReturn , queryOptions ); + // + //assert( query.obj.isEmpty() ); + //throw UserException( (string)"yay:" + ns ); + } + + + DBDirectClient::AlwaysAuthorized DBDirectClient::SavedContext::always; + + DBClientBase * createDirectClient(){ + return new DBDirectClient(); + } + + void recCacheCloseAll(); + + boost::mutex &exitMutex( *( new boost::mutex ) ); + int numExitCalls = 0; + void shutdown(); + + bool inShutdown(){ + return numExitCalls > 0; + } + + void tryToOutputFatal( const string& s ){ + try { + rawOut( s ); + return; + } + catch ( ... ){} + + try { + cerr << s << endl; + return; + } + catch ( ... ){} + + // uh - oh, not sure there is anything else we can do... + } + + /* not using log() herein in case we are already locked */ + void dbexit( ExitCode rc, const char *why) { + { + boostlock lk( exitMutex ); + if ( numExitCalls++ > 0 ) { + if ( numExitCalls > 5 ){ + // this means something horrible has happened + ::_exit( rc ); + } + stringstream ss; + ss << "dbexit: " << why << "; exiting immediately" << endl; + tryToOutputFatal( ss.str() ); + ::exit( rc ); + } + } + + stringstream ss; + ss << "dbexit: " << why << endl; + tryToOutputFatal( ss.str() ); + + try { + shutdown(); // gracefully shutdown instance + } + catch ( ... ){ + tryToOutputFatal( "shutdown failed with exception" ); + } + + tryToOutputFatal( "dbexit: really exiting now\n" ); + ::exit(rc); + } + + void shutdown() { + + + log() << "\t shutdown: going to close listening sockets..." << endl; + ListeningSockets::get()->closeAll(); + + log() << "\t shutdown: going to flush oplog..." << endl; + stringstream ss2; + flushOpLog( ss2 ); + rawOut( ss2.str() ); + + /* must do this before unmapping mem or you may get a seg fault */ + log() << "\t shutdown: going to close sockets..." << endl; + boost::thread close_socket_thread(closeAllSockets); + + // wait until file preallocation finishes + // we would only hang here if the file_allocator code generates a + // synchronous signal, which we don't expect + log() << "\t shutdown: waiting for fs preallocator..." << endl; + theFileAllocator().waitUntilFinished(); + + log() << "\t shutdown: closing all files..." << endl; + stringstream ss3; + MemoryMappedFile::closeAllFiles( ss3 ); + rawOut( ss3.str() ); + + // should we be locked here? we aren't. might be ok as-is. + recCacheCloseAll(); + +#if !defined(_WIN32) && !defined(__sunos__) + if ( lockFile ){ + log() << "\t shutdown: removing fs lock..." << endl; + if( ftruncate( lockFile , 0 ) ) + log() << "\t couldn't remove fs lock " << OUTPUT_ERRNO << endl; + flock( lockFile, LOCK_UN ); + } +#endif + } + + void acquirePathLock() { +#if !defined(_WIN32) && !defined(__sunos__) + string name = ( boost::filesystem::path( dbpath ) / "mongod.lock" ).native_file_string(); + lockFile = open( name.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRWXU | S_IRWXG | S_IRWXO ); + massert( 10309 , "Unable to create / open lock file for dbpath: " + name, lockFile > 0 ); + massert( 10310 , "Unable to acquire lock for dbpath: " + name, flock( lockFile, LOCK_EX | LOCK_NB ) == 0 ); + + stringstream ss; + ss << getpid() << endl; + string s = ss.str(); + const char * data = s.c_str(); + assert( write( lockFile , data , strlen( data ) ) ); + fsync( lockFile ); +#endif + } + +} // namespace mongo diff --git a/db/instance.h b/db/instance.h new file mode 100644 index 0000000..b2b2c94 --- /dev/null +++ b/db/instance.h @@ -0,0 +1,179 @@ +// instance.h : Global state functions. +// + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../client/dbclient.h" +#include "curop.h" +#include "security.h" +#include "cmdline.h" +#include "client.h" + +namespace mongo { + + extern string dbExecCommand; + +#define OPWRITE if( _diaglog.level & 1 ) _diaglog.write((char *) m.data, m.data->len); +#define OPREAD if( _diaglog.level & 2 ) _diaglog.readop((char *) m.data, m.data->len); + + struct DiagLog { + ofstream *f; + /* 0 = off; 1 = writes, 2 = reads, 3 = both + 7 = log a few reads, and all writes. + */ + int level; + DiagLog() : f(0) , level(0) { } + void init() { + if ( ! f && level ){ + log() << "diagLogging = " << level << endl; + stringstream ss; + ss << "diaglog." << hex << time(0); + string name = ss.str(); + f = new ofstream(name.c_str(), ios::out | ios::binary); + if ( ! f->good() ) { + problem() << "couldn't open log stream" << endl; + throw 1717; + } + } + } + /** + * @return old + */ + int setLevel( int newLevel ){ + int old = level; + level = newLevel; + init(); + return old; + } + void flush() { + if ( level ) f->flush(); + } + void write(char *data,int len) { + if ( level & 1 ) f->write(data,len); + } + void readop(char *data, int len) { + if ( level & 2 ) { + bool log = (level & 4) == 0; + OCCASIONALLY log = true; + if ( log ) + f->write(data,len); + } + } + }; + + extern DiagLog _diaglog; + + /* we defer response until we unlock. don't want a blocked socket to + keep things locked. + */ + struct DbResponse { + Message *response; + MSGID responseTo; + DbResponse(Message *r, MSGID rt) : response(r), responseTo(rt) { + } + DbResponse() { + response = 0; + } + ~DbResponse() { + delete response; + } + }; + + static SockAddr unknownAddress( "0.0.0.0", 0 ); + + bool assembleResponse( Message &m, DbResponse &dbresponse, const sockaddr_in &client = unknownAddress.sa ); + + void getDatabaseNames( vector< string > &names ); + +// --- local client --- + + class DBDirectClient : public DBClientBase { + + public: + virtual auto_ptr<DBClientCursor> query(const string &ns, Query query, int nToReturn = 0, int nToSkip = 0, + const BSONObj *fieldsToReturn = 0, int queryOptions = 0); + + virtual bool isFailed() const { + return false; + } + virtual string toString() { + return "DBDirectClient"; + } + virtual string getServerAddress() const{ + return "localhost"; // TODO: should this have the port? + } + virtual bool call( Message &toSend, Message &response, bool assertOk=true ); + virtual void say( Message &toSend ); + virtual void sayPiggyBack( Message &toSend ) { + // don't need to piggy back when connected locally + return say( toSend ); + } + class AlwaysAuthorized : public AuthenticationInfo { + virtual bool isAuthorized( const char *dbname ) { + return true; + } + }; + + /* TODO: this looks bad that auth is set to always. is that really always safe? */ + class SavedContext { + public: + SavedContext() { + _save = dbMutex.atLeastReadLocked(); + + Client *c = currentClient.get(); + oldAuth = c->ai; + // careful, don't want to free this: + c->ai = &always; + + /* it only makes sense to manipulate a pointer - c->database() - if locked. + thus the _saved flag. + */ + if( _save ) { + if ( c->database() ) { + dbMutex.assertAtLeastReadLocked(); + _oldName = c->database()->name; + } + } + } + ~SavedContext() { + Client *c = currentClient.get(); + c->ai = oldAuth; + if( _save ) { + if ( !_oldName.empty() ) { + dbMutex.assertAtLeastReadLocked(); + setClient( _oldName.c_str() ); + } + } + else { + // defensive + cc().clearns(); + } + } + private: + bool _save; + static AlwaysAuthorized always; + AuthenticationInfo *oldAuth; + string _oldName; + }; + }; + + extern int lockFile; + void acquirePathLock(); + +} // namespace mongo diff --git a/db/introspect.cpp b/db/introspect.cpp new file mode 100644 index 0000000..9cb477d --- /dev/null +++ b/db/introspect.cpp @@ -0,0 +1,41 @@ +// introspect.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "introspect.h" +#include "../util/builder.h" +#include "../util/goodies.h" +#include "pdfile.h" +#include "jsobj.h" +#include "pdfile.h" + +namespace mongo { + + void profile(const char *str, + int millis) + { + BSONObjBuilder b; + b.appendDate("ts", jsTime()); + b.append("info", str); + b.append("millis", (double) millis); + BSONObj p = b.done(); + theDataFileMgr.insert(cc().database()->profileName.c_str(), + p.objdata(), p.objsize(), true); + } + +} // namespace mongo diff --git a/db/introspect.h b/db/introspect.h new file mode 100644 index 0000000..1c0fe92 --- /dev/null +++ b/db/introspect.h @@ -0,0 +1,35 @@ +// introspect.h +// system management stuff. + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" +#include "pdfile.h" + +namespace mongo { + + /* --- profiling -------------------------------------------- + do when database->profile is set + */ + + void profile(const char *str, + int millis); + +} // namespace mongo diff --git a/db/javatest.cpp b/db/javatest.cpp new file mode 100644 index 0000000..22f2bdf --- /dev/null +++ b/db/javatest.cpp @@ -0,0 +1,24 @@ +// javatest.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "javajs.h" + +int main() { + JavaJS = new JavaJSImpl(); + javajstest(); +} diff --git a/db/jsobj.cpp b/db/jsobj.cpp new file mode 100644 index 0000000..1a299a5 --- /dev/null +++ b/db/jsobj.cpp @@ -0,0 +1,1636 @@ +/** @file jsobj.cpp - BSON implementation + http://www.mongodb.org/display/DOCS/BSON +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "jsobj.h" +#include "nonce.h" +#include "../util/goodies.h" +#include "../util/base64.h" +#include "../util/md5.hpp" +#include <limits> +#include "../util/unittest.h" +#include "../util/embedded_builder.h" +#include "json.h" +#include "jsobjmanipulator.h" +#include "../util/optime.h" +#include <boost/static_assert.hpp> +#undef assert +#define assert xassert + +// make sure our assumptions are valid +BOOST_STATIC_ASSERT( sizeof(int) == 4 ); +BOOST_STATIC_ASSERT( sizeof(long long) == 8 ); +BOOST_STATIC_ASSERT( sizeof(double) == 8 ); +BOOST_STATIC_ASSERT( sizeof(mongo::Date_t) == 8 ); +BOOST_STATIC_ASSERT( sizeof(mongo::OID) == 12 ); + +namespace mongo { + + BSONElement nullElement; + + ostream& operator<<( ostream &s, const OID &o ) { + s << o.str(); + return s; + } + + IDLabeler GENOID; + BSONObjBuilder& operator<<(BSONObjBuilder& b, IDLabeler& id) { + OID oid; + oid.init(); + b.appendOID("_id", &oid); + return b; + } + + DateNowLabeler DATENOW; + + string BSONElement::toString( bool includeFieldName ) const { + stringstream s; + if ( includeFieldName && type() != EOO ) + s << fieldName() << ": "; + switch ( type() ) { + case EOO: + return "EOO"; + case Date: + s << "new Date(" << date() << ')'; + break; + case RegEx: + { + s << "/" << regex() << '/'; + const char *p = regexFlags(); + if ( p ) s << p; + } + break; + case NumberDouble: + { + stringstream tmp; + tmp.precision( 16 ); + tmp << number(); + string n = tmp.str(); + s << n; + // indicate this is a double: + if( strchr(n.c_str(), '.') == 0 && strchr(n.c_str(), 'E') == 0 && strchr(n.c_str(), 'N') == 0 ) + s << ".0"; + } + break; + case NumberLong: + s << _numberLong(); + break; + case NumberInt: + s << _numberInt(); + break; + case Bool: + s << ( boolean() ? "true" : "false" ); + break; + case Object: + case Array: + s << embeddedObject().toString(); + break; + case Undefined: + s << "undefined"; + break; + case jstNULL: + s << "null"; + break; + case MaxKey: + s << "MaxKey"; + break; + case MinKey: + s << "MinKey"; + break; + case CodeWScope: + s << "CodeWScope( " + << codeWScopeCode() << ", " << codeWScopeObject().toString() << ")"; + break; + case Code: + if ( valuestrsize() > 80 ) + s << string(valuestr()).substr(0, 70) << "..."; + else { + s << valuestr(); + } + break; + case Symbol: + case String: + if ( valuestrsize() > 80 ) + s << '"' << string(valuestr()).substr(0, 70) << "...\""; + else { + s << '"' << valuestr() << '"'; + } + break; + case DBRef: + s << "DBRef('" << valuestr() << "',"; + { + OID *x = (OID *) (valuestr() + valuestrsize()); + s << *x << ')'; + } + break; + case jstOID: + s << "ObjId("; + s << __oid() << ')'; + break; + case BinData: + s << "BinData"; + break; + case Timestamp: + s << "Timestamp " << timestampTime() << "|" << timestampInc(); + break; + default: + s << "?type=" << type(); + break; + } + return s.str(); + } + + string escape( string s ) { + stringstream ret; + for ( string::iterator i = s.begin(); i != s.end(); ++i ) { + switch ( *i ) { + case '"': + ret << "\\\""; + break; + case '\\': + ret << "\\\\"; + break; + case '/': + ret << "\\/"; + break; + case '\b': + ret << "\\b"; + break; + case '\f': + ret << "\\f"; + break; + case '\n': + ret << "\\n"; + break; + case '\r': + ret << "\\r"; + break; + case '\t': + ret << "\\t"; + break; + default: + if ( *i >= 0 && *i <= 0x1f ) { + ret << "\\u"; + ret << hex; + ret.width( 4 ); + ret.fill( '0' ); + ret << int( *i ); + } else { + ret << *i; + } + } + } + return ret.str(); + } + + string BSONElement::jsonString( JsonStringFormat format, bool includeFieldNames ) const { + stringstream s; + if ( includeFieldNames ) + s << '"' << escape( fieldName() ) << "\" : "; + switch ( type() ) { + case String: + case Symbol: + s << '"' << escape( valuestr() ) << '"'; + break; + case NumberLong: + s << _numberLong(); + break; + case NumberInt: + case NumberDouble: + if ( number() >= -numeric_limits< double >::max() && + number() <= numeric_limits< double >::max() ) { + s.precision( 16 ); + s << number(); + } else { + stringstream ss; + ss << "Number " << number() << " cannot be represented in JSON"; + string message = ss.str(); + massert( 10311 , message.c_str(), false ); + } + break; + case Bool: + s << ( boolean() ? "true" : "false" ); + break; + case jstNULL: + s << "null"; + break; + case Object: + s << embeddedObject().jsonString( format ); + break; + case Array: { + if ( embeddedObject().isEmpty() ) { + s << "[]"; + break; + } + s << "[ "; + BSONObjIterator i( embeddedObject() ); + BSONElement e = i.next(); + if ( !e.eoo() ) + while ( 1 ) { + s << e.jsonString( format, false ); + e = i.next(); + if ( e.eoo() ) + break; + s << ", "; + } + s << " ]"; + break; + } + case DBRef: { + OID *x = (OID *) (valuestr() + valuestrsize()); + if ( format == TenGen ) + s << "Dbref( "; + else + s << "{ \"$ref\" : "; + s << '"' << valuestr() << "\", "; + if ( format != TenGen ) + s << "\"$id\" : "; + s << '"' << *x << "\" "; + if ( format == TenGen ) + s << ')'; + else + s << '}'; + break; + } + case jstOID: + if ( format == TenGen ) { + s << "ObjectId( "; + } else { + s << "{ \"$oid\" : "; + } + s << '"' << __oid() << '"'; + if ( format == TenGen ) { + s << " )"; + } else { + s << " }"; + } + break; + case BinData: { + int len = *(int *)( value() ); + BinDataType type = BinDataType( *(char *)( (int *)( value() ) + 1 ) ); + s << "{ \"$binary\" : \""; + char *start = ( char * )( value() ) + sizeof( int ) + 1; + base64::encode( s , start , len ); + s << "\", \"$type\" : \"" << hex; + s.width( 2 ); + s.fill( '0' ); + s << type << dec; + s << "\" }"; + break; + } + case Date: + if ( format == Strict ) + s << "{ \"$date\" : "; + else + s << "Date( "; + s << date(); + if ( format == Strict ) + s << " }"; + else + s << " )"; + break; + case RegEx: + if ( format == Strict ) + s << "{ \"$regex\" : \""; + else + s << "/"; + s << escape( regex() ); + if ( format == Strict ) + s << "\", \"$options\" : \"" << regexFlags() << "\" }"; + else { + s << "/"; + // FIXME Worry about alpha order? + for ( const char *f = regexFlags(); *f; ++f ) + switch ( *f ) { + case 'g': + case 'i': + case 'm': + s << *f; + default: + break; + } + } + break; + + case Code: + s << ascode(); + break; + + case Timestamp: + s << "{ \"t\" : " << timestampTime() << " , \"i\" : " << timestampInc() << " }"; + break; + + default: + stringstream ss; + ss << "Cannot create a properly formatted JSON string with " + << "element: " << toString() << " of type: " << type(); + string message = ss.str(); + massert( 10312 , message.c_str(), false ); + } + return s.str(); + } + + int BSONElement::size( int maxLen ) const { + if ( totalSize >= 0 ) + return totalSize; + + int remain = maxLen - fieldNameSize() - 1; + + int x = 0; + switch ( type() ) { + case EOO: + case Undefined: + case jstNULL: + case MaxKey: + case MinKey: + break; + case Bool: + x = 1; + break; + case NumberInt: + x = 4; + break; + case Timestamp: + case Date: + case NumberDouble: + case NumberLong: + x = 8; + break; + case jstOID: + x = 12; + break; + case Symbol: + case Code: + case String: + massert( 10313 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + x = valuestrsize() + 4; + break; + case CodeWScope: + massert( 10314 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + x = objsize(); + break; + + case DBRef: + massert( 10315 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + x = valuestrsize() + 4 + 12; + break; + case Object: + case Array: + massert( 10316 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + x = objsize(); + break; + case BinData: + massert( 10317 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + x = valuestrsize() + 4 + 1/*subtype*/; + break; + case RegEx: + { + const char *p = value(); + int len1 = ( maxLen == -1 ) ? strlen( p ) : strnlen( p, remain ); + massert( 10318 , "Invalid regex string", len1 != -1 ); + p = p + len1 + 1; + int len2 = ( maxLen == -1 ) ? strlen( p ) : strnlen( p, remain - len1 - 1 ); + massert( 10319 , "Invalid regex options string", len2 != -1 ); + x = len1 + 1 + len2 + 1; + } + break; + default: { + stringstream ss; + ss << "BSONElement: bad type " << (int) type(); + massert( 10320 , ss.str().c_str(),false); + } + } + totalSize = x + fieldNameSize() + 1; // BSONType + + return totalSize; + } + + int BSONElement::getGtLtOp( int def ) const { + const char *fn = fieldName(); + if ( fn[0] == '$' && fn[1] ) { + if ( fn[2] == 't' ) { + if ( fn[1] == 'g' ) { + if ( fn[3] == 0 ) return BSONObj::GT; + else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::GTE; + } + else if ( fn[1] == 'l' ) { + if ( fn[3] == 0 ) return BSONObj::LT; + else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::LTE; + } + } + else if ( fn[1] == 'n' && fn[2] == 'e' && fn[3] == 0) + return BSONObj::NE; + else if ( fn[1] == 'm' && fn[2] == 'o' && fn[3] == 'd' && fn[4] == 0 ) + return BSONObj::opMOD; + else if ( fn[1] == 't' && fn[2] == 'y' && fn[3] == 'p' && fn[4] == 'e' && fn[5] == 0 ) + return BSONObj::opTYPE; + else if ( fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0 ) + return BSONObj::opIN; + else if ( fn[1] == 'n' && fn[2] == 'i' && fn[3] == 'n' && fn[4] == 0 ) + return BSONObj::NIN; + else if ( fn[1] == 'a' && fn[2] == 'l' && fn[3] == 'l' && fn[4] == 0 ) + return BSONObj::opALL; + else if ( fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fn[5] == 0 ) + return BSONObj::opSIZE; + else if ( fn[1] == 'e' ){ + if ( fn[2] == 'x' && fn[3] == 'i' && fn[4] == 's' && fn[5] == 't' && fn[6] == 's' && fn[7] == 0 ) + return BSONObj::opEXISTS; + if ( fn[2] == 'l' && fn[3] == 'e' && fn[4] == 'm' && fn[5] == 'M' && fn[6] == 'a' && fn[7] == 't' && fn[8] == 'c' && fn[9] == 'h' && fn[10] == 0 ) + return BSONObj::opELEM_MATCH; + } + else if ( fn[1] == 'r' && fn[2] == 'e' && fn[3] == 'g' && fn[4] == 'e' && fn[5] == 'x' && fn[6] == 0 ) + return BSONObj::opREGEX; + else if ( fn[1] == 'o' && fn[2] == 'p' && fn[3] == 't' && fn[4] == 'i' && fn[5] == 'o' && fn[6] == 'n' && fn[7] == 's' && fn[8] == 0 ) + return BSONObj::opOPTIONS; + } + return def; + } + + /* wo = "well ordered" */ + int BSONElement::woCompare( const BSONElement &e, + bool considerFieldName ) const { + int lt = (int) canonicalType(); + int rt = (int) e.canonicalType(); + int x = lt - rt; + if( x != 0 && (!isNumber() || !e.isNumber()) ) + return x; + if ( considerFieldName ) { + x = strcmp(fieldName(), e.fieldName()); + if ( x != 0 ) + return x; + } + x = compareElementValues(*this, e); + return x; + } + + /* must be same type when called, unless both sides are #s + */ + int compareElementValues(const BSONElement& l, const BSONElement& r) { + int f; + double x; + + switch ( l.type() ) { + case EOO: + case Undefined: + case jstNULL: + case MaxKey: + case MinKey: + f = l.canonicalType() - r.canonicalType(); + if ( f<0 ) return -1; + return f==0 ? 0 : 1; + case Bool: + return *l.value() - *r.value(); + case Timestamp: + case Date: + if ( l.date() < r.date() ) + return -1; + return l.date() == r.date() ? 0 : 1; + case NumberLong: + if( r.type() == NumberLong ) { + long long L = l._numberLong(); + long long R = r._numberLong(); + if( L < R ) return -1; + if( L == R ) return 0; + return 1; + } + // else fall through + case NumberInt: + case NumberDouble: { + double left = l.number(); + double right = r.number(); + bool lNan = !( left <= numeric_limits< double >::max() && + left >= -numeric_limits< double >::max() ); + bool rNan = !( right <= numeric_limits< double >::max() && + right >= -numeric_limits< double >::max() ); + if ( lNan ) { + if ( rNan ) { + return 0; + } else { + return -1; + } + } else if ( rNan ) { + return 1; + } + x = left - right; + if ( x < 0 ) return -1; + return x == 0 ? 0 : 1; + } + case jstOID: + return memcmp(l.value(), r.value(), 12); + case Code: + case Symbol: + case String: + /* todo: utf version */ + return strcmp(l.valuestr(), r.valuestr()); + case Object: + case Array: + return l.embeddedObject().woCompare( r.embeddedObject() ); + case DBRef: + case BinData: { + int lsz = l.valuesize(); + int rsz = r.valuesize(); + if ( lsz - rsz != 0 ) return lsz - rsz; + return memcmp(l.value(), r.value(), lsz); + } + case RegEx: + { + int c = strcmp(l.regex(), r.regex()); + if ( c ) + return c; + return strcmp(l.regexFlags(), r.regexFlags()); + } + case CodeWScope : { + f = l.canonicalType() - r.canonicalType(); + if ( f ) + return f; + f = strcmp( l.codeWScopeCode() , r.codeWScopeCode() ); + if ( f ) + return f; + f = strcmp( l.codeWScopeScopeData() , r.codeWScopeScopeData() ); + if ( f ) + return f; + return 0; + } + default: + out() << "compareElementValues: bad type " << (int) l.type() << endl; + assert(false); + } + return -1; + } + + void BSONElement::validate() const { + switch( type() ) { + case DBRef: + case Code: + case Symbol: + case String: + massert( 10321 , "Invalid dbref/code/string/symbol size", + valuestrsize() > 0 && + valuestrsize() - 1 == strnlen( valuestr(), valuestrsize() ) ); + break; + case CodeWScope: { + int totalSize = *( int * )( value() ); + massert( 10322 , "Invalid CodeWScope size", totalSize >= 8 ); + int strSizeWNull = *( int * )( value() + 4 ); + massert( 10323 , "Invalid CodeWScope string size", totalSize >= strSizeWNull + 4 + 4 ); + massert( 10324 , "Invalid CodeWScope string size", + strSizeWNull > 0 && + strSizeWNull - 1 == strnlen( codeWScopeCode(), strSizeWNull ) ); + massert( 10325 , "Invalid CodeWScope size", totalSize >= strSizeWNull + 4 + 4 + 4 ); + int objSize = *( int * )( value() + 4 + 4 + strSizeWNull ); + massert( 10326 , "Invalid CodeWScope object size", totalSize == 4 + 4 + strSizeWNull + objSize ); + // Subobject validation handled elsewhere. + } + case Object: + // We expect Object size validation to be handled elsewhere. + default: + break; + } + } + + /* Matcher --------------------------------------*/ + +// If the element is something like: +// a : { $gt : 3 } +// we append +// a : 3 +// else we just append the element. +// + void appendElementHandlingGtLt(BSONObjBuilder& b, const BSONElement& e) { + if ( e.type() == Object ) { + BSONElement fe = e.embeddedObject().firstElement(); + const char *fn = fe.fieldName(); + if ( fn[0] == '$' && fn[1] && fn[2] == 't' ) { + b.appendAs(fe, e.fieldName()); + return; + } + } + b.append(e); + } + + int getGtLtOp(const BSONElement& e) { + if ( e.type() != Object ) + return BSONObj::Equality; + + BSONElement fe = e.embeddedObject().firstElement(); + return fe.getGtLtOp(); + } + + FieldCompareResult compareDottedFieldNames( const string& l , const string& r ){ + size_t lstart = 0; + size_t rstart = 0; + while ( 1 ){ + if ( lstart >= l.size() ){ + if ( rstart >= r.size() ) + return SAME; + return RIGHT_SUBFIELD; + } + if ( rstart >= r.size() ) + return LEFT_SUBFIELD; + + size_t a = l.find( '.' , lstart ); + size_t b = r.find( '.' , rstart ); + + size_t lend = a == string::npos ? l.size() : a; + size_t rend = b == string::npos ? r.size() : b; + + const string& c = l.substr( lstart , lend - lstart ); + const string& d = r.substr( rstart , rend - rstart ); + + int x = c.compare( d ); + + if ( x < 0 ) + return LEFT_BEFORE; + if ( x > 0 ) + return RIGHT_BEFORE; + + lstart = lend + 1; + rstart = rend + 1; + } + } + + /* BSONObj ------------------------------------------------------------*/ + + BSONObj::EmptyObject BSONObj::emptyObject; + + string BSONObj::toString() const { + if ( isEmpty() ) return "{}"; + + stringstream s; + s << "{ "; + BSONObjIterator i(*this); + bool first = true; + while ( 1 ) { + massert( 10327 , "Object does not end with EOO", i.moreWithEOO() ); + BSONElement e = i.next( true ); + massert( 10328 , "Invalid element size", e.size() > 0 ); + massert( 10329 , "Element too large", e.size() < ( 1 << 30 ) ); + int offset = e.rawdata() - this->objdata(); + massert( 10330 , "Element extends past end of object", + e.size() + offset <= this->objsize() ); + e.validate(); + bool end = ( e.size() + offset == this->objsize() ); + if ( e.eoo() ) { + massert( 10331 , "EOO Before end of object", end ); + break; + } + if ( first ) + first = false; + else + s << ", "; + s << e.toString(); + } + s << " }"; + return s.str(); + } + + string BSONObj::md5() const { + md5digest d; + md5_state_t st; + md5_init(&st); + md5_append( &st , (const md5_byte_t*)_objdata , objsize() ); + md5_finish(&st, d); + return digestToString( d ); + } + + string BSONObj::jsonString( JsonStringFormat format ) const { + + if ( isEmpty() ) return "{}"; + + stringstream s; + s << "{ "; + BSONObjIterator i(*this); + BSONElement e = i.next(); + if ( !e.eoo() ) + while ( 1 ) { + s << e.jsonString( format ); + e = i.next(); + if ( e.eoo() ) + break; + s << ", "; + } + s << " }"; + return s.str(); + } + +// todo: can be a little faster if we don't use toString() here. + bool BSONObj::valid() const { + try { + toString(); + } + catch (...) { + return false; + } + return true; + } + + /* well ordered compare */ + int BSONObj::woCompare(const BSONObj &r, const BSONObj &idxKey, + bool considerFieldName) const { + if ( isEmpty() ) + return r.isEmpty() ? 0 : -1; + if ( r.isEmpty() ) + return 1; + + bool ordered = !idxKey.isEmpty(); + + BSONObjIterator i(*this); + BSONObjIterator j(r); + BSONObjIterator k(idxKey); + while ( 1 ) { + // so far, equal... + + BSONElement l = i.next(); + BSONElement r = j.next(); + BSONElement o; + if ( ordered ) + o = k.next(); + if ( l.eoo() ) + return r.eoo() ? 0 : -1; + if ( r.eoo() ) + return 1; + + int x = l.woCompare( r, considerFieldName ); + if ( ordered && o.number() < 0 ) + x = -x; + if ( x != 0 ) + return x; + } + return -1; + } + + BSONObj staticNull = fromjson( "{'':null}" ); + + /* well ordered compare */ + int BSONObj::woSortOrder(const BSONObj& other, const BSONObj& sortKey ) const{ + if ( isEmpty() ) + return other.isEmpty() ? 0 : -1; + if ( other.isEmpty() ) + return 1; + + uassert( 10060 , "woSortOrder needs a non-empty sortKey" , ! sortKey.isEmpty() ); + + BSONObjIterator i(sortKey); + while ( 1 ){ + BSONElement f = i.next(); + if ( f.eoo() ) + return 0; + + BSONElement l = getField( f.fieldName() ); + if ( l.eoo() ) + l = staticNull.firstElement(); + BSONElement r = other.getField( f.fieldName() ); + if ( r.eoo() ) + r = staticNull.firstElement(); + + int x = l.woCompare( r, false ); + if ( f.number() < 0 ) + x = -x; + if ( x != 0 ) + return x; + } + return -1; + } + + + BSONElement BSONObj::getField(const char *name) const { + BSONObjIterator i(*this); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( strcmp(e.fieldName(), name) == 0 ) + return e; + } + return nullElement; + } + + /* return has eoo() true if no match + supports "." notation to reach into embedded objects + */ + BSONElement BSONObj::getFieldDotted(const char *name) const { + BSONElement e = getField( name ); + if ( e.eoo() ) { + const char *p = strchr(name, '.'); + if ( p ) { + string left(name, p-name); + BSONObj sub = getObjectField(left.c_str()); + return sub.isEmpty() ? nullElement : sub.getFieldDotted(p+1); + } + } + + return e; + } + + /* jul09 : 'deep' and this function will be going away in the future - kept only for backward compatibility of datafiles for now. */ + void trueDat( bool *deep ) { + if( deep ) + *deep = true; + } + + void BSONObj::getFieldsDotted(const char *name, BSONElementSet &ret, bool *deep ) const { + BSONElement e = getField( name ); + if ( e.eoo() ) { + const char *p = strchr(name, '.'); + if ( p ) { + string left(name, p-name); + BSONElement e = getField( left ); + if ( e.type() == Array ) { + trueDat( deep ); + BSONObjIterator i( e.embeddedObject() ); + while( i.moreWithEOO() ) { + BSONElement f = i.next(); + if ( f.eoo() ) + break; + if ( f.type() == Object ) + f.embeddedObject().getFieldsDotted(p+1, ret); + } + } else if ( e.type() == Object ) { + e.embeddedObject().getFieldsDotted(p+1, ret); + } + } + } else { + if ( e.type() == Array ) { + trueDat( deep ); + BSONObjIterator i( e.embeddedObject() ); + while( i.moreWithEOO() ) { + BSONElement f = i.next(); + if ( f.eoo() ) + break; + ret.insert( f ); + } + } else { + ret.insert( e ); + } + } + if ( ret.empty() && deep ) + *deep = false; + } + + BSONElement BSONObj::getFieldDottedOrArray(const char *&name) const { + const char *p = strchr(name, '.'); + string left; + if ( p ) { + left = string(name, p-name); + name = p + 1; + } else { + left = string(name); + name = name + strlen(name); + } + BSONElement sub = getField(left.c_str()); + if ( sub.eoo() ) + return nullElement; + else if ( sub.type() == Array || strlen( name ) == 0 ) + return sub; + else if ( sub.type() == Object ) + return sub.embeddedObject().getFieldDottedOrArray( name ); + else + return nullElement; + } + + /* makes a new BSONObj with the fields specified in pattern. + fields returned in the order they appear in pattern. + if any field missing or undefined in the original object, that field + in the output will be null. + + n^2 implementation bad if pattern and object have lots + of fields - normally pattern doesn't so should be fine. + */ + BSONObj BSONObj::extractFieldsDotted(BSONObj pattern) const { + BSONObjBuilder b; + BSONObjIterator i(pattern); + while (i.more()) { + BSONElement e = i.next(); + const char *name = e.fieldName(); + + BSONElement x = getFieldDotted( name ); + if ( x.eoo() || x.type() == Undefined ) { + b.appendNull(name); + } else { + b.appendAs(x, name); + } + } + return b.done(); + } + + /** + sets element field names to empty string + If a field in pattern is missing, it is omitted from the returned + object. + */ + BSONObj BSONObj::extractFieldsUnDotted(BSONObj pattern) const { + BSONObjBuilder b; + BSONObjIterator i(pattern); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + BSONElement x = getField(e.fieldName()); + if ( !x.eoo() ) + b.appendAs(x, ""); + } + return b.obj(); + } + + BSONObj BSONObj::extractFields(const BSONObj& pattern , bool fillWithNull ) const { + BSONObjBuilder b(32); // scanandorder.h can make a zillion of these, so we start the allocation very small + BSONObjIterator i(pattern); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + BSONElement x = getFieldDotted(e.fieldName()); + if ( ! x.eoo() ) + b.appendAs( x, e.fieldName() ); + else if ( fillWithNull ) + b.appendNull( e.fieldName() ); + } + return b.obj(); + } + + BSONObj BSONObj::filterFieldsUndotted( const BSONObj &filter, bool inFilter ) const { + BSONObjBuilder b; + BSONObjIterator i( *this ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + BSONElement x = filter.getField( e.fieldName() ); + if ( ( x.eoo() && !inFilter ) || + ( !x.eoo() && inFilter ) ) + b.append( e ); + } + return b.obj(); + } + + BSONElement BSONObj::getFieldUsingIndexNames(const char *fieldName, const BSONObj &indexKey) const { + BSONObjIterator i( indexKey ); + int j = 0; + while( i.moreWithEOO() ) { + BSONElement f = i.next(); + if ( f.eoo() ) + return BSONElement(); + if ( strcmp( f.fieldName(), fieldName ) == 0 ) + break; + ++j; + } + BSONObjIterator k( *this ); + while( k.moreWithEOO() ) { + BSONElement g = k.next(); + if ( g.eoo() ) + return BSONElement(); + if ( j == 0 ) { + return g; + } + --j; + } + return BSONElement(); + } + + int BSONObj::getIntField(const char *name) const { + BSONElement e = getField(name); + return e.isNumber() ? (int) e.number() : INT_MIN; + } + + bool BSONObj::getBoolField(const char *name) const { + BSONElement e = getField(name); + return e.type() == Bool ? e.boolean() : false; + } + + const char * BSONObj::getStringField(const char *name) const { + BSONElement e = getField(name); + return e.type() == String ? e.valuestr() : ""; + } + + BSONObj BSONObj::getObjectField(const char *name) const { + BSONElement e = getField(name); + BSONType t = e.type(); + return t == Object || t == Array ? e.embeddedObject() : BSONObj(); + } + + int BSONObj::nFields() const { + int n = 0; + BSONObjIterator i(*this); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + n++; + } + return n; + } + + /* grab names of all the fields in this object */ + int BSONObj::getFieldNames(set<string>& fields) const { + int n = 0; + BSONObjIterator i(*this); + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + fields.insert(e.fieldName()); + n++; + } + return n; + } + + /* note: addFields always adds _id even if not specified + returns n added not counting _id unless requested. + */ + int BSONObj::addFields(BSONObj& from, set<string>& fields) { + assert( isEmpty() && !isOwned() ); /* partial implementation for now... */ + + BSONObjBuilder b; + + int N = fields.size(); + int n = 0; + BSONObjIterator i(from); + bool gotId = false; + while ( i.moreWithEOO() ) { + BSONElement e = i.next(); + const char *fname = e.fieldName(); + if ( fields.count(fname) ) { + b.append(e); + ++n; + gotId = gotId || strcmp(fname, "_id")==0; + if ( n == N && gotId ) + break; + } else if ( strcmp(fname, "_id")==0 ) { + b.append(e); + gotId = true; + if ( n == N && gotId ) + break; + } + } + + if ( n ) { + int len; + init( b.decouple(len), true ); + } + + return n; + } + + BSONObj BSONObj::clientReadable() const { + BSONObjBuilder b; + BSONObjIterator i( *this ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + switch( e.type() ) { + case MinKey: { + BSONObjBuilder m; + m.append( "$minElement", 1 ); + b.append( e.fieldName(), m.done() ); + break; + } + case MaxKey: { + BSONObjBuilder m; + m.append( "$maxElement", 1 ); + b.append( e.fieldName(), m.done() ); + break; + } + default: + b.append( e ); + } + } + return b.obj(); + } + + BSONObj BSONObj::replaceFieldNames( const BSONObj &names ) const { + BSONObjBuilder b; + BSONObjIterator i( *this ); + BSONObjIterator j( names ); + BSONElement f = j.moreWithEOO() ? j.next() : BSONObj().firstElement(); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( !f.eoo() ) { + b.appendAs( e, f.fieldName() ); + f = j.next(); + } else { + b.append( e ); + } + } + return b.obj(); + } + + bool BSONObj::okForStorage() const { + BSONObjIterator i( *this ); + while ( i.more() ){ + BSONElement e = i.next(); + const char * name = e.fieldName(); + + if ( strchr( name , '.' ) || + strchr( name , '$' ) ){ + return false; + } + + if ( e.mayEncapsulate() ){ + switch ( e.type() ){ + case Object: + case Array: + if ( ! e.embeddedObject().okForStorage() ) + return false; + break; + case CodeWScope: + if ( ! e.codeWScopeObject().okForStorage() ) + return false; + break; + default: + uassert( 12579, "unhandled cases in BSONObj okForStorage" , 0 ); + } + + } + } + return true; + } + + string BSONObj::hexDump() const { + stringstream ss; + const char *d = objdata(); + int size = objsize(); + for( int i = 0; i < size; ++i ) { + ss.width( 2 ); + ss.fill( '0' ); + ss << hex << (unsigned)(unsigned char)( d[ i ] ) << dec; + if ( ( d[ i ] >= '0' && d[ i ] <= '9' ) || ( d[ i ] >= 'A' && d[ i ] <= 'z' ) ) + ss << '\'' << d[ i ] << '\''; + if ( i != size - 1 ) + ss << ' '; + } + return ss.str(); + } + + ostream& operator<<( ostream &s, const BSONObj &o ) { + return s << o.toString(); + } + + ostream& operator<<( ostream &s, const BSONElement &e ) { + return s << e.toString(); + } + + void nested2dotted(BSONObjBuilder& b, const BSONObj& obj, const string& base){ + BSONObjIterator it(obj); + while (it.more()){ + BSONElement e = it.next(); + if (e.type() == Object){ + string newbase = base + e.fieldName() + "."; + nested2dotted(b, e.embeddedObject(), newbase); + }else{ + string newbase = base + e.fieldName(); + b.appendAs(e, newbase.c_str()); + } + } + } + + void dotted2nested(BSONObjBuilder& b, const BSONObj& obj){ + //use map to sort fields + BSONMap sorted = bson2map(obj); + EmbeddedBuilder eb(&b); + for(BSONMap::const_iterator it=sorted.begin(); it!=sorted.end(); ++it){ + eb.appendAs(it->second, it->first); + } + eb.done(); + } + + /*-- test things ----------------------------------------------------*/ + +#pragma pack(1) + struct MaxKeyData { + MaxKeyData() { + totsize=7; + maxkey=MaxKey; + name=0; + eoo=EOO; + } + int totsize; + char maxkey; + char name; + char eoo; + } maxkeydata; + BSONObj maxKey((const char *) &maxkeydata); + + struct MinKeyData { + MinKeyData() { + totsize=7; + minkey=MinKey; + name=0; + eoo=EOO; + } + int totsize; + char minkey; + char name; + char eoo; + } minkeydata; + BSONObj minKey((const char *) &minkeydata); + + struct JSObj0 { + JSObj0() { + totsize = 5; + eoo = EOO; + } + int totsize; + char eoo; + } js0; +#pragma pack() + + BSONElement::BSONElement() { + data = &js0.eoo; + fieldNameSize_ = 0; + totalSize = 1; + } + + struct BsonUnitTest : public UnitTest { + void testRegex() { + + BSONObjBuilder b; + b.appendRegex("x", "foo"); + BSONObj o = b.done(); + + BSONObjBuilder c; + c.appendRegex("x", "goo"); + BSONObj p = c.done(); + + assert( !o.woEqual( p ) ); + assert( o.woCompare( p ) < 0 ); + + } + void testoid() { + OID id; + id.init(); + // sleepsecs(3); + + OID b; + // goes with sleep above... + // b.init(); + // assert( memcmp(id.getData(), b.getData(), 12) < 0 ); + + b.init( id.str() ); + assert( b == id ); + } + + void testbounds(){ + BSONObj l , r; + { + BSONObjBuilder b; + b.append( "x" , numeric_limits<long long>::max() ); + l = b.obj(); + } + { + BSONObjBuilder b; + b.append( "x" , numeric_limits<double>::max() ); + r = b.obj(); + } + assert( l.woCompare( r ) < 0 ); + assert( r.woCompare( l ) > 0 ); + { + BSONObjBuilder b; + b.append( "x" , numeric_limits<int>::max() ); + l = b.obj(); + } + assert( l.woCompare( r ) < 0 ); + assert( r.woCompare( l ) > 0 ); + } + + void testorder(){ + { + BSONObj x,y,z; + { BSONObjBuilder b; b.append( "x" , (long long)2 ); x = b.obj(); } + { BSONObjBuilder b; b.append( "x" , (int)3 ); y = b.obj(); } + { BSONObjBuilder b; b.append( "x" , (long long)4 ); z = b.obj(); } + assert( x.woCompare( y ) < 0 ); + assert( x.woCompare( z ) < 0 ); + assert( y.woCompare( x ) > 0 ); + assert( z.woCompare( x ) > 0 ); + assert( y.woCompare( z ) < 0 ); + assert( z.woCompare( y ) > 0 ); + } + + { + BSONObj ll,d,i,n,u; + { BSONObjBuilder b; b.append( "x" , (long long)2 ); ll = b.obj(); } + { BSONObjBuilder b; b.append( "x" , (double)2 ); d = b.obj(); } + { BSONObjBuilder b; b.append( "x" , (int)2 ); i = b.obj(); } + { BSONObjBuilder b; b.appendNull( "x" ); n = b.obj(); } + { BSONObjBuilder b; u = b.obj(); } + + assert( ll.woCompare( u ) == d.woCompare( u ) ); + assert( ll.woCompare( u ) == i.woCompare( u ) ); + BSONObj k = BSON( "x" << 1 ); + assert( ll.woCompare( u , k ) == d.woCompare( u , k ) ); + assert( ll.woCompare( u , k ) == i.woCompare( u , k ) ); + + assert( u.woCompare( ll ) == u.woCompare( d ) ); + assert( u.woCompare( ll ) == u.woCompare( i ) ); + assert( u.woCompare( ll , k ) == u.woCompare( d , k ) ); + assert( u.woCompare( ll , k ) == u.woCompare( d , k ) ); + + assert( i.woCompare( n ) == d.woCompare( n ) ); + + assert( ll.woCompare( n ) == d.woCompare( n ) ); + assert( ll.woCompare( n ) == i.woCompare( n ) ); + assert( ll.woCompare( n , k ) == d.woCompare( n , k ) ); + assert( ll.woCompare( n , k ) == i.woCompare( n , k ) ); + + assert( n.woCompare( ll ) == n.woCompare( d ) ); + assert( n.woCompare( ll ) == n.woCompare( i ) ); + assert( n.woCompare( ll , k ) == n.woCompare( d , k ) ); + assert( n.woCompare( ll , k ) == n.woCompare( d , k ) ); + } + + { + BSONObj l,r; + { BSONObjBuilder b; b.append( "x" , "eliot" ); l = b.obj(); } + { BSONObjBuilder b; b.appendSymbol( "x" , "eliot" ); r = b.obj(); } + assert( l.woCompare( r ) == 0 ); + assert( r.woCompare( l ) == 0 ); + } + } + + void run() { + testRegex(); + BSONObjBuilder A,B,C; + A.append("x", 2); + B.append("x", 2.0); + C.append("x", 2.1); + BSONObj a = A.done(); + BSONObj b = B.done(); + BSONObj c = C.done(); + assert( !a.woEqual( b ) ); // comments on operator== + int cmp = a.woCompare(b); + assert( cmp == 0 ); + cmp = a.woCompare(c); + assert( cmp < 0 ); + testoid(); + testbounds(); + testorder(); + } + } bson_unittest; + +/* + BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const char * value ) { + _builder->append( _fieldName , value ); + return *_builder; + } + + BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const int value ) { + _builder->append( _fieldName , value ); + return *_builder; + } + + BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const double value ) { + _builder->append( _fieldName , value ); + return *_builder; + } +*/ + + unsigned OID::_machine = (unsigned) security.getNonceInitSafe(); + void OID::newState(){ + // using fresh Security object to avoid buffered devrandom + _machine = (unsigned) Security().getNonce(); + } + + void OID::init() { + static WrappingInt inc = (unsigned) security.getNonce(); + unsigned t = (unsigned) time(0); + char *T = (char *) &t; + data[0] = T[3]; + data[1] = T[2]; + data[2] = T[1]; + data[3] = T[0]; + + (unsigned&) data[4] = _machine; + + int new_inc = inc.atomicIncrement(); + T = (char *) &new_inc; + char * raw = (char*)&b; + raw[0] = T[3]; + raw[1] = T[2]; + raw[2] = T[1]; + raw[3] = T[0]; + } + + void OID::init( string s ){ + assert( s.size() == 24 ); + const char *p = s.c_str(); + char buf[3]; + buf[2] = 0; + for( int i = 0; i < 12; i++ ) { + buf[0] = p[0]; + buf[1] = p[1]; + p += 2; + stringstream ss(buf); + unsigned z; + ss >> hex >> z; + data[i] = z; + } + +/* + string as = s.substr( 0 , 16 ); + string bs = s.substr( 16 ); + + stringstream ssa(as); + ssa >> hex >> a; + + stringstream ssb(bs); + ssb >> hex >> b; +*/ + } + + Labeler::Label GT( "$gt" ); + Labeler::Label GTE( "$gte" ); + Labeler::Label LT( "$lt" ); + Labeler::Label LTE( "$lte" ); + Labeler::Label NE( "$ne" ); + Labeler::Label SIZE( "$size" ); + + void BSONElementManipulator::initTimestamp() { + massert( 10332 , "Expected CurrentTime type", element_.type() == Timestamp ); + unsigned long long ×tamp = *( reinterpret_cast< unsigned long long* >( value() ) ); + if ( timestamp == 0 ) + timestamp = OpTime::now().asDate(); + } + + + void BSONObjBuilder::appendMinForType( const string& field , int t ){ + switch ( t ){ + case MinKey: appendMinKey( field.c_str() ); return; + case MaxKey: appendMinKey( field.c_str() ); return; + case NumberInt: + case NumberDouble: + case NumberLong: + append( field.c_str() , - numeric_limits<double>::max() ); return; + case jstOID: + { + OID o; + memset(&o, 0, sizeof(o)); + appendOID( field.c_str() , &o); + return; + } + case Bool: appendBool( field.c_str() , false); return; + case Date: appendDate( field.c_str() , 0); return; + case jstNULL: appendNull( field.c_str() ); return; + case Symbol: + case String: append( field.c_str() , "" ); return; + case Object: append( field.c_str() , BSONObj() ); return; + case Array: + appendArray( field.c_str() , BSONObj() ); return; + case BinData: + appendBinData( field.c_str() , 0 , Function , (const char *) 0 ); return; + case Undefined: + appendUndefined( field.c_str() ); return; + case RegEx: appendRegex( field.c_str() , "" ); return; + case DBRef: + { + OID o; + memset(&o, 0, sizeof(o)); + appendDBRef( field.c_str() , "" , o ); + return; + } + case Code: appendCode( field.c_str() , "" ); return; + case CodeWScope: appendCodeWScope( field.c_str() , "" , BSONObj() ); return; + case Timestamp: appendTimestamp( field.c_str() , 0); return; + + }; + log() << "type not support for appendMinElementForType: " << t << endl; + uassert( 10061 , "type not supported for appendMinElementForType" , false ); + } + + void BSONObjBuilder::appendMaxForType( const string& field , int t ){ + switch ( t ){ + case MinKey: appendMaxKey( field.c_str() ); break; + case MaxKey: appendMaxKey( field.c_str() ); break; + case NumberInt: + case NumberDouble: + case NumberLong: + append( field.c_str() , numeric_limits<double>::max() ); + break; + case BinData: + appendMinForType( field , jstOID ); + break; + case jstOID: + { + OID o; + memset(&o, 0xFF, sizeof(o)); + appendOID( field.c_str() , &o); + break; + } + case Undefined: + case jstNULL: + appendMinForType( field , NumberInt ); + case Bool: appendBool( field.c_str() , true); break; + case Date: appendDate( field.c_str() , 0xFFFFFFFFFFFFFFFFLL ); break; + case Symbol: + case String: append( field.c_str() , BSONObj() ); break; + case Code: + case CodeWScope: + appendCodeWScope( field.c_str() , "ZZZ" , BSONObj() ); break; + case Timestamp: + appendTimestamp( field.c_str() , numeric_limits<unsigned long long>::max() ); break; + default: + appendMinForType( field , t + 1 ); + } + } + + const string BSONObjBuilder::numStrs[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + }; + + bool BSONObjBuilder::appendAsNumber( const string& fieldName , const string& data ){ + if ( data.size() == 0 ) + return false; + + unsigned int pos=0; + if ( data[0] == '-' ) + pos++; + + bool hasDec = false; + + for ( ; pos<data.size(); pos++ ){ + if ( isdigit(data[pos]) ) + continue; + + if ( data[pos] == '.' ){ + if ( hasDec ) + return false; + hasDec = true; + continue; + } + + return false; + } + + if ( hasDec ){ + double d = atof( data.c_str() ); + append( fieldName.c_str() , d ); + return true; + } + + if ( data.size() < 8 ){ + append( fieldName , atoi( data.c_str() ) ); + return true; + } + + try { + long long num = boost::lexical_cast<long long>( data ); + append( fieldName , num ); + return true; + } + catch(bad_lexical_cast &){ + return false; + } + + } + + + int BSONElementFieldSorter( const void * a , const void * b ){ + const char * x = *((const char**)a); + const char * y = *((const char**)b); + x++; y++; + return strcmp( x , y ); + } + + BSONObjIteratorSorted::BSONObjIteratorSorted( const BSONObj& o ){ + _nfields = o.nFields(); + _fields = new const char*[_nfields]; + int x = 0; + BSONObjIterator i( o ); + while ( i.more() ){ + _fields[x++] = i.next().rawdata(); + assert( _fields[x-1] ); + } + assert( x == _nfields ); + qsort( _fields , _nfields , sizeof(char*) , BSONElementFieldSorter ); + _cur = 0; + } + + +} // namespace mongo diff --git a/db/jsobj.h b/db/jsobj.h new file mode 100644 index 0000000..4030122 --- /dev/null +++ b/db/jsobj.h @@ -0,0 +1,1869 @@ +/** @file jsobj.h + BSON classes +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + BSONObj and its helpers + + "BSON" stands for "binary JSON" -- ie a binary way to represent objects that would be + represented in JSON (plus a few extensions useful for databases & other languages). + + http://www.mongodb.org/display/DOCS/BSON +*/ + +#pragma once + +#include "../stdafx.h" +#include "../util/builder.h" +#include "../util/optime.h" +#include "boost/utility.hpp" + +#include <set> + +namespace mongo { + + class BSONObj; + struct BSONArray; // empty subclass of BSONObj useful for overloading + class BSONElement; + class Record; + class BSONObjBuilder; + class BSONArrayBuilder; + class BSONObjBuilderValueStream; + +#pragma pack(1) + + /** + the complete list of valid BSON types + */ + enum BSONType { + /** smaller than all other types */ + MinKey=-1, + /** end of object */ + EOO=0, + /** double precision floating point value */ + NumberDouble=1, + /** character string, stored in utf8 */ + String=2, + /** an embedded object */ + Object=3, + /** an embedded array */ + Array=4, + /** binary data */ + BinData=5, + /** Undefined type */ + Undefined=6, + /** ObjectId */ + jstOID=7, + /** boolean type */ + Bool=8, + /** date type */ + Date=9, + /** null type */ + jstNULL=10, + /** regular expression, a pattern with options */ + RegEx=11, + /** deprecated / will be redesigned */ + DBRef=12, + /** deprecated / use CodeWScope */ + Code=13, + /** a programming language (e.g., Python) symbol */ + Symbol=14, + /** javascript code that can execute on the database server, with SavedContext */ + CodeWScope=15, + /** 32 bit signed integer */ + NumberInt = 16, + /** Updated to a Date with value next OpTime on insert */ + Timestamp = 17, + /** 64 bit integer */ + NumberLong = 18, + /** max type that is not MaxKey */ + JSTypeMax=18, + /** larger than all other types */ + MaxKey=127 + }; + + /* subtypes of BinData. + bdtCustom and above are ones that the JS compiler understands, but are + opaque to the database. + */ + enum BinDataType { Function=1, ByteArray=2, bdtUUID = 3, MD5Type=5, bdtCustom=128 }; + + /** Object ID type. + BSON objects typically have an _id field for the object id. This field should be the first + member of the object when present. class OID is a special type that is a 12 byte id which + is likely to be unique to the system. You may also use other types for _id's. + When _id field is missing from a BSON object, on an insert the database may insert one + automatically in certain circumstances. + + Warning: You must call OID::newState() after a fork(). + */ + class OID { + union { + struct{ + long long a; + unsigned b; + }; + unsigned char data[12]; + }; + static unsigned _machine; + public: + /** call this after a fork */ + static void newState(); + + /** initialize to 'null' */ + void clear() { a = 0; b = 0; } + + const unsigned char *getData() const { return data; } + + bool operator==(const OID& r) { + return a==r.a&&b==r.b; + } + bool operator!=(const OID& r) { + return a!=r.a||b!=r.b; + } + + /** The object ID output as 24 hex digits. */ + string str() const { + stringstream s; + s << hex; + // s.fill( '0' ); + // s.width( 2 ); + // fill wasn't working so doing manually... + for( int i = 0; i < 8; i++ ) { + unsigned u = data[i]; + if( u < 16 ) s << '0'; + s << u; + } + const unsigned char * raw = (const unsigned char*)&b; + for( int i = 0; i < 4; i++ ) { + unsigned u = raw[i]; + if( u < 16 ) s << '0'; + s << u; + } + /* + s.width( 16 ); + s << a; + s.width( 8 ); + s << b; + s << dec; + */ + return s.str(); + } + + /** + sets the contents to a new oid / randomized value + */ + void init(); + + /** Set to the hex string value specified. */ + void init( string s ); + + }; + ostream& operator<<( ostream &s, const OID &o ); + + /** Formatting mode for generating JSON from BSON. + See <http://mongodb.onconfluence.com/display/DOCS/Mongo+Extended+JSON> + for details. + */ + enum JsonStringFormat { + /** strict RFC format */ + Strict, + /** 10gen format, which is close to JS format. This form is understandable by + javascript running inside the Mongo server via eval() */ + TenGen, + /** Javascript JSON compatible */ + JS + }; + + /* l and r MUST have same type when called: check that first. */ + int compareElementValues(const BSONElement& l, const BSONElement& r); + +#pragma pack() + + /* internals + <type><fieldName ><value> + -------- size() ------------ + -fieldNameSize- + value() + type() + */ + /** BSONElement represents an "element" in a BSONObj. So for the object { a : 3, b : "abc" }, + 'a : 3' is the first element (key+value). + + The BSONElement object points into the BSONObj's data. Thus the BSONObj must stay in scope + for the life of the BSONElement. + */ + class BSONElement { + friend class BSONObjIterator; + friend class BSONObj; + public: + string toString( bool includeFieldName = true ) const; + operator string() const { return toString(); } + string jsonString( JsonStringFormat format, bool includeFieldNames = true ) const; + + /** Returns the type of the element */ + BSONType type() const { + return (BSONType) *data; + } + + /** returns the tyoe of the element fixed for the main type + the main purpose is numbers. any numeric type will return NumberDouble + Note: if the order changes, indexes have to be re-built or than can be corruption + */ + int canonicalType() const { + BSONType t = type(); + switch ( t ){ + case MinKey: + case MaxKey: + return t; + case EOO: + case Undefined: + return 0; + case jstNULL: + return 5; + case NumberDouble: + case NumberInt: + case NumberLong: + return 10; + case String: + case Symbol: + return 15; + case Object: + return 20; + case Array: + return 25; + case BinData: + return 30; + case jstOID: + return 35; + case Bool: + return 40; + case Date: + case Timestamp: + return 45; + case RegEx: + return 50; + case DBRef: + return 55; + case Code: + return 60; + case CodeWScope: + return 65; + default: + assert(0); + return -1; + } + } + + /** Indicates if it is the end-of-object element, which is present at the end of + every BSON object. + */ + bool eoo() const { + return type() == EOO; + } + + /** Size of the element. + @param maxLen If maxLen is specified, don't scan more than maxLen bytes to calculate size. + */ + int size( int maxLen = -1 ) const; + + /** Wrap this element up as a singleton object. */ + BSONObj wrap() const; + + /** Wrap this element up as a singleton object with a new name. */ + BSONObj wrap( const char* newName) const; + + /** field name of the element. e.g., for + name : "Joe" + "name" is the fieldname + */ + const char * fieldName() const { + if ( eoo() ) return ""; // no fieldname for it. + return data + 1; + } + + /** raw data of the element's value (so be careful). */ + const char * value() const { + return (data + fieldNameSize() + 1); + } + /** size in bytes of the element's value (when applicable). */ + int valuesize() const { + return size() - fieldNameSize() - 1; + } + + bool isBoolean() const { + return type() == Bool; + } + + /** @return value of a boolean element. + You must assure element is a boolean before + calling. */ + bool boolean() const { + return *value() ? true : false; + } + + /** Retrieve a java style date value from the element. + Ensure element is of type Date before calling. + */ + Date_t date() const { + return *reinterpret_cast< const Date_t* >( value() ); + } + + /** Convert the value to boolean, regardless of its type, in a javascript-like fashion + (i.e., treat zero and null as false). + */ + bool trueValue() const { + switch( type() ) { + case NumberLong: + return *reinterpret_cast< const long long* >( value() ) != 0; + case NumberDouble: + return *reinterpret_cast< const double* >( value() ) != 0; + case NumberInt: + return *reinterpret_cast< const int* >( value() ) != 0; + case Bool: + return boolean(); + case EOO: + case jstNULL: + case Undefined: + return false; + + default: + ; + } + return true; + } + + /** True if element is of a numeric type. */ + bool isNumber() const { + switch( type() ) { + case NumberLong: + case NumberDouble: + case NumberInt: + return true; + default: + return false; + } + } + + bool isSimpleType() const { + switch( type() ){ + case NumberLong: + case NumberDouble: + case NumberInt: + case String: + case Bool: + case Date: + case jstOID: + return true; + default: + return false; + } + } + + /** Return double value for this field. MUST be NumberDouble type. */ + double _numberDouble() const {return *reinterpret_cast< const double* >( value() ); } + /** Return double value for this field. MUST be NumberInt type. */ + int _numberInt() const {return *reinterpret_cast< const int* >( value() ); } + /** Return double value for this field. MUST be NumberLong type. */ + long long _numberLong() const {return *reinterpret_cast< const long long* >( value() ); } + + /** Retrieve int value for the element safely. Zero returned if not a number. */ + int numberInt() const { + switch( type() ) { + case NumberDouble: + return (int) _numberDouble(); + case NumberInt: + return _numberInt(); + case NumberLong: + return (int) _numberLong(); + default: + return 0; + } + } + + /** Retrieve long value for the element safely. Zero returned if not a number. */ + long long numberLong() const { + switch( type() ) { + case NumberDouble: + return (long long) _numberDouble(); + case NumberInt: + return _numberInt(); + case NumberLong: + return _numberLong(); + default: + return 0; + } + } + + /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. + NOTE: casts to double, data loss may occur with large (>52 bit) NumberLong values. + */ + double numberDouble() const { + switch( type() ) { + case NumberDouble: + return _numberDouble(); + case NumberInt: + return *reinterpret_cast< const int* >( value() ); + case NumberLong: + return (double) *reinterpret_cast< const long long* >( value() ); + default: + return 0; + } + } + /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. + NOTE: casts to double, data loss may occur with large (>52 bit) NumberLong values. + */ + double number() const { return numberDouble(); } + + /** Retrieve the object ID stored in the object. + You must ensure the element is of type jstOID first. */ + const OID &__oid() const { + return *reinterpret_cast< const OID* >( value() ); + } + + /** True if element is null. */ + bool isNull() const { + return type() == jstNULL; + } + + /** Size (length) of a string element. + You must assure of type String first. */ + int valuestrsize() const { + return *reinterpret_cast< const int* >( value() ); + } + + // for objects the size *includes* the size of the size field + int objsize() const { + return *reinterpret_cast< const int* >( value() ); + } + + /** Get a string's value. Also gives you start of the real data for an embedded object. + You must assure data is of an appropriate type first -- see also valuestrsafe(). + */ + const char * valuestr() const { + return value() + 4; + } + + /** Get the string value of the element. If not a string returns "". */ + const char *valuestrsafe() const { + return type() == String ? valuestr() : ""; + } + /** Get the string value of the element. If not a string returns "". */ + string str() const { return valuestrsafe(); } + + /** Get javascript code of a CodeWScope data element. */ + const char * codeWScopeCode() const { + return value() + 8; + } + /** Get the scope SavedContext of a CodeWScope data element. */ + const char * codeWScopeScopeData() const { + // TODO fix + return codeWScopeCode() + strlen( codeWScopeCode() ) + 1; + } + + /** Get the embedded object this element holds. */ + BSONObj embeddedObject() const; + + /* uasserts if not an object */ + BSONObj embeddedObjectUserCheck(); + + BSONObj codeWScopeObject() const; + + string ascode() const { + switch( type() ){ + case String: + case Code: + return valuestr(); + case CodeWScope: + return codeWScopeCode(); + default: + log() << "can't convert type: " << (int)(type()) << " to code" << endl; + } + uassert( 10062 , "not code" , 0 ); + return ""; + } + + /** Get binary data. Element must be of type BinData */ + const char *binData(int& len) const { + // BinData: <int len> <byte subtype> <byte[len] data> + assert( type() == BinData ); + len = valuestrsize(); + return value() + 5; + } + + BinDataType binDataType() const { + // BinData: <int len> <byte subtype> <byte[len] data> + assert( type() == BinData ); + char c = (value() + 4)[0]; + return (BinDataType)c; + } + + /** Retrieve the regex string for a Regex element */ + const char *regex() const { + assert(type() == RegEx); + return value(); + } + + /** Retrieve the regex flags (options) for a Regex element */ + const char *regexFlags() const { + const char *p = regex(); + return p + strlen(p) + 1; + } + + /** like operator== but doesn't check the fieldname, + just the value. + */ + bool valuesEqual(const BSONElement& r) const { + switch( type() ) { + case NumberLong: + return _numberLong() == r.numberLong() && r.isNumber(); + case NumberDouble: + return _numberDouble() == r.number() && r.isNumber(); + case NumberInt: + return _numberInt() == r.numberInt() && r.isNumber(); + default: + ; + } + bool match= valuesize() == r.valuesize() && + memcmp(value(),r.value(),valuesize()) == 0; + return match && canonicalType() == r.canonicalType(); + } + + /** Returns true if elements are equal. */ + bool operator==(const BSONElement& r) const { + if ( strcmp(fieldName(), r.fieldName()) != 0 ) + return false; + return valuesEqual(r); + } + + + /** Well ordered comparison. + @return <0: l<r. 0:l==r. >0:l>r + order by type, field name, and field value. + If considerFieldName is true, pay attention to the field name. + */ + int woCompare( const BSONElement &e, bool considerFieldName = true ) const; + + const char * rawdata() const { + return data; + } + + /** 0 == Equality, just not defined yet */ + int getGtLtOp( int def = 0 ) const; + + /** Constructs an empty element */ + BSONElement(); + + /** Check that data is internally consistent. */ + void validate() const; + + /** True if this element may contain subobjects. */ + bool mayEncapsulate() const { + return type() == Object || + type() == Array || + type() == CodeWScope; + } + + Date_t timestampTime() const{ + unsigned long long t = ((unsigned int*)(value() + 4 ))[0]; + return t * 1000; + } + unsigned int timestampInc() const{ + return ((unsigned int*)(value() ))[0]; + } + + const char * dbrefNS() const { + uassert( 10063 , "not a dbref" , type() == DBRef ); + return value() + 4; + } + + const OID& dbrefOID() const { + uassert( 10064 , "not a dbref" , type() == DBRef ); + const char * start = value(); + start += 4 + *reinterpret_cast< const int* >( start ); + return *reinterpret_cast< const OID* >( start ); + } + + bool operator<( const BSONElement& other ) const { + int x = (int)canonicalType() - (int)other.canonicalType(); + if ( x < 0 ) return true; + else if ( x > 0 ) return false; + return compareElementValues(*this,other) < 0; + } + + // If maxLen is specified, don't scan more than maxLen bytes. + BSONElement(const char *d, int maxLen = -1) : data(d) { + fieldNameSize_ = -1; + if ( eoo() ) + fieldNameSize_ = 0; + else { + if ( maxLen != -1 ) { + int size = strnlen( fieldName(), maxLen - 1 ); + massert( 10333 , "Invalid field name", size != -1 ); + fieldNameSize_ = size + 1; + } + } + totalSize = -1; + } + private: + const char *data; + mutable int fieldNameSize_; // cached value + int fieldNameSize() const { + if ( fieldNameSize_ == -1 ) + fieldNameSize_ = strlen( fieldName() ) + 1; + return fieldNameSize_; + } + mutable int totalSize; /* caches the computed size */ + }; + + int getGtLtOp(const BSONElement& e); + + struct BSONElementCmpWithoutField { + bool operator()( const BSONElement &l, const BSONElement &r ) const { + return l.woCompare( r, false ); + } + }; + + typedef set< BSONElement, BSONElementCmpWithoutField > BSONElementSet; + + /** + C++ representation of a "BSON" object -- that is, an extended JSON-style + object in a binary representation. + + Note that BSONObj's have a smart pointer capability built in -- so you can + pass them around by value. The reference counts used to implement this + do not use locking, so copying and destroying BSONObj's are not thread-safe + operations. + + BSON object format: + + \code + <unsigned totalSize> {<byte BSONType><cstring FieldName><Data>}* EOO + + totalSize includes itself. + + Data: + Bool: <byte> + EOO: nothing follows + Undefined: nothing follows + OID: an OID object + NumberDouble: <double> + NumberInt: <int32> + String: <unsigned32 strsizewithnull><cstring> + Date: <8bytes> + Regex: <cstring regex><cstring options> + Object: a nested object, leading with its entire size, which terminates with EOO. + Array: same as object + DBRef: <strlen> <cstring ns> <oid> + DBRef: a database reference: basically a collection name plus an Object ID + BinData: <int len> <byte subtype> <byte[len] data> + Code: a function (not a closure): same format as String. + Symbol: a language symbol (say a python symbol). same format as String. + Code With Scope: <total size><String><Object> + \endcode + */ + class BSONObj { + friend class BSONObjIterator; + class Holder { + public: + Holder( const char *objdata ) : + _objdata( objdata ) { + } + ~Holder() { + free((void *)_objdata); + _objdata = 0; + } + private: + const char *_objdata; + }; + const char *_objdata; + boost::shared_ptr< Holder > _holder; + void init(const char *data, bool ifree) { + if ( ifree ) + _holder.reset( new Holder( data ) ); + _objdata = data; + if ( ! isValid() ){ + stringstream ss; + ss << "Invalid BSONObj spec size: " << objsize(); + string s = ss.str(); + massert( 10334 , s , 0 ); + } + } +#pragma pack(1) + static struct EmptyObject { + EmptyObject() { + len = 5; + jstype = EOO; + } + int len; + char jstype; + } emptyObject; +#pragma pack() + public: + /** Construct a BSONObj from data in the proper format. + @param ifree true if the BSONObj should free() the msgdata when + it destructs. + */ + explicit BSONObj(const char *msgdata, bool ifree = false) { + init(msgdata, ifree); + } + BSONObj(const Record *r); + /** Construct an empty BSONObj -- that is, {}. */ + BSONObj() : _objdata( reinterpret_cast< const char * >( &emptyObject ) ) { } + // defensive + ~BSONObj() { _objdata = 0; } + + void appendSelfToBufBuilder(BufBuilder& b) const { + assert( objsize() ); + b.append(reinterpret_cast<const void *>( objdata() ), objsize()); + } + + /** Readable representation of a BSON object in an extended JSON-style notation. + This is an abbreviated representation which might be used for logging. + */ + string toString() const; + operator string() const { return toString(); } + + /** Properly formatted JSON string. */ + string jsonString( JsonStringFormat format = Strict ) const; + + /** note: addFields always adds _id even if not specified */ + int addFields(BSONObj& from, set<string>& fields); /* returns n added */ + + /** returns # of top level fields in the object + note: iterates to count the fields + */ + int nFields() const; + + /** adds the field names to the fields set. does NOT clear it (appends). */ + int getFieldNames(set<string>& fields) const; + + /** return has eoo() true if no match + supports "." notation to reach into embedded objects + */ + BSONElement getFieldDotted(const char *name) const; + /** Like getFieldDotted(), but expands multikey arrays and returns all matching objects + */ + void getFieldsDotted(const char *name, BSONElementSet &ret, bool *deep = 0) const; + /** Like getFieldDotted(), but returns first array encountered while traversing the + dotted fields of name. The name variable is updated to represent field + names with respect to the returned element. */ + BSONElement getFieldDottedOrArray(const char *&name) const; + + /** Get the field of the specified name. eoo() is true on the returned + element if not found. + */ + BSONElement getField(const string name) const { + return getField( name.c_str() ); + }; + + /** Get the field of the specified name. eoo() is true on the returned + element if not found. + */ + BSONElement getField(const char *name) const; /* return has eoo() true if no match */ + + /** Get the field of the specified name. eoo() is true on the returned + element if not found. + */ + BSONElement operator[] (const char *field) const { + return getField(field); + } + + BSONElement operator[] (const string& field) const { + return getField(field); + } + + BSONElement operator[] (int field) const { + stringstream ss; + ss << field; + string s = ss.str(); + return getField(s.c_str()); + } + + /** @return true if field exists */ + bool hasField( const char * name )const { + return ! getField( name ).eoo(); + } + + /** @return "" if DNE or wrong type */ + const char * getStringField(const char *name) const; + + /** @return subobject of the given name */ + BSONObj getObjectField(const char *name) const; + + /** @return INT_MIN if not present - does some type conversions */ + int getIntField(const char *name) const; + + /** @return false if not present */ + bool getBoolField(const char *name) const; + + /** makes a new BSONObj with the fields specified in pattern. + fields returned in the order they appear in pattern. + if any field is missing or undefined in the object, that field in the + output will be null. + + sets output field names to match pattern field names. + If an array is encountered while scanning the dotted names in pattern, + that field is treated as missing. + */ + BSONObj extractFieldsDotted(BSONObj pattern) const; + + /** + sets element field names to empty string + If a field in pattern is missing, it is omitted from the returned + object. + */ + BSONObj extractFieldsUnDotted(BSONObj pattern) const; + + /** extract items from object which match a pattern object. + e.g., if pattern is { x : 1, y : 1 }, builds an object with + x and y elements of this object, if they are present. + returns elements with original field names + */ + BSONObj extractFields(const BSONObj &pattern , bool fillWithNull=false) const; + + BSONObj filterFieldsUndotted(const BSONObj &filter, bool inFilter) const; + + BSONElement getFieldUsingIndexNames(const char *fieldName, const BSONObj &indexKey) const; + + /** @return the raw data of the object */ + const char *objdata() const { + return _objdata; + } + /** @return total size of the BSON object in bytes */ + int objsize() const { + return *(reinterpret_cast<const int*>(objdata())); + } + + bool isValid(); + + /** @return if the user is a valid user doc + criter: isValid() no . or $ field names + */ + bool okForStorage() const; + + /** @return true if object is empty -- i.e., {} */ + bool isEmpty() const { + return objsize() <= 5; + } + + void dump() const { + out() << hex; + const char *p = objdata(); + for ( int i = 0; i < objsize(); i++ ) { + out() << i << '\t' << ( 0xff & ( (unsigned) *p ) ); + if ( *p >= 'A' && *p <= 'z' ) + out() << '\t' << *p; + out() << endl; + p++; + } + } + + // Alternative output format + string hexDump() const; + + /**wo='well ordered'. fields must be in same order in each object. + Ordering is with respect to the signs of the elements in idxKey. + @return <0 if l<r. 0 if l==r. >0 if l>r + */ + int woCompare(const BSONObj& r, const BSONObj &idxKey = BSONObj(), + bool considerFieldName=true) const; + + int woSortOrder( const BSONObj& r , const BSONObj& sortKey ) const; + + /** This is "shallow equality" -- ints and doubles won't match. for a + deep equality test use woCompare (which is slower). + */ + bool woEqual(const BSONObj& r) const { + int os = objsize(); + if ( os == r.objsize() ) { + return (os == 0 || memcmp(objdata(),r.objdata(),os)==0); + } + return false; + } + + /** @return first field of the object */ + BSONElement firstElement() const { + return BSONElement(objdata() + 4); + } + + /** @return element with fieldname "name". returnvalue.eoo() is true if not found */ + BSONElement findElement(const char *name) const; + + /** @return element with fieldname "name". returnvalue.eoo() is true if not found */ + BSONElement findElement(string name) const { + return findElement(name.c_str()); + } + + /** @return true if field exists in the object */ + bool hasElement(const char *name) const; + + /** Get the _id field from the object. For good performance drivers should + assure that _id is the first element of the object; however, correct operation + is assured regardless. + @return true if found + */ + bool getObjectID(BSONElement& e) const; + + /** makes a copy of the object. + */ + BSONObj copy() const; + + /* make sure the data buffer is under the control of BSONObj's and not a remote buffer */ + BSONObj getOwned() const{ + if ( !isOwned() ) + return copy(); + return *this; + } + bool isOwned() const { return _holder.get() != 0; } + + /** @return A hash code for the object */ + int hash() const { + unsigned x = 0; + const char *p = objdata(); + for ( int i = 0; i < objsize(); i++ ) + x = x * 131 + p[i]; + return (x & 0x7fffffff) | 0x8000000; // must be > 0 + } + + // Return a version of this object where top level elements of types + // that are not part of the bson wire protocol are replaced with + // string identifier equivalents. + // TODO Support conversion of element types other than min and max. + BSONObj clientReadable() const; + + /** Return new object with the field names replaced by those in the + passed object. */ + BSONObj replaceFieldNames( const BSONObj &obj ) const; + + /** true unless corrupt */ + bool valid() const; + + string md5() const; + + bool operator==( const BSONObj& other ){ + return woCompare( other ) == 0; + } + + enum MatchType { + Equality = 0, + LT = 0x1, + LTE = 0x3, + GTE = 0x6, + GT = 0x4, + opIN = 0x8, // { x : { $in : [1,2,3] } } + NE = 0x9, + opSIZE = 0x0A, + opALL = 0x0B, + NIN = 0x0C, + opEXISTS = 0x0D, + opMOD = 0x0E, + opTYPE = 0x0F, + opREGEX = 0x10, + opOPTIONS = 0x11, + opELEM_MATCH = 0x12 + }; + }; + ostream& operator<<( ostream &s, const BSONObj &o ); + ostream& operator<<( ostream &s, const BSONElement &e ); + + struct BSONArray: BSONObj { + // Don't add anything other than forwarding constructors!!! + BSONArray(): BSONObj() {} + explicit BSONArray(const BSONObj& obj): BSONObj(obj) {} + }; + + class BSONObjCmp { + public: + BSONObjCmp( const BSONObj &_order = BSONObj() ) : order( _order ) {} + bool operator()( const BSONObj &l, const BSONObj &r ) const { + return l.woCompare( r, order ) < 0; + } + private: + BSONObj order; + }; + + class BSONObjCmpDefaultOrder : public BSONObjCmp { + public: + BSONObjCmpDefaultOrder() : BSONObjCmp( BSONObj() ) {} + }; + + typedef set< BSONObj, BSONObjCmpDefaultOrder > BSONObjSetDefaultOrder; + + enum FieldCompareResult { + LEFT_SUBFIELD = -2, + LEFT_BEFORE = -1, + SAME = 0, + RIGHT_BEFORE = 1 , + RIGHT_SUBFIELD = 2 + }; + + FieldCompareResult compareDottedFieldNames( const string& l , const string& r ); + +/** Use BSON macro to build a BSONObj from a stream + + e.g., + BSON( "name" << "joe" << "age" << 33 ) + + with auto-generated object id: + BSON( GENOID << "name" << "joe" << "age" << 33 ) + + The labels GT, GTE, LT, LTE, NE can be helpful for stream-oriented construction + of a BSONObj, particularly when assembling a Query. For example, + BSON( "a" << GT << 23.4 << NE << 30 << "b" << 2 ) produces the object + { a: { \$gt: 23.4, \$ne: 30 }, b: 2 }. +*/ +#define BSON(x) (( mongo::BSONObjBuilder() << x ).obj()) + +/** Use BSON_ARRAY macro like BSON macro, but without keys + + BSONArray arr = BSON_ARRAY( "hello" << 1 << BSON( "foo" << BSON_ARRAY( "bar" << "baz" << "qux" ) ) ); + + */ +#define BSON_ARRAY(x) (( mongo::BSONArrayBuilder() << x ).arr()) + + /* Utility class to auto assign object IDs. + Example: + cout << BSON( GENOID << "z" << 3 ); // { _id : ..., z : 3 } + */ + extern struct IDLabeler { } GENOID; + BSONObjBuilder& operator<<(BSONObjBuilder& b, IDLabeler& id); + + /* Utility class to add a Date element with the current time + Example: + cout << BSON( "created" << DATENOW ); // { created : "2009-10-09 11:41:42" } + */ + extern struct DateNowLabeler { } DATENOW; + + // Utility class to implement GT, GTE, etc as described above. + class Labeler { + public: + struct Label { + Label( const char *l ) : l_( l ) {} + const char *l_; + }; + Labeler( const Label &l, BSONObjBuilderValueStream *s ) : l_( l ), s_( s ) {} + template<class T> + BSONObjBuilder& operator<<( T value ); + + /* the value of the element e is appended i.e. for + "age" << GT << someElement + one gets + { age : { $gt : someElement's value } } + */ + BSONObjBuilder& operator<<( const BSONElement& e ); + private: + const Label &l_; + BSONObjBuilderValueStream *s_; + }; + + extern Labeler::Label GT; + extern Labeler::Label GTE; + extern Labeler::Label LT; + extern Labeler::Label LTE; + extern Labeler::Label NE; + extern Labeler::Label SIZE; + + // Utility class to implement BSON( key << val ) as described above. + class BSONObjBuilderValueStream : public boost::noncopyable { + public: + friend class Labeler; + BSONObjBuilderValueStream( BSONObjBuilder * builder ); + + BSONObjBuilder& operator<<( const BSONElement& e ); + + template<class T> + BSONObjBuilder& operator<<( T value ); + + BSONObjBuilder& operator<<(DateNowLabeler& id); + + Labeler operator<<( const Labeler::Label &l ); + + void endField( const char *nextFieldName = 0 ); + bool subobjStarted() const { return _fieldName != 0; } + + private: + const char * _fieldName; + BSONObjBuilder * _builder; + + bool haveSubobj() const { return _subobj.get() != 0; } + BSONObjBuilder *subobj(); + auto_ptr< BSONObjBuilder > _subobj; + }; + + /** + utility for creating a BSONObj + */ + class BSONObjBuilder : boost::noncopyable { + public: + /** @param initsize this is just a hint as to the final size of the object */ + BSONObjBuilder(int initsize=512) : b(buf_), buf_(initsize), offset_( 0 ), s_( this ) { + b.skip(4); /*leave room for size field*/ + } + + /** @param baseBuilder construct a BSONObjBuilder using an existing BufBuilder */ + BSONObjBuilder( BufBuilder &baseBuilder ) : b( baseBuilder ), buf_( 0 ), offset_( baseBuilder.len() ), s_( this ) { + b.skip( 4 ); + } + + /** add all the fields from the object specified to this object */ + BSONObjBuilder& appendElements(BSONObj x); + + /** append element to the object we are building */ + void append( const BSONElement& e) { + assert( !e.eoo() ); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. + b.append((void*) e.rawdata(), e.size()); + } + + /** append an element but with a new name */ + void appendAs(const BSONElement& e, const char *as) { + assert( !e.eoo() ); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. + b.append((char) e.type()); + b.append(as); + b.append((void *) e.value(), e.valuesize()); + } + + void appendAs(const BSONElement& e, const string& as) { + appendAs( e , as.c_str() ); + } + + + /** add a subobject as a member */ + void append(const char *fieldName, BSONObj subObj) { + b.append((char) Object); + b.append(fieldName); + b.append((void *) subObj.objdata(), subObj.objsize()); + } + + void append(const string& fieldName , BSONObj subObj) { + append( fieldName.c_str() , subObj ); + } + + /** add header for a new subobject and return bufbuilder for writing to + the subobject's body */ + BufBuilder &subobjStart(const char *fieldName) { + b.append((char) Object); + b.append(fieldName); + return b; + } + + /** add a subobject as a member with type Array. Thus arr object should have "0", "1", ... + style fields in it. + */ + void appendArray(const char *fieldName, BSONObj subObj) { + b.append((char) Array); + b.append(fieldName); + b.append((void *) subObj.objdata(), subObj.objsize()); + } + void append(const char *fieldName, BSONArray arr) { appendArray(fieldName, arr); } + + + /** add header for a new subarray and return bufbuilder for writing to + the subarray's body */ + BufBuilder &subarrayStart(const char *fieldName) { + b.append((char) Array); + b.append(fieldName); + return b; + } + + /** Append a boolean element */ + void appendBool(const char *fieldName, int val) { + b.append((char) Bool); + b.append(fieldName); + b.append((char) (val?1:0)); + } + + /** Append a 32 bit integer element */ + void append(const char *fieldName, int n) { + b.append((char) NumberInt); + b.append(fieldName); + b.append(n); + } + /** Append a 32 bit integer element */ + void append(const string &fieldName, int n) { + append( fieldName.c_str(), n ); + } + + /** Append a 32 bit unsigned element - cast to a signed int. */ + void append(const char *fieldName, unsigned n) { append(fieldName, (int) n); } + + /** Append a NumberLong */ + void append(const char *fieldName, long long n) { + b.append((char) NumberLong); + b.append(fieldName); + b.append(n); + } + + /** Append a NumberLong */ + void append(const string& fieldName, long long n) { + append( fieldName.c_str() , n ); + } + + + /** Append a double element */ + BSONObjBuilder& append(const char *fieldName, double n) { + b.append((char) NumberDouble); + b.append(fieldName); + b.append(n); + return *this; + } + + /** tries to append the data as a number + * @return true if the data was able to be converted to a number + */ + bool appendAsNumber( const string& fieldName , const string& data ); + + /** Append a BSON Object ID (OID type). */ + void appendOID(const char *fieldName, OID *oid = 0 , bool generateIfBlank = false ) { + b.append((char) jstOID); + b.append(fieldName); + if ( oid ) + b.append( (void *) oid, 12 ); + else { + OID tmp; + if ( generateIfBlank ) + tmp.init(); + else + tmp.clear(); + b.append( (void *) &tmp, 12 ); + } + } + void append( const char *fieldName, OID oid ) { + appendOID( fieldName, &oid ); + } + /** Append a time_t date. + @param dt a C-style 32 bit date value, that is + the number of seconds since January 1, 1970, 00:00:00 GMT + */ + void appendTimeT(const char *fieldName, time_t dt) { + b.append((char) Date); + b.append(fieldName); + b.append(static_cast<unsigned long long>(dt) * 1000); + } + /** Append a date. + @param dt a Java-style 64 bit date value, that is + the number of milliseconds since January 1, 1970, 00:00:00 GMT + */ + void appendDate(const char *fieldName, Date_t dt) { + b.append((char) Date); + b.append(fieldName); + b.append(dt); + } + void append(const char *fieldName, Date_t dt) { + appendDate(fieldName, dt); + } + + /** Append a regular expression value + @param regex the regular expression pattern + @param regex options such as "i" or "g" + */ + void appendRegex(const char *fieldName, const char *regex, const char *options = "") { + b.append((char) RegEx); + b.append(fieldName); + b.append(regex); + b.append(options); + } + /** Append a regular expression value + @param regex the regular expression pattern + @param regex options such as "i" or "g" + */ + void appendRegex(string fieldName, string regex, string options = "") { + appendRegex(fieldName.c_str(), regex.c_str(), options.c_str()); + } + void appendCode(const char *fieldName, const char *code) { + b.append((char) Code); + b.append(fieldName); + b.append((int) strlen(code)+1); + b.append(code); + } + /** Append a string element */ + BSONObjBuilder& append(const char *fieldName, const char *str) { + b.append((char) String); + b.append(fieldName); + b.append((int) strlen(str)+1); + b.append(str); + return *this; + } + /** Append a string element */ + void append(const char *fieldName, string str) { + append(fieldName, str.c_str()); + } + void appendSymbol(const char *fieldName, const char *symbol) { + b.append((char) Symbol); + b.append(fieldName); + b.append((int) strlen(symbol)+1); + b.append(symbol); + } + + /** Append a Null element to the object */ + void appendNull( const char *fieldName ) { + b.append( (char) jstNULL ); + b.append( fieldName ); + } + + // Append an element that is less than all other keys. + void appendMinKey( const char *fieldName ) { + b.append( (char) MinKey ); + b.append( fieldName ); + } + // Append an element that is greater than all other keys. + void appendMaxKey( const char *fieldName ) { + b.append( (char) MaxKey ); + b.append( fieldName ); + } + + // Append a Timestamp field -- will be updated to next OpTime on db insert. + void appendTimestamp( const char *fieldName ) { + b.append( (char) Timestamp ); + b.append( fieldName ); + b.append( (unsigned long long) 0 ); + } + + void appendTimestamp( const char *fieldName , unsigned long long val ) { + b.append( (char) Timestamp ); + b.append( fieldName ); + b.append( val ); + } + + /** + * @param time - in millis (but stored in seconds) + */ + void appendTimestamp( const char *fieldName , unsigned long long time , unsigned int inc ){ + OpTime t( (unsigned) (time / 1000) , inc ); + appendTimestamp( fieldName , t.asDate() ); + } + + /* Deprecated (but supported) */ + void appendDBRef( const char *fieldName, const char *ns, const OID &oid ) { + b.append( (char) DBRef ); + b.append( fieldName ); + b.append( (int) strlen( ns ) + 1 ); + b.append( ns ); + b.append( (void *) &oid, 12 ); + } + + /** Append a binary data element + @param fieldName name of the field + @param len length of the binary data in bytes + @param type type information for the data. @see BinDataType. Use ByteArray if you + don't care about the type. + @param data the byte array + */ + void appendBinData( const char *fieldName, int len, BinDataType type, const char *data ) { + b.append( (char) BinData ); + b.append( fieldName ); + b.append( len ); + b.append( (char) type ); + b.append( (void *) data, len ); + } + void appendBinData( const char *fieldName, int len, BinDataType type, const unsigned char *data ) { + appendBinData(fieldName, len, type, (const char *) data); + } + + /** + @param len the length of data + */ + void appendBinDataArray( const char * fieldName , const char * data , int len ){ + b.append( (char) BinData ); + b.append( fieldName ); + b.append( len + 4 ); + b.append( (char)0x2 ); + b.append( len ); + b.append( (void *) data, len ); + } + + /** Append to the BSON object a field of type CodeWScope. This is a javascript code + fragment accompanied by some scope that goes with it. + */ + void appendCodeWScope( const char *fieldName, const char *code, const BSONObj &scope ) { + b.append( (char) CodeWScope ); + b.append( fieldName ); + b.append( ( int )( 4 + 4 + strlen( code ) + 1 + scope.objsize() ) ); + b.append( ( int ) strlen( code ) + 1 ); + b.append( code ); + b.append( ( void * )scope.objdata(), scope.objsize() ); + } + + void appendUndefined( const char *fieldName ) { + b.append( (char) Undefined ); + b.append( fieldName ); + } + + /* helper function -- see Query::where() for primary way to do this. */ + void appendWhere( const char *code, const BSONObj &scope ){ + appendCodeWScope( "$where" , code , scope ); + } + void appendWhere( const string &code, const BSONObj &scope ){ + appendWhere( code.c_str(), scope ); + } + + /** + these are the min/max when comparing, not strict min/max elements for a given type + */ + void appendMinForType( const string& field , int type ); + void appendMaxForType( const string& field , int type ); + + /** Append an array of values. */ + template < class T > + void append( const char *fieldName, const vector< T >& vals ) { + BSONObjBuilder arrBuilder; + for ( unsigned int i = 0; i < vals.size(); ++i ) + arrBuilder.append( numStr( i ).c_str(), vals[ i ] ); + marshalArray( fieldName, arrBuilder.done() ); + } + + /* Append an array of ints + void appendArray( const char *fieldName, const vector< int >& vals ) { + BSONObjBuilder arrBuilder; + for ( unsigned i = 0; i < vals.size(); ++i ) + arrBuilder.append( numStr( i ).c_str(), vals[ i ] ); + marshalArray( fieldName, arrBuilder.done() ); + }*/ + + /** The returned BSONObj will free the buffer when it is finished. */ + BSONObj obj() { + massert( 10335 , "builder does not own memory", owned() ); + int l; + return BSONObj(decouple(l), true); + } + + /** Fetch the object we have built. + BSONObjBuilder still frees the object when the builder goes out of + scope -- very important to keep in mind. Use obj() if you + would like the BSONObj to last longer than the builder. + */ + BSONObj done() { + return BSONObj(_done()); + } + + /* assume ownership of the buffer - you must then free it (with free()) */ + char* decouple(int& l) { + char *x = _done(); + assert( x ); + l = b.len(); + b.decouple(); + return x; + } + void decouple() { + b.decouple(); // post done() call version. be sure jsobj frees... + } + + + private: + static const string numStrs[100]; // cache of 0 to 99 inclusive + public: + static string numStr( int i ) { + if (i>=0 && i<100) + return numStrs[i]; + + stringstream o; + o << i; + return o.str(); + } + + /** Stream oriented way to add field names and values. */ + BSONObjBuilderValueStream &operator<<(const char * name ) { + s_.endField( name ); + return s_; + } + + // prevent implicit string conversions which would allow bad things like BSON( BSON( "foo" << 1 ) << 2 ) + struct ForceExplicitString { + ForceExplicitString( const string &str ) : str_( str ) {} + string str_; + }; + + /** Stream oriented way to add field names and values. */ + BSONObjBuilderValueStream &operator<<( const ForceExplicitString& name ) { + return operator<<( name.str_.c_str() ); + } + + Labeler operator<<( const Labeler::Label &l ) { + massert( 10336 , "No subobject started", s_.subobjStarted() ); + return s_ << l; + } + + bool owned() const { + return &b == &buf_; + } + + private: + // Append the provided arr object as an array. + void marshalArray( const char *fieldName, const BSONObj &arr ) { + b.append( (char) Array ); + b.append( fieldName ); + b.append( (void *) arr.objdata(), arr.objsize() ); + } + + char* _done() { + s_.endField(); + b.append((char) EOO); + char *data = b.buf() + offset_; + *((int*)data) = b.len() - offset_; + return data; + } + + BufBuilder &b; + BufBuilder buf_; + int offset_; + BSONObjBuilderValueStream s_; + }; + + class BSONArrayBuilder : boost::noncopyable{ + public: + BSONArrayBuilder() :i(0), b() {} + + template <typename T> + BSONArrayBuilder& append(const T& x){ + b.append(num().c_str(), x); + return *this; + } + + BSONArrayBuilder& append(const BSONElement& e){ + b.appendAs(e, num().c_str()); + return *this; + } + + template <typename T> + BSONArrayBuilder& operator<<(const T& x){ + return append(x); + } + + BSONArray arr(){ return BSONArray(b.obj()); } + + private: + string num(){ return b.numStr(i++); } + int i; + BSONObjBuilder b; + }; + + + /** iterator for a BSONObj + + Note each BSONObj ends with an EOO element: so you will get more() on an empty + object, although next().eoo() will be true. + + todo: we may want to make a more stl-like iterator interface for this + with things like begin() and end() + */ + class BSONObjIterator { + public: + /** Create an iterator for a BSON object. + */ + BSONObjIterator(const BSONObj& jso) { + int sz = jso.objsize(); + if ( sz == 0 ) { + pos = theend = 0; + return; + } + pos = jso.objdata() + 4; + theend = jso.objdata() + sz; + } + /** @return true if more elements exist to be enumerated. */ + bool moreWithEOO() { + return pos < theend; + } + bool more(){ + return pos < theend && pos[0]; + } + /** @return the next element in the object. For the final element, element.eoo() will be true. */ + BSONElement next( bool checkEnd = false ) { + assert( pos < theend ); + BSONElement e( pos, checkEnd ? theend - pos : -1 ); + pos += e.size( checkEnd ? theend - pos : -1 ); + return e; + } + private: + const char *pos; + const char *theend; + }; + + /* iterator a BSONObj which is an array, in array order. + class JSArrayIter { + public: + BSONObjIterator(const BSONObj& jso) { + ... + } + bool more() { return ... } + BSONElement next() { + ... + } + }; + */ + + extern BSONObj maxKey; + extern BSONObj minKey; + + // a BoundList contains intervals specified by inclusive start + // and end bounds. The intervals should be nonoverlapping and occur in + // the specified direction of traversal. For example, given a simple index {i:1} + // and direction +1, one valid BoundList is: (1, 2); (4, 6). The same BoundList + // would be valid for index {i:-1} with direction -1. + typedef vector< pair< BSONObj, BSONObj > > BoundList; + + /*- just for testing -- */ + +#pragma pack(1) + struct JSObj1 { + JSObj1() { + totsize=sizeof(JSObj1); + n = NumberDouble; + strcpy_s(nname, 5, "abcd"); + N = 3.1; + s = String; + strcpy_s(sname, 7, "abcdef"); + slen = 10; + strcpy_s(sval, 10, "123456789"); + eoo = EOO; + } + unsigned totsize; + + char n; + char nname[5]; + double N; + + char s; + char sname[7]; + unsigned slen; + char sval[10]; + + char eoo; + }; +#pragma pack() + extern JSObj1 js1; + +#ifdef _DEBUG +#define CHECK_OBJECT( o , msg ) massert( 10337 , (string)"object not valid" + (msg) , (o).isValid() ) +#else +#define CHECK_OBJECT( o , msg ) +#endif + + inline BSONObj BSONElement::embeddedObjectUserCheck() { + uassert( 10065 , "invalid parameter: expected an object", type()==Object || type()==Array ); + return BSONObj(value()); + } + + inline BSONObj BSONElement::embeddedObject() const { + assert( type()==Object || type()==Array ); + return BSONObj(value()); + } + + inline BSONObj BSONElement::codeWScopeObject() const { + assert( type() == CodeWScope ); + int strSizeWNull = *(int *)( value() + 4 ); + return BSONObj( value() + 4 + 4 + strSizeWNull ); + } + + inline BSONObj BSONObj::copy() const { + char *p = (char*) malloc(objsize()); + memcpy(p, objdata(), objsize()); + return BSONObj(p, true); + } + +// wrap this element up as a singleton object. + inline BSONObj BSONElement::wrap() const { + BSONObjBuilder b(size()+6); + b.append(*this); + return b.obj(); + } + + inline BSONObj BSONElement::wrap( const char * newName ) const { + BSONObjBuilder b(size()+6+strlen(newName)); + b.appendAs(*this,newName); + return b.obj(); + } + + + inline bool BSONObj::hasElement(const char *name) const { + if ( !isEmpty() ) { + BSONObjIterator it(*this); + while ( it.moreWithEOO() ) { + BSONElement e = it.next(); + if ( strcmp(name, e.fieldName()) == 0 ) + return true; + } + } + return false; + } + + inline BSONElement BSONObj::findElement(const char *name) const { + if ( !isEmpty() ) { + BSONObjIterator it(*this); + while ( it.moreWithEOO() ) { + BSONElement e = it.next(); + if ( strcmp(name, e.fieldName()) == 0 ) + return e; + } + } + return BSONElement(); + } + + /* add all the fields from the object specified to this object */ + inline BSONObjBuilder& BSONObjBuilder::appendElements(BSONObj x) { + BSONObjIterator it(x); + while ( it.moreWithEOO() ) { + BSONElement e = it.next(); + if ( e.eoo() ) break; + append(e); + } + return *this; + } + + inline bool BSONObj::isValid(){ + return objsize() > 0 && objsize() <= 1024 * 1024 * 8; + } + + inline bool BSONObj::getObjectID(BSONElement& e) const { + BSONElement f = findElement("_id"); + if( !f.eoo() ) { + e = f; + return true; + } + return false; + } + + inline BSONObjBuilderValueStream::BSONObjBuilderValueStream( BSONObjBuilder * builder ) { + _fieldName = 0; + _builder = builder; + } + + template<class T> + inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<( T value ) { + _builder->append(_fieldName, value); + _fieldName = 0; + return *_builder; + } + + inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const BSONElement& e ) { + _builder->appendAs( e , _fieldName ); + _fieldName = 0; + return *_builder; + } + + inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(DateNowLabeler& id){ + _builder->appendDate(_fieldName, jsTime()); + _fieldName = 0; + return *_builder; + } + + inline Labeler BSONObjBuilderValueStream::operator<<( const Labeler::Label &l ) { + return Labeler( l, this ); + } + + inline void BSONObjBuilderValueStream::endField( const char *nextFieldName ) { + if ( _fieldName && haveSubobj() ) { + _builder->append( _fieldName, subobj()->done() ); + } + _subobj.reset(); + _fieldName = nextFieldName; + } + + inline BSONObjBuilder *BSONObjBuilderValueStream::subobj() { + if ( !haveSubobj() ) + _subobj.reset( new BSONObjBuilder() ); + return _subobj.get(); + } + + template<class T> inline + BSONObjBuilder& Labeler::operator<<( T value ) { + s_->subobj()->append( l_.l_, value ); + return *s_->_builder; + } + + inline + BSONObjBuilder& Labeler::operator<<( const BSONElement& e ) { + s_->subobj()->appendAs( e, l_.l_ ); + return *s_->_builder; + } + + // {a: {b:1}} -> {a.b:1} + void nested2dotted(BSONObjBuilder& b, const BSONObj& obj, const string& base=""); + inline BSONObj nested2dotted(const BSONObj& obj){ + BSONObjBuilder b; + nested2dotted(b, obj); + return b.obj(); + } + + // {a.b:1} -> {a: {b:1}} + void dotted2nested(BSONObjBuilder& b, const BSONObj& obj); + inline BSONObj dotted2nested(const BSONObj& obj){ + BSONObjBuilder b; + dotted2nested(b, obj); + return b.obj(); + } + + /* WARNING: nested/dotted conversions are not 100% reversible + * nested2dotted(dotted2nested({a.b: {c:1}})) -> {a.b.c: 1} + * also, dotted2nested ignores order + */ + + typedef map<string, BSONElement> BSONMap; + inline BSONMap bson2map(const BSONObj& obj){ + BSONMap m; + BSONObjIterator it(obj); + while (it.more()){ + BSONElement e = it.next(); + m[e.fieldName()] = e; + } + return m; + } + + struct BSONElementFieldNameCmp { + bool operator()( const BSONElement &l, const BSONElement &r ) const { + return strcmp( l.fieldName() , r.fieldName() ) <= 0; + } + }; + + + typedef set<BSONElement, BSONElementFieldNameCmp> BSONSortedElements; + inline BSONSortedElements bson2set( const BSONObj& obj ){ + BSONSortedElements s; + BSONObjIterator it(obj); + while ( it.more() ) + s.insert( it.next() ); + return s; + } + + class BSONObjIteratorSorted { + public: + BSONObjIteratorSorted( const BSONObj& o ); + + ~BSONObjIteratorSorted(){ + assert( _fields ); + delete _fields; + _fields = 0; + } + + bool more(){ + return _cur < _nfields; + } + + BSONElement next(){ + assert( _fields ); + if ( _cur < _nfields ) + return BSONElement( _fields[_cur++] ); + return BSONElement(); + } + + private: + const char ** _fields; + int _nfields; + int _cur; + }; + +} // namespace mongo diff --git a/db/jsobjmanipulator.h b/db/jsobjmanipulator.h new file mode 100644 index 0000000..d534d08 --- /dev/null +++ b/db/jsobjmanipulator.h @@ -0,0 +1,78 @@ +/** jsobjManipulator.h */ + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "jsobj.h" + +namespace mongo { + +/** Manipulate the binary representation of a BSONElement in-place. + Careful, this casts away const. + */ +class BSONElementManipulator { +public: + BSONElementManipulator( const BSONElement &element ) : + element_( element ) { + assert( !element_.eoo() ); + } + /** Replace a Timestamp type with a Date type initialized to + OpTime::now().asDate() + */ + void initTimestamp(); + + /** Change the value, in place, of the number. */ + void setNumber(double d) { + if ( element_.type() == NumberDouble ) *reinterpret_cast< double * >( value() ) = d; + else if ( element_.type() == NumberInt ) *reinterpret_cast< int * >( value() ) = (int) d; + } + void setLong(long long n) { + if( element_.type() == NumberLong ) *reinterpret_cast< long long * >( value() ) = n; + } + + /** Replace the type and value of the element with the type and value of e, + preserving the original fieldName */ + void replaceTypeAndValue( const BSONElement &e ) { + *data() = e.type(); + memcpy( value(), e.value(), e.valuesize() ); + } + + static void lookForTimestamps( const BSONObj& obj ){ + // If have a Timestamp field as the first or second element, + // update it to a Date field set to OpTime::now().asDate(). The + // replacement policy is a work in progress. + + BSONObjIterator i( obj ); + for( int j = 0; i.moreWithEOO() && j < 2; ++j ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( e.type() == Timestamp ){ + BSONElementManipulator( e ).initTimestamp(); + break; + } + } + } +private: + char *data() { return nonConst( element_.rawdata() ); } + char *value() { return nonConst( element_.value() ); } + static char *nonConst( const char *s ) { return const_cast< char * >( s ); } + const BSONElement element_; +}; + +} // namespace mongo diff --git a/db/json.cpp b/db/json.cpp new file mode 100644 index 0000000..b55ddb1 --- /dev/null +++ b/db/json.cpp @@ -0,0 +1,569 @@ +// json.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "json.h" +#include "../util/builder.h" +#include "../util/base64.h" + +using namespace boost::spirit; + +namespace mongo { + + struct ObjectBuilder { + BSONObjBuilder *back() { + return builders.back().get(); + } + // Storage for field names of elements within builders.back(). + const char *fieldName() { + return fieldNames.back().c_str(); + } + bool empty() const { + return builders.size() == 0; + } + void init() { + boost::shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() ); + builders.push_back( b ); + fieldNames.push_back( "" ); + indexes.push_back( 0 ); + } + void pushObject( const char *fieldName ) { + boost::shared_ptr< BSONObjBuilder > b( new BSONObjBuilder( builders.back()->subobjStart( fieldName ) ) ); + builders.push_back( b ); + fieldNames.push_back( "" ); + indexes.push_back( 0 ); + } + void pushArray( const char *fieldName ) { + boost::shared_ptr< BSONObjBuilder > b( new BSONObjBuilder( builders.back()->subarrayStart( fieldName ) ) ); + builders.push_back( b ); + fieldNames.push_back( "" ); + indexes.push_back( 0 ); + } + BSONObj pop() { + BSONObj ret; + if ( back()->owned() ) + ret = back()->obj(); + else + ret = back()->done(); + builders.pop_back(); + fieldNames.pop_back(); + indexes.pop_back(); + return ret; + } + void nameFromIndex() { + fieldNames.back() = BSONObjBuilder::numStr( indexes.back() ); + } + string popString() { + string ret = ss.str(); + ss.str( "" ); + return ret; + } + // Cannot use auto_ptr because its copy constructor takes a non const reference. + vector< boost::shared_ptr< BSONObjBuilder > > builders; + vector< string > fieldNames; + vector< int > indexes; + stringstream ss; + string ns; + OID oid; + string binData; + BinDataType binDataType; + string regex; + string regexOptions; + Date_t date; + }; + + struct objectStart { + objectStart( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char &c ) const { + if ( b.empty() ) + b.init(); + else + b.pushObject( b.fieldName() ); + } + ObjectBuilder &b; + }; + + struct arrayStart { + arrayStart( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char &c ) const { + b.pushArray( b.fieldName() ); + b.nameFromIndex(); + } + ObjectBuilder &b; + }; + + struct arrayNext { + arrayNext( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char &c ) const { + ++b.indexes.back(); + b.nameFromIndex(); + } + ObjectBuilder &b; + }; + + struct ch { + ch( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char c ) const { + b.ss << c; + } + ObjectBuilder &b; + }; + + struct chE { + chE( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char c ) const { + char o = '\0'; + switch ( c ) { + case '\"': + o = '\"'; + break; + case '\'': + o = '\''; + break; + case '\\': + o = '\\'; + break; + case '/': + o = '/'; + break; + case 'b': + o = '\b'; + break; + case 'f': + o = '\f'; + break; + case 'n': + o = '\n'; + break; + case 'r': + o = '\r'; + break; + case 't': + o = '\t'; + break; + case 'v': + o = '\v'; + break; + default: + assert( false ); + } + b.ss << o; + } + ObjectBuilder &b; + }; + + namespace hex { + int val( char c ) { + if ( '0' <= c && c <= '9' ) + return c - '0'; + if ( 'a' <= c && c <= 'f' ) + return c - 'a' + 10; + if ( 'A' <= c && c <= 'F' ) + return c - 'A' + 10; + assert( false ); + return 0xff; + } + char val( const char *c ) { + return ( val( c[ 0 ] ) << 4 ) | val( c[ 1 ] ); + } + } // namespace hex + + struct chU { + chU( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + unsigned char first = hex::val( start ); + unsigned char second = hex::val( start + 2 ); + if ( first == 0 && second < 0x80 ) + b.ss << second; + else if ( first < 0x08 ) { + b.ss << char( 0xc0 | ( ( first << 2 ) | ( second >> 6 ) ) ); + b.ss << char( 0x80 | ( ~0xc0 & second ) ); + } else { + b.ss << char( 0xe0 | ( first >> 4 ) ); + b.ss << char( 0x80 | ( ~0xc0 & ( ( first << 2 ) | ( second >> 6 ) ) ) ); + b.ss << char( 0x80 | ( ~0xc0 & second ) ); + } + } + ObjectBuilder &b; + }; + + struct chClear { + chClear( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char c ) const { + b.popString(); + } + ObjectBuilder &b; + }; + + struct fieldNameEnd { + fieldNameEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + string name = b.popString(); + massert( 10338 , "Invalid use of reserved field name", + name != "$oid" && + name != "$binary" && + name != "$type" && + name != "$date" && + name != "$regex" && + name != "$options" ); + b.fieldNames.back() = name; + } + ObjectBuilder &b; + }; + + struct unquotedFieldNameEnd { + unquotedFieldNameEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + string name( start, end ); + b.fieldNames.back() = name; + } + ObjectBuilder &b; + }; + + struct stringEnd { + stringEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->append( b.fieldName(), b.popString() ); + } + ObjectBuilder &b; + }; + + struct numberValue { + numberValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( double d ) const { + b.back()->append( b.fieldName(), d ); + } + ObjectBuilder &b; + }; + + struct intValue { + intValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( long long num ) const { + if (num >= numeric_limits<int>::min() && num <= numeric_limits<int>::max()) + b.back()->append( b.fieldName(), (int)num ); + else + b.back()->append( b.fieldName(), num ); + } + ObjectBuilder &b; + }; + + struct subobjectEnd { + subobjectEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.pop(); + } + ObjectBuilder &b; + }; + + struct arrayEnd { + arrayEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.pop(); + } + ObjectBuilder &b; + }; + + struct trueValue { + trueValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendBool( b.fieldName(), true ); + } + ObjectBuilder &b; + }; + + struct falseValue { + falseValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendBool( b.fieldName(), false ); + } + ObjectBuilder &b; + }; + + struct nullValue { + nullValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendNull( b.fieldName() ); + } + ObjectBuilder &b; + }; + + struct dbrefNS { + dbrefNS( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.ns = b.popString(); + } + ObjectBuilder &b; + }; + +// NOTE s must be 24 characters. + OID stringToOid( const char *s ) { + OID oid; + char *oidP = (char *)( &oid ); + for ( int i = 0; i < 12; ++i ) + oidP[ i ] = hex::val( s + ( i * 2 ) ); + return oid; + } + + struct oidValue { + oidValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.oid = stringToOid( start ); + } + ObjectBuilder &b; + }; + + struct dbrefEnd { + dbrefEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendDBRef( b.fieldName(), b.ns.c_str(), b.oid ); + } + ObjectBuilder &b; + }; + + struct oidEnd { + oidEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendOID( b.fieldName(), &b.oid ); + } + ObjectBuilder &b; + }; + + struct binDataBinary { + binDataBinary( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + massert( 10339 , "Badly formatted bindata", ( end - start ) % 4 == 0 ); + string encoded( start, end ); + b.binData = base64::decode( encoded ); + } + ObjectBuilder &b; + }; + + struct binDataType { + binDataType( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.binDataType = BinDataType( hex::val( start ) ); + } + ObjectBuilder &b; + }; + + struct binDataEnd { + binDataEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendBinData( b.fieldName(), b.binData.length(), + b.binDataType, b.binData.data() ); + } + ObjectBuilder &b; + }; + + struct dateValue { + dateValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( Date_t v ) const { + b.date = v; + } + ObjectBuilder &b; + }; + + struct dateEnd { + dateEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendDate( b.fieldName(), b.date ); + } + ObjectBuilder &b; + }; + + struct regexValue { + regexValue( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.regex = b.popString(); + } + ObjectBuilder &b; + }; + + struct regexOptions { + regexOptions( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.regexOptions = string( start, end ); + } + ObjectBuilder &b; + }; + + struct regexEnd { + regexEnd( ObjectBuilder &_b ) : b( _b ) {} + void operator() ( const char *start, const char *end ) const { + b.back()->appendRegex( b.fieldName(), b.regex.c_str(), + b.regexOptions.c_str() ); + } + ObjectBuilder &b; + }; + +// One gotcha with this parsing library is probably best ilustrated with an +// example. Say we have a production like this: +// z = ( ch_p( 'a' )[ foo ] >> ch_p( 'b' ) ) | ( ch_p( 'a' )[ foo ] >> ch_p( 'c' ) ); +// On input "ac", action foo() will be called twice -- once as the parser tries +// to match "ab", again as the parser successfully matches "ac". Sometimes +// the grammar can be modified to eliminate these situations. Here, for example: +// z = ch_p( 'a' )[ foo ] >> ( ch_p( 'b' ) | ch_p( 'c' ) ); +// However, this is not always possible. In my implementation I've tried to +// stick to the following pattern: store fields fed to action callbacks +// temporarily as ObjectBuilder members, then append to a BSONObjBuilder once +// the parser has completely matched a nonterminal and won't backtrack. It's +// worth noting here that this parser follows a short-circuit convention. So, +// in the original z example on line 3, if the input was "ab", foo() would only +// be called once. + struct JsonGrammar : public grammar< JsonGrammar > { +public: + JsonGrammar( ObjectBuilder &_b ) : b( _b ) {} + + template < typename ScannerT > + struct definition { + definition( JsonGrammar const &self ) { + object = ch_p( '{' )[ objectStart( self.b ) ] >> !members >> '}'; + members = list_p((fieldName >> ':' >> value) , ','); + fieldName = + str[ fieldNameEnd( self.b ) ] | + singleQuoteStr[ fieldNameEnd( self.b ) ] | + unquotedFieldName[ unquotedFieldNameEnd( self.b ) ]; + array = ch_p( '[' )[ arrayStart( self.b ) ] >> !elements >> ']'; + elements = list_p(value, ch_p(',')[arrayNext( self.b )]); + value = + oid[ oidEnd( self.b ) ] | + dbref[ dbrefEnd( self.b ) ] | + bindata[ binDataEnd( self.b ) ] | + date[ dateEnd( self.b ) ] | + regex[ regexEnd( self.b ) ] | + str[ stringEnd( self.b ) ] | + singleQuoteStr[ stringEnd( self.b ) ] | + number | + integer | + object[ subobjectEnd( self.b ) ] | + array[ arrayEnd( self.b ) ] | + lexeme_d[ str_p( "true" ) ][ trueValue( self.b ) ] | + lexeme_d[ str_p( "false" ) ][ falseValue( self.b ) ] | + lexeme_d[ str_p( "null" ) ][ nullValue( self.b ) ]; + // NOTE lexeme_d and rules don't mix well, so we have this mess. + // NOTE We use range_p rather than cntrl_p, because the latter is locale dependent. + str = lexeme_d[ ch_p( '"' )[ chClear( self.b ) ] >> + *( ( ch_p( '\\' ) >> + ( + ch_p( 'b' )[ chE( self.b ) ] | + ch_p( 'f' )[ chE( self.b ) ] | + ch_p( 'n' )[ chE( self.b ) ] | + ch_p( 'r' )[ chE( self.b ) ] | + ch_p( 't' )[ chE( self.b ) ] | + ch_p( 'v' )[ chE( self.b ) ] | + ( ch_p( 'u' ) >> ( repeat_p( 4 )[ xdigit_p ][ chU( self.b ) ] ) ) | + ( ~ch_p('x') & (~range_p('0','9'))[ ch( self.b ) ] ) // hex and octal aren't supported + ) + ) | + ( ~range_p( 0x00, 0x1f ) & ~ch_p( '"' ) & ( ~ch_p( '\\' ) )[ ch( self.b ) ] ) ) >> '"' ]; + + singleQuoteStr = lexeme_d[ ch_p( '\'' )[ chClear( self.b ) ] >> + *( ( ch_p( '\\' ) >> + ( + ch_p( 'b' )[ chE( self.b ) ] | + ch_p( 'f' )[ chE( self.b ) ] | + ch_p( 'n' )[ chE( self.b ) ] | + ch_p( 'r' )[ chE( self.b ) ] | + ch_p( 't' )[ chE( self.b ) ] | + ch_p( 'v' )[ chE( self.b ) ] | + ( ch_p( 'u' ) >> ( repeat_p( 4 )[ xdigit_p ][ chU( self.b ) ] ) ) | + ( ~ch_p('x') & (~range_p('0','9'))[ ch( self.b ) ] ) // hex and octal aren't supported + ) + ) | + ( ~range_p( 0x00, 0x1f ) & ~ch_p( '\'' ) & ( ~ch_p( '\\' ) )[ ch( self.b ) ] ) ) >> '\'' ]; + + // real_p accepts numbers with nonsignificant zero prefixes, which + // aren't allowed in JSON. Oh well. + number = strict_real_p[ numberValue( self.b ) ]; + + static int_parser<long long, 10, 1, numeric_limits<long long>::digits10 + 1> long_long_p; + integer = long_long_p[ intValue(self.b) ]; + + // We allow a subset of valid js identifier names here. + unquotedFieldName = lexeme_d[ ( alpha_p | ch_p( '$' ) | ch_p( '_' ) ) >> *( ( alnum_p | ch_p( '$' ) | ch_p( '_' )) ) ]; + + dbref = dbrefS | dbrefT; + dbrefS = ch_p( '{' ) >> "\"$ref\"" >> ':' >> + str[ dbrefNS( self.b ) ] >> ',' >> "\"$id\"" >> ':' >> quotedOid >> '}'; + dbrefT = str_p( "Dbref" ) >> '(' >> str[ dbrefNS( self.b ) ] >> ',' >> + quotedOid >> ')'; + + oid = oidS | oidT; + oidS = ch_p( '{' ) >> "\"$oid\"" >> ':' >> quotedOid >> '}'; + oidT = str_p( "ObjectId" ) >> '(' >> quotedOid >> ')'; + + quotedOid = lexeme_d[ '"' >> ( repeat_p( 24 )[ xdigit_p ] )[ oidValue( self.b ) ] >> '"' ]; + + bindata = ch_p( '{' ) >> "\"$binary\"" >> ':' >> + lexeme_d[ '"' >> ( *( range_p( 'A', 'Z' ) | range_p( 'a', 'z' ) | range_p( '0', '9' ) | ch_p( '+' ) | ch_p( '/' ) ) >> *ch_p( '=' ) )[ binDataBinary( self.b ) ] >> '"' ] >> ',' >> "\"$type\"" >> ':' >> + lexeme_d[ '"' >> ( repeat_p( 2 )[ xdigit_p ] )[ binDataType( self.b ) ] >> '"' ] >> '}'; + + // TODO: this will need to use a signed parser at some point + date = dateS | dateT; + dateS = ch_p( '{' ) >> "\"$date\"" >> ':' >> uint_parser< Date_t >()[ dateValue( self.b ) ] >> '}'; + dateT = !str_p("new") >> str_p( "Date" ) >> '(' >> uint_parser< Date_t >()[ dateValue( self.b ) ] >> ')'; + + regex = regexS | regexT; + regexS = ch_p( '{' ) >> "\"$regex\"" >> ':' >> str[ regexValue( self.b ) ] >> ',' >> "\"$options\"" >> ':' >> lexeme_d[ '"' >> ( *( alpha_p ) )[ regexOptions( self.b ) ] >> '"' ] >> '}'; + // FIXME Obviously it would be nice to unify this with str. + regexT = lexeme_d[ ch_p( '/' )[ chClear( self.b ) ] >> + *( ( ch_p( '\\' ) >> + ( ch_p( '"' )[ chE( self.b ) ] | + ch_p( '\\' )[ chE( self.b ) ] | + ch_p( '/' )[ chE( self.b ) ] | + ch_p( 'b' )[ chE( self.b ) ] | + ch_p( 'f' )[ chE( self.b ) ] | + ch_p( 'n' )[ chE( self.b ) ] | + ch_p( 'r' )[ chE( self.b ) ] | + ch_p( 't' )[ chE( self.b ) ] | + ( ch_p( 'u' ) >> ( repeat_p( 4 )[ xdigit_p ][ chU( self.b ) ] ) ) ) ) | + ( ~range_p( 0x00, 0x1f ) & ~ch_p( '/' ) & ( ~ch_p( '\\' ) )[ ch( self.b ) ] ) ) >> str_p( "/" )[ regexValue( self.b ) ] + >> ( *( ch_p( 'i' ) | ch_p( 'g' ) | ch_p( 'm' ) ) )[ regexOptions( self.b ) ] ]; + } + rule< ScannerT > object, members, array, elements, value, str, number, integer, + dbref, dbrefS, dbrefT, oid, oidS, oidT, bindata, date, dateS, dateT, + regex, regexS, regexT, quotedOid, fieldName, unquotedFieldName, singleQuoteStr; + const rule< ScannerT > &start() const { + return object; + } + }; + ObjectBuilder &b; + }; + + BSONObj fromjson( const char *str ) { + if ( ! strlen(str) ) + return BSONObj(); + ObjectBuilder b; + JsonGrammar parser( b ); + parse_info<> result = parse( str, parser, space_p ); + if ( !result.full ) { + int len = strlen( result.stop ); + if ( len > 10 ) + len = 10; + stringstream ss; + ss << "Failure parsing JSON string near: " << string( result.stop, len ); + massert( 10340 , ss.str(), false ); + } + return b.pop(); + } + + BSONObj fromjson( const string &str ) { + return fromjson( str.c_str() ); + } + +} // namespace mongo diff --git a/db/json.h b/db/json.h new file mode 100644 index 0000000..c65785a --- /dev/null +++ b/db/json.h @@ -0,0 +1,40 @@ +/** @file json.h */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" + +namespace mongo { + + /** Create a BSONObj from a JSON <http://www.json.org> string. In addition + to the JSON extensions extensions described here + <http://mongodb.onconfluence.com/display/DOCS/Mongo+Extended+JSON>, + this function accepts certain unquoted field names and allows single quotes + to optionally be used when specifying field names and string values instead + of double quotes. JSON unicode escape sequences (of the form \uXXXX) are + converted to utf8. + \throws MsgAssertionException if parsing fails. The message included with + this assertion includes a rough indication of where parsing failed. + */ + BSONObj fromjson(const string &str); + + BSONObj fromjson(const char *str); + +} // namespace mongo diff --git a/db/lasterror.cpp b/db/lasterror.cpp new file mode 100644 index 0000000..e8b1fcf --- /dev/null +++ b/db/lasterror.cpp @@ -0,0 +1,193 @@ +// lasterror.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" + +#include "../util/unittest.h" +#include "../util/message.h" + + +#include "lasterror.h" +#include "jsobj.h" + +namespace mongo { + + LastError LastError::noError; + LastErrorHolder lastError; + boost::mutex LastErrorHolder::_idsmutex; + + void LastError::appendSelf( BSONObjBuilder &b ) { + if ( !valid ) { + b.appendNull( "err" ); + b.append( "n", 0 ); + return; + } + if ( msg.empty() ) + b.appendNull( "err" ); + else + b.append( "err", msg ); + if ( code ) + b.append( "code" , code ); + if ( updatedExisting != NotUpdate ) + b.appendBool( "updatedExisting", updatedExisting == True ); + b.append( "n", nObjects ); + } + + void LastErrorHolder::setID( int id ){ + _id.set( id ); + } + + int LastErrorHolder::getID(){ + return _id.get(); + } + + LastError * LastErrorHolder::disableForCommand() { + LastError *le = _get(); + assert( le ); + le->disabled = true; + le->nPrev--; // caller is a command that shouldn't count as an operation + return le; + } + + LastError * LastErrorHolder::get( bool create ) { + LastError *ret = _get( create ); + if ( ret && !ret->disabled ) + return ret; + return 0; + } + + LastError * LastErrorHolder::_get( bool create ){ + int id = _id.get(); + if ( id == 0 ) + return _tl.get(); + + boostlock lock(_idsmutex); + map<int,Status>::iterator i = _ids.find( id ); + if ( i == _ids.end() ){ + if ( ! create ) + return 0; + + LastError * le = new LastError(); + Status s; + s.time = time(0); + s.lerr = le; + _ids[id] = s; + return le; + } + + Status &status = i->second; + status.time = time(0); + return status.lerr; + } + + void LastErrorHolder::remove( int id ){ + boostlock lock(_idsmutex); + map<int,Status>::iterator i = _ids.find( id ); + if ( i == _ids.end() ) + return; + + delete i->second.lerr; + _ids.erase( i ); + } + + void LastErrorHolder::release(){ + int id = _id.get(); + if ( id == 0 ){ + _tl.release(); + return; + } + + remove( id ); + } + + void LastErrorHolder::reset( LastError * le ){ + int id = _id.get(); + if ( id == 0 ){ + _tl.reset( le ); + return; + } + + boostlock lock(_idsmutex); + Status & status = _ids[id]; + status.time = time(0); + status.lerr = le; + } + + void prepareErrForNewRequest( Message &m, LastError * err ) { + // a killCursors message shouldn't affect last error + if ( m.data->operation() == dbKillCursors ) { + err->disabled = true; + } else { + err->disabled = false; + err->nPrev++; + } + } + + void LastErrorHolder::startRequest( Message& m ) { + int id = m.data->id & 0xFFFF0000; + setID( id ); + LastError * le = _get( true ); + prepareErrForNewRequest( m, le ); + } + + void LastErrorHolder::startRequest( Message& m , LastError * connectionOwned ) { + if ( !connectionOwned->overridenById ) { + prepareErrForNewRequest( m, connectionOwned ); + return; + } + startRequest(m); + } + + struct LastErrorHolderTest : public UnitTest { + public: + + void test( int i ){ + _tl.set( i ); + assert( _tl.get() == i ); + } + + void tlmaptest(){ + test( 1 ); + test( 12123123 ); + test( -123123 ); + test( numeric_limits<int>::min() ); + test( numeric_limits<int>::max() ); + } + + void run(){ + tlmaptest(); + + LastError * a = new LastError(); + LastError * b = new LastError(); + + LastErrorHolder holder; + holder.reset( a ); + assert( a == holder.get() ); + holder.setID( 1 ); + assert( 0 == holder.get() ); + holder.reset( b ); + assert( b == holder.get() ); + holder.setID( 0 ); + assert( a == holder.get() ); + + holder.remove( 1 ); + } + + ThreadLocalValue<int> _tl; + } lastErrorHolderTest; + +} // namespace mongo diff --git a/db/lasterror.h b/db/lasterror.h new file mode 100644 index 0000000..8f687bb --- /dev/null +++ b/db/lasterror.h @@ -0,0 +1,130 @@ +// lasterror.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <boost/thread/tss.hpp> +#undef assert +#define assert xassert + +namespace mongo { + class BSONObjBuilder; + class Message; + + struct LastError { + int code; + string msg; + enum UpdatedExistingType { NotUpdate, True, False } updatedExisting; + /* todo: nObjects should be 64 bit */ + int nObjects; + int nPrev; + bool valid; + bool overridenById; + bool disabled; + void raiseError(int _code , const char *_msg) { + reset( true ); + code = _code; + msg = _msg; + } + void recordUpdate( bool _updatedExisting, int nChanged ) { + reset( true ); + nObjects = nChanged; + updatedExisting = _updatedExisting ? True : False; + } + void recordDelete( int nDeleted ) { + reset( true ); + nObjects = nDeleted; + } + LastError() { + overridenById = false; + reset(); + } + void reset( bool _valid = false ) { + code = 0; + msg.clear(); + updatedExisting = NotUpdate; + nObjects = 0; + nPrev = 1; + valid = _valid; + disabled = false; + } + void appendSelf( BSONObjBuilder &b ); + static LastError noError; + }; + + extern class LastErrorHolder { + public: + LastErrorHolder() : _id( 0 ) {} + + LastError * get( bool create = false ); + + LastError * _get( bool create = false ); // may return a disabled LastError + + void reset( LastError * le ); + + /** + * id of 0 means should use thread local management + */ + void setID( int id ); + int getID(); + + void remove( int id ); + void release(); + + /** when db receives a message/request, call this */ + void startRequest( Message& m , LastError * connectionOwned ); + void startRequest( Message& m ); + + // used to disable lastError reporting while processing a killCursors message + // disable causes get() to return 0. + LastError *disableForCommand(); // only call once per command invocation! + private: + ThreadLocalValue<int> _id; + boost::thread_specific_ptr<LastError> _tl; + + struct Status { + time_t time; + LastError *lerr; + }; + static boost::mutex _idsmutex; + map<int,Status> _ids; + } lastError; + + inline void raiseError(int code , const char *msg) { + LastError *le = lastError.get(); + if ( le == 0 ) { + DEV log() << "warning: lastError==0 can't report:" << msg << '\n'; + } else if ( le->disabled ) { + log() << "lastError disabled, can't report: " << msg << endl; + } else { + le->raiseError(code, msg); + } + } + + inline void recordUpdate( bool updatedExisting, int nChanged ) { + LastError *le = lastError.get(); + if ( le ) + le->recordUpdate( updatedExisting, nChanged ); + } + + inline void recordDelete( int nDeleted ) { + LastError *le = lastError.get(); + if ( le ) + le->recordDelete( nDeleted ); + } + +} // namespace mongo diff --git a/db/matcher.cpp b/db/matcher.cpp new file mode 100644 index 0000000..d71b7ef --- /dev/null +++ b/db/matcher.cpp @@ -0,0 +1,672 @@ +// matcher.cpp + +/* Matcher is our boolean expression evaluator for "where" clauses */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "matcher.h" +#include "../util/goodies.h" +#include "../util/unittest.h" +#include "storage.h" +#include "../scripting/engine.h" +#include "db.h" +#include "client.h" + +namespace mongo { + + //#include "minilex.h" + //MiniLex minilex; + + class Where { + public: + Where() { + jsScope = 0; + func = 0; + } + ~Where() { + + if ( scope.get() ) + scope->execSetup( "_mongo.readOnly = false;" , "make not read only" ); + + if ( jsScope ){ + delete jsScope; + jsScope = 0; + } + func = 0; + } + + auto_ptr<Scope> scope; + ScriptingFunction func; + BSONObj *jsScope; + + void setFunc(const char *code) { + massert( 10341 , "scope has to be created first!" , scope.get() ); + func = scope->createFunction( code ); + } + + }; + + Matcher::~Matcher() { + delete where; + where = 0; + } + + ElementMatcher::ElementMatcher( BSONElement _e , int _op ) : toMatch( _e ) , compareOp( _op ) { + if ( _op == BSONObj::opMOD ){ + BSONObj o = _e.embeddedObject().firstElement().embeddedObject(); + mod = o["0"].numberInt(); + modm = o["1"].numberInt(); + + uassert( 10073 , "mod can't be 0" , mod ); + } + else if ( _op == BSONObj::opTYPE ){ + type = (BSONType)(_e.embeddedObject().firstElement().numberInt()); + } + else if ( _op == BSONObj::opELEM_MATCH ){ + BSONElement m = toMatch.embeddedObjectUserCheck().firstElement(); + uassert( 12517 , "$elemMatch needs an Object" , m.type() == Object ); + subMatcher.reset( new Matcher( m.embeddedObject() ) ); + } + } + + + ElementMatcher::~ElementMatcher(){ + } + + + +} // namespace mongo + +#include "pdfile.h" + +namespace { + inline pcrecpp::RE_Options flags2options(const char* flags){ + pcrecpp::RE_Options options; + options.set_utf8(true); + while ( flags && *flags ) { + if ( *flags == 'i' ) + options.set_caseless(true); + else if ( *flags == 'm' ) + options.set_multiline(true); + else if ( *flags == 'x' ) + options.set_extended(true); + flags++; + } + return options; + } +} + +namespace mongo { + + CoveredIndexMatcher::CoveredIndexMatcher(const BSONObj &jsobj, const BSONObj &indexKeyPattern) : + _keyMatcher(jsobj.filterFieldsUndotted(indexKeyPattern, true), + indexKeyPattern), + _docMatcher(jsobj) + { + _needRecord = ! ( + _docMatcher.keyMatch() && + _keyMatcher.jsobj.nFields() == _docMatcher.jsobj.nFields() + ); + } + + bool CoveredIndexMatcher::matches(const BSONObj &key, const DiskLoc &recLoc ) { + if ( _keyMatcher.keyMatch() ) { + if ( !_keyMatcher.matches(key) ) { + return false; + } + } + + if ( ! _needRecord ){ + return true; + } + + return _docMatcher.matches(recLoc.rec()); + } + + + /* _jsobj - the query pattern + */ + Matcher::Matcher(const BSONObj &_jsobj, const BSONObj &constrainIndexKey) : + where(0), jsobj(_jsobj), haveSize(), all(), hasArray(0), _atomic(false), nRegex(0) { + + BSONObjIterator i(jsobj); + while ( i.more() ) { + BSONElement e = i.next(); + + if ( ( e.type() == CodeWScope || e.type() == Code || e.type() == String ) && strcmp(e.fieldName(), "$where")==0 ) { + // $where: function()... + uassert( 10066 , "$where occurs twice?", where == 0 ); + uassert( 10067 , "$where query, but no script engine", globalScriptEngine ); + where = new Where(); + where->scope = globalScriptEngine->getPooledScope( cc().ns() ); + where->scope->localConnect( cc().database()->name.c_str() ); + + if ( e.type() == CodeWScope ) { + where->setFunc( e.codeWScopeCode() ); + where->jsScope = new BSONObj( e.codeWScopeScopeData() , 0 ); + } + else { + const char *code = e.valuestr(); + where->setFunc(code); + } + + where->scope->execSetup( "_mongo.readOnly = true;" , "make read only" ); + + continue; + } + + if ( e.type() == RegEx ) { + if ( nRegex >= 4 ) { + out() << "ERROR: too many regexes in query" << endl; + } + else { + RegexMatcher& rm = regexs[nRegex]; + rm.re = new pcrecpp::RE(e.regex(), flags2options(e.regexFlags())); + rm.fieldName = e.fieldName(); + nRegex++; + } + continue; + } + + // greater than / less than... + // e.g., e == { a : { $gt : 3 } } + // or + // { a : { $in : [1,2,3] } } + if ( e.type() == Object ) { + // support {$regex:"a|b", $options:"imx"} + const char* regex = NULL; + const char* flags = ""; + + // e.g., fe == { $gt : 3 } + BSONObjIterator j(e.embeddedObject()); + bool isOperator = false; + while ( j.more() ) { + BSONElement fe = j.next(); + const char *fn = fe.fieldName(); + + if ( fn[0] == '$' && fn[1] ) { + int op = fe.getGtLtOp( -1 ); + + if ( op == -1 ){ + if ( fn[1] == 'r' && fn[2] == 'e' && fn[3] == 'f' && fn[4] == 0 ){ + break; // { $ref : xxx } - treat as normal object + } + uassert( 10068 , (string)"invalid operator: " + fn , op != -1 ); + } + + isOperator = true; + + switch ( op ){ + case BSONObj::GT: + case BSONObj::GTE: + case BSONObj::LT: + case BSONObj::LTE:{ + shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() ); + _builders.push_back( b ); + b->appendAs(fe, e.fieldName()); + addBasic(b->done().firstElement(), op); + isOperator = true; + break; + } + case BSONObj::NE:{ + shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() ); + _builders.push_back( b ); + b->appendAs(fe, e.fieldName()); + addBasic(b->done().firstElement(), BSONObj::NE); + break; + } + case BSONObj::opALL: + all = true; + case BSONObj::opIN: + case BSONObj::NIN: + basics.push_back( ElementMatcher( e , op , fe.embeddedObject() ) ); + break; + case BSONObj::opMOD: + case BSONObj::opTYPE: + case BSONObj::opELEM_MATCH: + // these are types where ElementMatcher has all the info + basics.push_back( ElementMatcher( e , op ) ); + break; + case BSONObj::opSIZE:{ + shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() ); + _builders.push_back( b ); + b->appendAs(fe, e.fieldName()); + addBasic(b->done().firstElement(), BSONObj::opSIZE); + haveSize = true; + break; + } + case BSONObj::opEXISTS:{ + shared_ptr< BSONObjBuilder > b( new BSONObjBuilder() ); + _builders.push_back( b ); + b->appendAs(fe, e.fieldName()); + addBasic(b->done().firstElement(), BSONObj::opEXISTS); + break; + } + case BSONObj::opREGEX:{ + regex = fe.valuestrsafe(); + break; + } + case BSONObj::opOPTIONS:{ + flags = fe.valuestrsafe(); + break; + } + default: + uassert( 10069 , (string)"BUG - can't operator for: " + fn , 0 ); + } + + } + else { + isOperator = false; + break; + } + } + if (regex){ + if ( nRegex >= 4 ) { + out() << "ERROR: too many regexes in query" << endl; + } else { + RegexMatcher& rm = regexs[nRegex]; + rm.re = new pcrecpp::RE(regex, flags2options(flags)); + rm.fieldName = e.fieldName(); + nRegex++; + } + } + if ( isOperator ) + continue; + } + + if ( e.type() == Array ){ + hasArray = true; + } + else if( strcmp(e.fieldName(), "$atomic") == 0 ) { + _atomic = e.trueValue(); + continue; + } + + // normal, simple case e.g. { a : "foo" } + addBasic(e, BSONObj::Equality); + } + + constrainIndexKey_ = constrainIndexKey; + } + + inline int Matcher::valuesMatch(const BSONElement& l, const BSONElement& r, int op, const ElementMatcher& bm) { + assert( op != BSONObj::NE && op != BSONObj::NIN ); + + if ( op == BSONObj::Equality ) + return l.valuesEqual(r); + + if ( op == BSONObj::opIN ) { + // { $in : [1,2,3] } + return bm.myset->count(l); + } + + if ( op == BSONObj::opSIZE ) { + if ( l.type() != Array ) + return 0; + int count = 0; + BSONObjIterator i( l.embeddedObject() ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + ++count; + } + return count == r.number(); + } + + if ( op == BSONObj::opMOD ){ + if ( ! l.isNumber() ) + return false; + + return l.numberLong() % bm.mod == bm.modm; + } + + if ( op == BSONObj::opTYPE ){ + return bm.type == l.type(); + } + + /* check LT, GTE, ... */ + if ( l.canonicalType() != r.canonicalType() ) + return false; + int c = compareElementValues(l, r); + if ( c < -1 ) c = -1; + if ( c > 1 ) c = 1; + int z = 1 << (c+1); + return (op & z); + } + + int Matcher::matchesNe(const char *fieldName, const BSONElement &toMatch, const BSONObj &obj, const ElementMatcher& bm ) { + int ret = matchesDotted( fieldName, toMatch, obj, BSONObj::Equality, bm ); + if ( bm.toMatch.type() != jstNULL ) + return ( ret <= 0 ) ? 1 : 0; + else + return -ret; + } + + int retMissing( const ElementMatcher &bm ) { + if ( bm.compareOp != BSONObj::opEXISTS ) + return 0; + return bm.toMatch.boolean() ? -1 : 1; + } + + /* Check if a particular field matches. + + fieldName - field to match "a.b" if we are reaching into an embedded object. + toMatch - element we want to match. + obj - database object to check against + compareOp - Equality, LT, GT, etc. + isArr - + + Special forms: + + { "a.b" : 3 } means obj.a.b == 3 + { a : { $lt : 3 } } means obj.a < 3 + { a : { $in : [1,2] } } means [1,2].contains(obj.a) + + return value + -1 mismatch + 0 missing element + 1 match + */ + int Matcher::matchesDotted(const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, int compareOp, const ElementMatcher& bm , bool isArr) { + + if ( compareOp == BSONObj::opALL ) { + if ( bm.myset->size() == 0 ) + return -1; // is this desired? + BSONObjSetDefaultOrder actualKeys; + IndexSpec( BSON( fieldName << 1 ) ).getKeys( obj, actualKeys ); + if ( actualKeys.size() == 0 ) + return 0; + for( set< BSONElement, element_lt >::const_iterator i = bm.myset->begin(); i != bm.myset->end(); ++i ) { + // ignore nulls + if ( i->type() == jstNULL ) + continue; + // parallel traversal would be faster worst case I guess + BSONObjBuilder b; + b.appendAs( *i, "" ); + if ( !actualKeys.count( b.done() ) ) + return -1; + } + return 1; + } + + if ( compareOp == BSONObj::NE ) + return matchesNe( fieldName, toMatch, obj, bm ); + if ( compareOp == BSONObj::NIN ) { + for( set<BSONElement,element_lt>::const_iterator i = bm.myset->begin(); i != bm.myset->end(); ++i ) { + int ret = matchesNe( fieldName, *i, obj, bm ); + if ( ret != 1 ) + return ret; + } + return 1; + } + + BSONElement e; + bool indexed = !constrainIndexKey_.isEmpty(); + if ( indexed ) { + e = obj.getFieldUsingIndexNames(fieldName, constrainIndexKey_); + assert( !e.eoo() ); + } else { + if ( isArr ) { + BSONObjIterator ai(obj); + bool found = false; + while ( ai.moreWithEOO() ) { + BSONElement z = ai.next(); + if ( z.type() == Object ) { + BSONObj eo = z.embeddedObject(); + int cmp = matchesDotted(fieldName, toMatch, eo, compareOp, bm, false); + if ( cmp > 0 ) { + return 1; + } else if ( cmp < 0 ) { + found = true; + } + } + } + return found ? -1 : retMissing( bm ); + } + const char *p = strchr(fieldName, '.'); + if ( p ) { + string left(fieldName, p-fieldName); + + BSONElement se = obj.getField(left.c_str()); + if ( se.eoo() ) + return retMissing( bm ); + if ( se.type() != Object && se.type() != Array ) + return retMissing( bm ); + + BSONObj eo = se.embeddedObject(); + return matchesDotted(p+1, toMatch, eo, compareOp, bm, se.type() == Array); + } else { + e = obj.getField(fieldName); + } + } + + if ( compareOp == BSONObj::opEXISTS ) { + return ( e.eoo() ^ toMatch.boolean() ) ? 1 : -1; + } else if ( ( e.type() != Array || indexed || compareOp == BSONObj::opSIZE ) && + valuesMatch(e, toMatch, compareOp, bm ) ) { + return 1; + } else if ( e.type() == Array && compareOp != BSONObj::opSIZE ) { + + BSONObjIterator ai(e.embeddedObject()); + + while ( ai.moreWithEOO() ) { + BSONElement z = ai.next(); + + if ( compareOp == BSONObj::opELEM_MATCH ){ + // SERVER-377 + if ( z.type() == Object && bm.subMatcher->matches( z.embeddedObject() ) ) + return 1; + } + else { + if ( valuesMatch( z, toMatch, compareOp, bm) ) { + return 1; + } + } + + } + + if ( compareOp == BSONObj::Equality && e.woCompare( toMatch ) == 0 ){ + // match an entire array to itself + return 1; + } + + } + else if ( e.eoo() ) { + // 0 indicates "missing element" + return 0; + } + return -1; + } + + extern int dump; + + inline bool regexMatches(RegexMatcher& rm, const BSONElement& e) { + char buf[64]; + const char *p = buf; + if ( e.type() == String || e.type() == Symbol ) + p = e.valuestr(); + else if ( e.isNumber() ) { + sprintf(buf, "%f", e.number()); + } + else if ( e.type() == Date ) { + Date_t d = e.date(); + time_t t = (d.millis/1000); + time_t_to_String(t, buf); + } + else + return false; + return rm.re->PartialMatch(p); + } + + /* See if an object matches the query. + */ + bool Matcher::matches(const BSONObj& jsobj ) { + /* assuming there is usually only one thing to match. if more this + could be slow sometimes. */ + + // check normal non-regex cases: + for ( unsigned i = 0; i < basics.size(); i++ ) { + ElementMatcher& bm = basics[i]; + BSONElement& m = bm.toMatch; + // -1=mismatch. 0=missing element. 1=match + int cmp = matchesDotted(m.fieldName(), m, jsobj, bm.compareOp, bm ); + if ( cmp < 0 ) + return false; + if ( cmp == 0 ) { + /* missing is ok iff we were looking for null */ + if ( m.type() == jstNULL || m.type() == Undefined ) { + if ( bm.compareOp == BSONObj::NE ) { + return false; + } + } else { + return false; + } + } + } + + for ( int r = 0; r < nRegex; r++ ) { + RegexMatcher& rm = regexs[r]; + BSONElementSet s; + if ( !constrainIndexKey_.isEmpty() ) { + BSONElement e = jsobj.getFieldUsingIndexNames(rm.fieldName, constrainIndexKey_); + if ( !e.eoo() ) + s.insert( e ); + } else { + jsobj.getFieldsDotted( rm.fieldName, s ); + } + bool match = false; + for( BSONElementSet::const_iterator i = s.begin(); i != s.end(); ++i ) + if ( regexMatches(rm, *i) ) + match = true; + if ( !match ) + return false; + } + + if ( where ) { + if ( where->func == 0 ) { + uassert( 10070 , "$where compile error", false); + return false; // didn't compile + } + + if ( where->jsScope ){ + where->scope->init( where->jsScope ); + } + where->scope->setThis( const_cast< BSONObj * >( &jsobj ) ); + where->scope->setObject( "obj", const_cast< BSONObj & >( jsobj ) ); + where->scope->setBoolean( "fullObject" , true ); // this is a hack b/c fullObject used to be relevant + + int err = where->scope->invoke( where->func , BSONObj() , 1000 * 60 , false ); + where->scope->setThis( 0 ); + if ( err == -3 ) { // INVOKE_ERROR + stringstream ss; + ss << "error on invocation of $where function:\n" + << where->scope->getError(); + uassert( 10071 , ss.str(), false); + return false; + } else if ( err != 0 ) { // ! INVOKE_SUCCESS + uassert( 10072 , "unknown error in invocation of $where function", false); + return false; + } + return where->scope->getBoolean( "return" ) != 0; + + } + + return true; + } + + struct JSObj1 js1; + +#pragma pack(1) + struct JSObj2 { + JSObj2() { + totsize=sizeof(JSObj2); + s = String; + strcpy_s(sname, 7, "abcdef"); + slen = 10; + strcpy_s(sval, 10, "123456789"); + eoo = EOO; + } + unsigned totsize; + char s; + char sname[7]; + unsigned slen; + char sval[10]; + char eoo; + } js2; + + struct JSUnitTest : public UnitTest { + void run() { + + BSONObj j1((const char *) &js1); + BSONObj j2((const char *) &js2); + Matcher m(j2); + assert( m.matches(j1) ); + js2.sval[0] = 'z'; + assert( !m.matches(j1) ); + Matcher n(j1); + assert( n.matches(j1) ); + assert( !n.matches(j2) ); + + BSONObj j0 = BSONObj(); +// BSONObj j0((const char *) &js0); + Matcher p(j0); + assert( p.matches(j1) ); + assert( p.matches(j2) ); + } + } jsunittest; + +#pragma pack() + + struct RXTest : public UnitTest { + + RXTest() { + } + + void run() { + /* + static const boost::regex e("(\\d{4}[- ]){3}\\d{4}"); + static const boost::regex b("....."); + out() << "regex result: " << regex_match("hello", e) << endl; + out() << "regex result: " << regex_match("abcoo", b) << endl; + */ + + int ret = 0; + + pcre_config( PCRE_CONFIG_UTF8 , &ret ); + massert( 10342 , "pcre not compiled with utf8 support" , ret ); + + pcrecpp::RE re1(")({a}h.*o"); + pcrecpp::RE re("h.llo"); + assert( re.FullMatch("hello") ); + assert( !re1.FullMatch("hello") ); + + + pcrecpp::RE_Options options; + options.set_utf8(true); + pcrecpp::RE part("dwi", options); + assert( part.PartialMatch("dwight") ); + + pcre_config( PCRE_CONFIG_UNICODE_PROPERTIES , &ret ); + if ( ! ret ) + cout << "warning: some regex utf8 things will not work. pcre build doesn't have --enable-unicode-properties" << endl; + + } + } rxtest; + +} // namespace mongo diff --git a/db/matcher.h b/db/matcher.h new file mode 100644 index 0000000..f1609f9 --- /dev/null +++ b/db/matcher.h @@ -0,0 +1,184 @@ +// matcher.h + +/* Matcher is our boolean expression evaluator for "where" clauses */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "jsobj.h" +#include <pcrecpp.h> + +namespace mongo { + + class CoveredIndexMatcher; + class Matcher; + + class RegexMatcher { + public: + const char *fieldName; + pcrecpp::RE *re; + RegexMatcher() { + re = 0; + } + ~RegexMatcher() { + delete re; + } + }; + + struct element_lt + { + bool operator()(const BSONElement& l, const BSONElement& r) const + { + int x = (int) l.canonicalType() - (int) r.canonicalType(); + if ( x < 0 ) return true; + else if ( x > 0 ) return false; + return compareElementValues(l,r) < 0; + } + }; + + + class ElementMatcher { + public: + + ElementMatcher() { + } + + ElementMatcher( BSONElement _e , int _op ); + + ElementMatcher( BSONElement _e , int _op , const BSONObj& array ) : toMatch( _e ) , compareOp( _op ) { + + myset.reset( new set<BSONElement,element_lt>() ); + + BSONObjIterator i( array ); + while ( i.more() ) { + BSONElement ie = i.next(); + myset->insert(ie); + } + } + + ~ElementMatcher(); + + BSONElement toMatch; + int compareOp; + shared_ptr< set<BSONElement,element_lt> > myset; + + // these are for specific operators + int mod; + int modm; + BSONType type; + + shared_ptr<Matcher> subMatcher; + }; + +// SQL where clause equivalent + class Where; + class DiskLoc; + + /* Match BSON objects against a query pattern. + + e.g. + db.foo.find( { a : 3 } ); + + { a : 3 } is the pattern object. See wiki documentation for full info. + + GT/LT: + { a : { $gt : 3 } } + Not equal: + { a : { $ne : 3 } } + + TODO: we should rewrite the matcher to be more an AST style. + */ + class Matcher : boost::noncopyable { + int matchesDotted( + const char *fieldName, + const BSONElement& toMatch, const BSONObj& obj, + int compareOp, const ElementMatcher& bm, bool isArr = false); + + int matchesNe( + const char *fieldName, + const BSONElement &toMatch, const BSONObj &obj, + const ElementMatcher&bm); + + public: + static int opDirection(int op) { + return op <= BSONObj::LTE ? -1 : 1; + } + + // Only specify constrainIndexKey if matches() will be called with + // index keys having empty string field names. + Matcher(const BSONObj &pattern, const BSONObj &constrainIndexKey = BSONObj()); + + ~Matcher(); + + bool matches(const BSONObj& j); + + bool keyMatch() const { return !all && !haveSize && !hasArray; } + + bool atomic() const { return _atomic; } + + private: + void addBasic(const BSONElement &e, int c) { + // TODO May want to selectively ignore these element types based on op type. + if ( e.type() == MinKey || e.type() == MaxKey ) + return; + basics.push_back( ElementMatcher( e , c ) ); + } + + int valuesMatch(const BSONElement& l, const BSONElement& r, int op, const ElementMatcher& bm); + + Where *where; // set if query uses $where + BSONObj jsobj; // the query pattern. e.g., { name: "joe" } + BSONObj constrainIndexKey_; + vector<ElementMatcher> basics; +// int n; // # of basicmatcher items + bool haveSize; + bool all; + bool hasArray; + + /* $atomic - if true, a multi document operation (some removes, updates) + should be done atomically. in that case, we do not yield - + i.e. we stay locked the whole time. + http://www.mongodb.org/display/DOCS/Removing[ + */ + bool _atomic; + + RegexMatcher regexs[4]; + int nRegex; + + // so we delete the mem when we're done: + vector< shared_ptr< BSONObjBuilder > > _builders; + + friend class CoveredIndexMatcher; + }; + + // If match succeeds on index key, then attempt to match full document. + class CoveredIndexMatcher : boost::noncopyable { + public: + CoveredIndexMatcher(const BSONObj &pattern, const BSONObj &indexKeyPattern); + bool matches(const BSONObj &o){ return _docMatcher.matches( o ); } + bool matches(const BSONObj &key, const DiskLoc &recLoc); + bool needRecord(){ return _needRecord; } + + Matcher& docMatcher() { return _docMatcher; } + private: + Matcher _keyMatcher; + Matcher _docMatcher; + bool _needRecord; + }; + +} // namespace mongo diff --git a/db/minilex.h b/db/minilex.h new file mode 100644 index 0000000..ba8df26 --- /dev/null +++ b/db/minilex.h @@ -0,0 +1,160 @@ +// minilex.h +// mini js lexical analyzer. idea is to be dumb and fast. + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace mongo { + +#if defined(_WIN32) + +} // namespace mongo + +#include <hash_map> +using namespace stdext; + +namespace mongo { + + typedef const char * MyStr; + struct less_str { + bool operator()(const MyStr & x, const MyStr & y) const { + if ( strcmp(x, y) > 0) + return true; + + return false; + } + }; + + typedef hash_map<const char*, int, hash_compare<const char *, less_str> > strhashmap; + +#else + +} // namespace mongo + +#include <ext/hash_map> + +namespace mongo { + + using namespace __gnu_cxx; + + typedef const char * MyStr; + struct eq_str { + bool operator()(const MyStr & x, const MyStr & y) const { + if ( strcmp(x, y) == 0) + return true; + + return false; + } + }; + + typedef hash_map<const char*, int, hash<const char *>, eq_str > strhashmap; + +#endif + + struct MiniLex { + strhashmap reserved; + bool ic[256]; // ic=Identifier Character + bool starter[256]; + + // dm: very dumb about comments and escaped quotes -- but we are faster then at least, + // albeit returning too much (which is ok for jsbobj current usage). + void grabVariables(char *code /*modified and must stay in scope*/, strhashmap& vars) { + char *p = code; + char last = 0; + while ( *p ) { + if ( starter[*p] ) { + char *q = p+1; + while ( *q && ic[*q] ) q++; + const char *identifier = p; + bool done = *q == 0; + *q = 0; + if ( !reserved.count(identifier) ) { + // we try to be smart about 'obj' but have to be careful as obj.obj + // can happen; this is so that nFields is right for simplistic where cases + // so we can stop scanning in jsobj when we find the field of interest. + if ( strcmp(identifier,"obj")==0 && p>code && p[-1] != '.' ) + ; + else + vars[identifier] = 1; + } + if ( done ) + break; + p = q + 1; + continue; + } + + if ( *p == '\'' ) { + p++; + while ( *p && *p != '\'' ) p++; + } + else if ( *p == '"' ) { + p++; + while ( *p && *p != '"' ) p++; + } + p++; + } + } + + MiniLex() { + strhashmap atest; + atest["foo"] = 3; + assert( atest.count("bar") == 0 ); + assert( atest.count("foo") == 1 ); + assert( atest["foo"] == 3 ); + + for ( int i = 0; i < 256; i++ ) { + ic[i] = starter[i] = false; + } + for ( int i = 'a'; i <= 'z'; i++ ) + ic[i] = starter[i] = true; + for ( int i = 'A'; i <= 'Z'; i++ ) + ic[i] = starter[i] = true; + for ( int i = '0'; i <= '9'; i++ ) + ic[i] = true; + for ( int i = 128; i < 256; i++ ) + ic[i] = starter[i] = true; + ic['$'] = starter['$'] = true; + ic['_'] = starter['_'] = true; + + reserved["break"] = true; + reserved["case"] = true; + reserved["catch"] = true; + reserved["continue"] = true; + reserved["default"] = true; + reserved["delete"] = true; + reserved["do"] = true; + reserved["else"] = true; + reserved["finally"] = true; + reserved["for"] = true; + reserved["function"] = true; + reserved["if"] = true; + reserved["in"] = true; + reserved["instanceof"] = true; + reserved["new"] = true; + reserved["return"] = true; + reserved["switch"] = true; + reserved["this"] = true; + reserved["throw"] = true; + reserved["try"] = true; + reserved["typeof"] = true; + reserved["var"] = true; + reserved["void"] = true; + reserved["while"] = true; + reserved["with "] = true; + } + }; + +} // namespace mongo diff --git a/db/module.cpp b/db/module.cpp new file mode 100644 index 0000000..d218fe6 --- /dev/null +++ b/db/module.cpp @@ -0,0 +1,52 @@ +// module.cpp + +#include "stdafx.h" +#include "module.h" + +namespace mongo { + + std::list<Module*> * Module::_all; + + Module::Module( const string& name ) + : _name( name ) , _options( (string)"Module " + name + " options" ){ + if ( ! _all ) + _all = new list<Module*>(); + _all->push_back( this ); + } + + Module::~Module(){} + + void Module::addOptions( program_options::options_description& options ){ + if ( ! _all ) { + return; + } + for ( list<Module*>::iterator i=_all->begin(); i!=_all->end(); i++ ){ + Module* m = *i; + options.add( m->_options ); + } + } + + void Module::configAll( program_options::variables_map& params ){ + if ( ! _all ) { + return; + } + for ( list<Module*>::iterator i=_all->begin(); i!=_all->end(); i++ ){ + Module* m = *i; + m->config( params ); + } + + } + + + void Module::initAll(){ + if ( ! _all ) { + return; + } + for ( list<Module*>::iterator i=_all->begin(); i!=_all->end(); i++ ){ + Module* m = *i; + m->init(); + } + + } + +} diff --git a/db/module.h b/db/module.h new file mode 100644 index 0000000..728e861 --- /dev/null +++ b/db/module.h @@ -0,0 +1,70 @@ +// module.h + +/** +* Copyright (C) 2008 10gen Inc.info +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include <boost/program_options.hpp> +#include <list> + +namespace mongo { + + /** + * Module is the base class for adding modules to MongoDB + * modules allow adding hooks and features to mongo + * the idea is to add hooks into the main code for module support where needed + * some ideas are: monitoring, indexes, full text search + */ + class Module { + public: + Module( const string& name ); + virtual ~Module(); + + boost::program_options::options_description_easy_init add_options(){ + return _options.add_options(); + } + + /** + * read config from command line + */ + virtual void config( program_options::variables_map& params ) = 0; + + /** + * called after configuration when the server is ready start + */ + virtual void init() = 0; + + /** + * called when the database is about to shutdown + */ + virtual void shutdown() = 0; + + const string& getName(){ return _name; } + + // --- static things + + static void addOptions( program_options::options_description& options ); + static void configAll( program_options::variables_map& params ); + static void initAll(); + + private: + static std::list<Module*> * _all; + string _name; + program_options::options_description _options; + }; +} diff --git a/db/modules/mms.cpp b/db/modules/mms.cpp new file mode 100644 index 0000000..9c00e60 --- /dev/null +++ b/db/modules/mms.cpp @@ -0,0 +1,144 @@ +// mms.cpp + +#include "stdafx.h" +#include "../db.h" +#include "../instance.h" +#include "../module.h" +#include "../../util/httpclient.h" +#include "../../util/background.h" + +namespace po = boost::program_options; + +namespace mongo { + + /** Mongo Monitoring Service + if enabled, this runs in the background ands pings mss + */ + class MMS : public BackgroundJob , Module { + public: + + MMS() + : Module( "mms" ) , _baseurl( "http://mms.10gen.com/ping/" ) , + _secsToSleep(1) , _token( "" ) , _name( "" ) { + + add_options() + ( "mms-token" , po::value<string>() , "account token for mongo monitoring server" ) + ( "mms-name" , po::value<string>() , "server name mongo monitoring server" ) + ( "mms-interval" , po::value<int>()->default_value(30) , "ping interval for mongo monitoring server" ) + ; + } + + ~MMS(){} + + void config( program_options::variables_map& params ){ + if ( params.count( "mms-token" ) ){ + _token = params["mms-token"].as<string>(); + } + if ( params.count( "mms-name" ) ){ + _name = params["mms-name"].as<string>(); + } + _secsToSleep = params["mms-interval"].as<int>(); + } + + void run(){ + if ( _token.size() == 0 && _name.size() == 0 ){ + log(1) << "mms not configured" << endl; + return; + } + + if ( _token.size() == 0 ){ + log() << "no token for mms - not running" << endl; + return; + } + + if ( _name.size() == 0 ){ + log() << "no name for mms - not running" << endl; + return; + } + + log() << "mms monitor staring... token:" << _token << " name:" << _name << " interval: " << _secsToSleep << endl; + + unsigned long long lastTime = 0; + unsigned long long lastLockTime = 0; + + while ( ! inShutdown() ){ + sleepsecs( _secsToSleep ); + + stringstream url; + url << _baseurl << _token << "?"; + url << "monitor_name=" << _name << "&"; + url << "version=" << versionString << "&"; + url << "git_hash=" << gitVersion() << "&"; + + { //percent_locked + unsigned long long time = curTimeMicros64(); + unsigned long long start , lock; + dbMutex.info().getTimingInfo( start , lock ); + if ( lastTime ){ + double timeDiff = (double) (time - lastTime); + double lockDiff = (double) (lock - lastLockTime); + url << "percent_locked=" << (int)ceil( 100 * ( lockDiff / timeDiff ) ) << "&"; + } + lastTime = time; + lastLockTime = lock; + } + + vector< string > dbNames; + getDatabaseNames( dbNames ); + boost::intmax_t totalSize = 0; + for ( vector< string >::iterator i = dbNames.begin(); i != dbNames.end(); ++i ) { + boost::intmax_t size = dbSize( i->c_str() ); + totalSize += size; + } + url << "data_size=" << totalSize / ( 1024 * 1024 ) << "&"; + + + + /* TODO: + message_operations + update_operations + insert_operations + get_more_operations + delete_operations + kill_cursors_operations + */ + + + log(1) << "mms url: " << url.str() << endl; + + try { + HttpClient c; + map<string,string> headers; + stringstream ss; + int rc = c.get( url.str() , headers , ss ); + log(1) << "\t response code: " << rc << endl; + if ( rc != 200 ){ + log() << "mms error response code:" << rc << endl; + log(1) << "mms error body:" << ss.str() << endl; + } + } + catch ( std::exception& e ){ + log() << "mms get exception: " << e.what() << endl; + } + } + } + + void init(){ go(); } + + void shutdown(){ + // TODO + } + + private: + string _baseurl; + int _secsToSleep; + + string _token; + string _name; + + } /* mms */; + +} + + + diff --git a/db/mr.cpp b/db/mr.cpp new file mode 100644 index 0000000..ff88d9e --- /dev/null +++ b/db/mr.cpp @@ -0,0 +1,596 @@ +// mr.cpp + +/** + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "db.h" +#include "instance.h" +#include "commands.h" +#include "../scripting/engine.h" +#include "../client/dbclient.h" +#include "../client/connpool.h" +#include "../client/parallel.h" + +namespace mongo { + + namespace mr { + + class MyCmp { + public: + MyCmp(){} + bool operator()( const BSONObj &l, const BSONObj &r ) const { + return l.firstElement().woCompare( r.firstElement() ) < 0; + } + }; + + typedef pair<BSONObj,BSONObj> Data; + //typedef list< Data > InMemory; + typedef map< BSONObj,list<BSONObj>,MyCmp > InMemory; + + BSONObj reduceValues( list<BSONObj>& values , Scope * s , ScriptingFunction reduce , bool final , ScriptingFunction finalize ){ + uassert( 10074 , "need values" , values.size() ); + + int sizeEstimate = ( values.size() * values.begin()->getField( "value" ).size() ) + 128; + BSONObj key; + + BSONObjBuilder reduceArgs( sizeEstimate ); + + BSONObjBuilder valueBuilder( sizeEstimate ); + int n = 0; + for ( list<BSONObj>::iterator i=values.begin(); i!=values.end(); i++){ + BSONObj o = *i; + BSONObjIterator j(o); + BSONElement keyE = j.next(); + if ( n == 0 ){ + reduceArgs.append( keyE ); + BSONObjBuilder temp; + temp.append( keyE ); + key = temp.obj(); + } + valueBuilder.appendAs( j.next() , BSONObjBuilder::numStr( n++ ).c_str() ); + } + + reduceArgs.appendArray( "values" , valueBuilder.obj() ); + BSONObj args = reduceArgs.obj(); + + s->invokeSafe( reduce , args ); + if ( s->type( "return" ) == Array ){ + uassert( 10075 , "reduce -> multiple not supported yet",0); + return BSONObj(); + } + + if ( finalize ){ + BSONObjBuilder b; + b.appendAs( key.firstElement() , "_id" ); + s->append( b , "value" , "return" ); + s->invokeSafe( finalize , b.obj() ); + } + + BSONObjBuilder b; + b.appendAs( key.firstElement() , final ? "_id" : "0" ); + s->append( b , final ? "value" : "1" , "return" ); + return b.obj(); + } + + class MRSetup { + public: + MRSetup( const string& _dbname , const BSONObj& cmdObj , bool markAsTemp = true ){ + static int jobNumber = 1; + + dbname = _dbname; + ns = dbname + "." + cmdObj.firstElement().valuestr(); + + verbose = cmdObj["verbose"].trueValue(); + keeptemp = cmdObj["keeptemp"].trueValue(); + + { // setup names + stringstream ss; + if ( ! keeptemp ) + ss << "tmp."; + ss << "mr." << cmdObj.firstElement().fieldName() << "_" << time(0) << "_" << jobNumber++; + tempShort = ss.str(); + tempLong = dbname + "." + tempShort; + incLong = tempLong + "_inc"; + + if ( ! keeptemp && markAsTemp ) + cc().addTempCollection( tempLong ); + + if ( cmdObj["out"].type() == String ) + finalShort = cmdObj["out"].valuestr(); + else + finalShort = tempShort; + + finalLong = dbname + "." + finalShort; + + } + + { // code + mapCode = cmdObj["map"].ascode(); + reduceCode = cmdObj["reduce"].ascode(); + if ( cmdObj["finalize"].type() ){ + finalizeCode = cmdObj["finalize"].ascode(); + } + + + if ( cmdObj["mapparams"].type() == Array ){ + mapparams = cmdObj["mapparams"].embeddedObjectUserCheck(); + } + + if ( cmdObj["scope"].type() == Object ){ + scopeSetup = cmdObj["scope"].embeddedObjectUserCheck(); + } + + } + + { // query options + if ( cmdObj["query"].type() == Object ){ + filter = cmdObj["query"].embeddedObjectUserCheck(); + q = filter; + } + + if ( cmdObj["sort"].type() == Object ) + q.sort( cmdObj["sort"].embeddedObjectUserCheck() ); + + if ( cmdObj["limit"].isNumber() ) + limit = cmdObj["limit"].numberLong(); + else + limit = 0; + } + } + + /** + @return number objects in collection + */ + long long renameIfNeeded( DBDirectClient& db ){ + if ( finalLong != tempLong ){ + db.dropCollection( finalLong ); + if ( db.count( tempLong ) ){ + BSONObj info; + uassert( 10076 , "rename failed" , db.runCommand( "admin" , BSON( "renameCollection" << tempLong << "to" << finalLong ) , info ) ); + } + } + return db.count( finalLong ); + } + + string dbname; + string ns; + + // options + bool verbose; + bool keeptemp; + + // query options + + BSONObj filter; + Query q; + long long limit; + + // functions + + string mapCode; + string reduceCode; + string finalizeCode; + + BSONObj mapparams; + BSONObj scopeSetup; + + // output tables + string incLong; + + string tempShort; + string tempLong; + + string finalShort; + string finalLong; + + }; // end MRsetup + + class MRState { + public: + MRState( MRSetup& s ) : setup(s){ + scope = globalScriptEngine->getPooledScope( setup.dbname ); + scope->localConnect( setup.dbname.c_str() ); + + map = scope->createFunction( setup.mapCode.c_str() ); + if ( ! map ) + throw UserException( 9012, (string)"map compile failed: " + scope->getError() ); + + reduce = scope->createFunction( setup.reduceCode.c_str() ); + if ( ! reduce ) + throw UserException( 9013, (string)"reduce compile failed: " + scope->getError() ); + + if ( setup.finalizeCode.size() ) + finalize = scope->createFunction( setup.finalizeCode.c_str() ); + else + finalize = 0; + + if ( ! setup.scopeSetup.isEmpty() ) + scope->init( &setup.scopeSetup ); + + db.dropCollection( setup.tempLong ); + db.dropCollection( setup.incLong ); + + writelock l( setup.incLong ); + string err; + assert( userCreateNS( setup.incLong.c_str() , BSON( "autoIndexId" << 0 ) , err , false ) ); + + } + + void finalReduce( list<BSONObj>& values ){ + if ( values.size() == 0 ) + return; + + BSONObj key = values.begin()->firstElement().wrap( "_id" ); + BSONObj res = reduceValues( values , scope.get() , reduce , 1 , finalize ); + + writelock l( setup.tempLong ); + theDataFileMgr.insertAndLog( setup.tempLong.c_str() , res , false ); + } + + + MRSetup& setup; + auto_ptr<Scope> scope; + DBDirectClient db; + + ScriptingFunction map; + ScriptingFunction reduce; + ScriptingFunction finalize; + + }; + + class MRTL { + public: + MRTL( MRState& state ) : _state( state ){ + _temp = new InMemory(); + _size = 0; + numEmits = 0; + } + ~MRTL(){ + delete _temp; + } + + + void reduceInMemory(){ + + InMemory * old = _temp; + InMemory * n = new InMemory(); + _temp = n; + _size = 0; + + for ( InMemory::iterator i=old->begin(); i!=old->end(); i++ ){ + BSONObj key = i->first; + list<BSONObj>& all = i->second; + + if ( all.size() == 1 ){ + // this key has low cardinality, so just write to db + writelock l(_state.setup.incLong); + write( *(all.begin()) ); + } + else if ( all.size() > 1 ){ + BSONObj res = reduceValues( all , _state.scope.get() , _state.reduce , false , 0 ); + insert( res ); + } + } + + delete( old ); + + } + + void dump(){ + writelock l(_state.setup.incLong); + + for ( InMemory::iterator i=_temp->begin(); i!=_temp->end(); i++ ){ + list<BSONObj>& all = i->second; + if ( all.size() < 1 ) + continue; + + for ( list<BSONObj>::iterator j=all.begin(); j!=all.end(); j++ ) + write( *j ); + } + _temp->clear(); + _size = 0; + + } + + void insert( const BSONObj& a ){ + list<BSONObj>& all = (*_temp)[a]; + all.push_back( a ); + _size += a.objsize() + 16; + } + + void checkSize(){ + if ( _size < 1024 * 5 ) + return; + + long before = _size; + reduceInMemory(); + log(1) << " mr: did reduceInMemory " << before << " -->> " << _size << endl; + + if ( _size < 1024 * 15 ) + return; + + dump(); + log(1) << " mr: dumping to db" << endl; + } + + private: + void write( BSONObj& o ){ + theDataFileMgr.insert( _state.setup.incLong.c_str() , o , true ); + } + + MRState& _state; + + InMemory * _temp; + long _size; + + public: + long long numEmits; + }; + + boost::thread_specific_ptr<MRTL> _tlmr; + + BSONObj fast_emit( const BSONObj& args ){ + uassert( 10077 , "fast_emit takes 2 args" , args.nFields() == 2 ); + _tlmr->insert( args ); + _tlmr->numEmits++; + return BSONObj(); + } + + class MapReduceCommand : public Command { + public: + MapReduceCommand() : Command("mapreduce"){} + virtual bool slaveOk() { return true; } + + virtual void help( stringstream &help ) const { + help << "see http://www.mongodb.org/display/DOCS/MapReduce"; + } + + bool run(const char *dbname, BSONObj& cmd, string& errmsg, BSONObjBuilder& result, bool fromRepl ){ + Timer t; + Client::GodScope cg; + MRSetup mr( cc().database()->name , cmd ); + + log(1) << "mr ns: " << mr.ns << endl; + + if ( ! db.exists( mr.ns ) ){ + errmsg = "ns doesn't exist"; + return false; + } + + bool shouldHaveData = false; + + long long num = 0; + long long inReduce = 0; + + BSONObjBuilder countsBuilder; + BSONObjBuilder timingBuilder; + try { + + MRState state( mr ); + state.scope->injectNative( "emit" , fast_emit ); + + MRTL * mrtl = new MRTL( state ); + _tlmr.reset( mrtl ); + + ProgressMeter pm( db.count( mr.ns , mr.filter ) ); + auto_ptr<DBClientCursor> cursor = db.query( mr.ns , mr.q ); + long long mapTime = 0; + Timer mt; + while ( cursor->more() ){ + BSONObj o = cursor->next(); + + if ( mr.verbose ) mt.reset(); + + state.scope->setThis( &o ); + if ( state.scope->invoke( state.map , state.setup.mapparams , 0 , true ) ) + throw UserException( 9014, (string)"map invoke failed: " + state.scope->getError() ); + + if ( mr.verbose ) mapTime += mt.micros(); + + num++; + if ( num % 100 == 0 ){ + Timer t; + mrtl->checkSize(); + inReduce += t.micros(); + dbtemprelease temprlease; + } + pm.hit(); + + if ( mr.limit && num >= mr.limit ) + break; + } + + countsBuilder.append( "input" , num ); + countsBuilder.append( "emit" , mrtl->numEmits ); + if ( mrtl->numEmits ) + shouldHaveData = true; + + timingBuilder.append( "mapTime" , mapTime / 1000 ); + timingBuilder.append( "emitLoop" , t.millis() ); + + // final reduce + + mrtl->reduceInMemory(); + mrtl->dump(); + + BSONObj sortKey = BSON( "0" << 1 ); + db.ensureIndex( mr.incLong , sortKey ); + + BSONObj prev; + list<BSONObj> all; + + ProgressMeter fpm( db.count( mr.incLong ) ); + cursor = db.query( mr.incLong, Query().sort( sortKey ) ); + + while ( cursor->more() ){ + BSONObj o = cursor->next().getOwned(); + + if ( o.woSortOrder( prev , sortKey ) == 0 ){ + all.push_back( o ); + continue; + } + + state.finalReduce( all ); + + all.clear(); + prev = o; + all.push_back( o ); + fpm.hit(); + dbtemprelease tl; + } + + state.finalReduce( all ); + + _tlmr.reset( 0 ); + } + catch ( ... ){ + log() << "mr failed, removing collection" << endl; + db.dropCollection( mr.tempLong ); + db.dropCollection( mr.incLong ); + throw; + } + + db.dropCollection( mr.incLong ); + + long long finalCount = mr.renameIfNeeded( db ); + + timingBuilder.append( "total" , t.millis() ); + + result.append( "result" , mr.finalShort ); + result.append( "timeMillis" , t.millis() ); + countsBuilder.append( "output" , finalCount ); + if ( mr.verbose ) result.append( "timing" , timingBuilder.obj() ); + result.append( "counts" , countsBuilder.obj() ); + + if ( finalCount == 0 && shouldHaveData ){ + result.append( "cmd" , cmd ); + errmsg = "there were emits but no data!"; + return false; + } + + return true; + } + + private: + DBDirectClient db; + + } mapReduceCommand; + + class MapReduceFinishCommand : public Command { + public: + MapReduceFinishCommand() : Command( "mapreduce.shardedfinish" ){} + virtual bool slaveOk() { return true; } + + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + dbtemprelease temprlease; // we don't touch the db directly + + string dbname = cc().database()->name; + string shardedOutputCollection = cmdObj["shardedOutputCollection"].valuestrsafe(); + + MRSetup mr( dbname , cmdObj.firstElement().embeddedObjectUserCheck() , false ); + + set<ServerAndQuery> servers; + + BSONObjBuilder shardCounts; + map<string,long long> counts; + + BSONObj shards = cmdObj["shards"].embeddedObjectUserCheck(); + vector< auto_ptr<DBClientCursor> > shardCursors; + BSONObjIterator i( shards ); + while ( i.more() ){ + BSONElement e = i.next(); + string shard = e.fieldName(); + + BSONObj res = e.embeddedObjectUserCheck(); + + uassert( 10078 , "something bad happened" , shardedOutputCollection == res["result"].valuestrsafe() ); + servers.insert( shard ); + shardCounts.appendAs( res["counts"] , shard.c_str() ); + + BSONObjIterator j( res["counts"].embeddedObjectUserCheck() ); + while ( j.more() ){ + BSONElement temp = j.next(); + counts[temp.fieldName()] += temp.numberLong(); + } + + } + + BSONObj sortKey = BSON( "_id" << 1 ); + + ParallelSortClusteredCursor cursor( servers , dbname + "." + shardedOutputCollection , + Query().sort( sortKey ) ); + + + auto_ptr<Scope> s = globalScriptEngine->getPooledScope( ns ); + ScriptingFunction reduceFunction = s->createFunction( mr.reduceCode.c_str() ); + ScriptingFunction finalizeFunction = 0; + if ( mr.finalizeCode.size() ) + finalizeFunction = s->createFunction( mr.finalizeCode.c_str() ); + + list<BSONObj> values; + + result.append( "result" , mr.finalShort ); + + DBDirectClient db; + + while ( cursor.more() ){ + BSONObj t = cursor.next(); + + if ( values.size() == 0 ){ + values.push_back( t ); + continue; + } + + if ( t.woSortOrder( *(values.begin()) , sortKey ) == 0 ){ + values.push_back( t ); + continue; + } + + + db.insert( mr.tempLong , reduceValues( values , s.get() , reduceFunction , 1 , finalizeFunction ) ); + values.clear(); + values.push_back( t ); + } + + if ( values.size() ) + db.insert( mr.tempLong , reduceValues( values , s.get() , reduceFunction , 1 , finalizeFunction ) ); + + long long finalCount = mr.renameIfNeeded( db ); + log(0) << " mapreducefinishcommand " << mr.finalLong << " " << finalCount << endl; + + for ( set<ServerAndQuery>::iterator i=servers.begin(); i!=servers.end(); i++ ){ + ScopedDbConnection conn( i->_server ); + conn->dropCollection( dbname + "." + shardedOutputCollection ); + } + + result.append( "shardCounts" , shardCounts.obj() ); + + { + BSONObjBuilder c; + for ( map<string,long long>::iterator i=counts.begin(); i!=counts.end(); i++ ){ + c.append( i->first , i->second ); + } + result.append( "counts" , c.obj() ); + } + + return 1; + } + } mapReduceFinishCommand; + + } + +} + diff --git a/db/namespace.cpp b/db/namespace.cpp new file mode 100644 index 0000000..ecd5f64 --- /dev/null +++ b/db/namespace.cpp @@ -0,0 +1,753 @@ +// namespace.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "pdfile.h" +#include "db.h" +#include "../util/mmap.h" +#include "../util/hashtab.h" +#include "../scripting/engine.h" +#include "btree.h" +#include <algorithm> +#include <list> +#include "query.h" +#include "queryutil.h" +#include "json.h" + +namespace mongo { + + BSONObj idKeyPattern = fromjson("{\"_id\":1}"); + + /* deleted lists -- linked lists of deleted records -- are placed in 'buckets' of various sizes + so you can look for a deleterecord about the right size. + */ + int bucketSizes[] = { + 32, 64, 128, 256, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, + 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, + 0x400000, 0x800000 + }; + + bool NamespaceIndex::exists() const { + return !boost::filesystem::exists(path()); + } + + boost::filesystem::path NamespaceIndex::path() const { + return boost::filesystem::path( dir_ ) / ( database_ + ".ns" ); + } + + int lenForNewNsFiles = 16 * 1024 * 1024; + + void NamespaceIndex::init() { + if ( ht ) + return; + /* if someone manually deleted the datafiles for a database, + we need to be sure to clear any cached info for the database in + local.*. + */ + /* + if ( "local" != database_ ) { + DBInfo i(database_.c_str()); + i.dbDropped(); + } + */ + int len = -1; + boost::filesystem::path nsPath = path(); + string pathString = nsPath.string(); + void *p; + if( boost::filesystem::exists(nsPath) ) { + p = f.map(pathString.c_str()); + if( p ) { + len = f.length(); + if ( len % (1024*1024) != 0 ){ + log() << "bad .ns file: " << pathString << endl; + uassert( 10079 , "bad .ns file length, cannot open database", len % (1024*1024) == 0 ); + } + } + } + else { + // use lenForNewNsFiles, we are making a new database + massert( 10343 , "bad lenForNewNsFiles", lenForNewNsFiles >= 1024*1024 ); + long l = lenForNewNsFiles; + p = f.map(pathString.c_str(), l); + if( p ) { + len = (int) l; + assert( len == lenForNewNsFiles ); + } + } + + if ( p == 0 ) { + problem() << "couldn't open file " << pathString << " terminating" << endl; + dbexit( EXIT_FS ); + } + ht = new HashTable<Namespace,NamespaceDetails>(p, len, "namespace index"); + } + + void NamespaceDetails::addDeletedRec(DeletedRecord *d, DiskLoc dloc) { + { + // defensive code: try to make us notice if we reference a deleted record + (unsigned&) (((Record *) d)->data) = 0xeeeeeeee; + } + dassert( dloc.drec() == d ); + DEBUGGING out() << "TEMP: add deleted rec " << dloc.toString() << ' ' << hex << d->extentOfs << endl; + if ( capped ) { + if ( !deletedList[ 1 ].isValid() ) { + // Initial extent allocation. Insert at end. + d->nextDeleted = DiskLoc(); + if ( deletedList[ 0 ].isNull() ) + deletedList[ 0 ] = dloc; + else { + DiskLoc i = deletedList[ 0 ]; + for (; !i.drec()->nextDeleted.isNull(); i = i.drec()->nextDeleted ); + i.drec()->nextDeleted = dloc; + } + } else { + d->nextDeleted = firstDeletedInCapExtent(); + firstDeletedInCapExtent() = dloc; + } + } else { + int b = bucket(d->lengthWithHeaders); + DiskLoc& list = deletedList[b]; + DiskLoc oldHead = list; + list = dloc; + d->nextDeleted = oldHead; + } + } + + /* + lenToAlloc is WITH header + */ + DiskLoc NamespaceDetails::alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc) { + lenToAlloc = (lenToAlloc + 3) & 0xfffffffc; + DiskLoc loc = _alloc(ns, lenToAlloc); + if ( loc.isNull() ) + return loc; + + DeletedRecord *r = loc.drec(); + + /* note we want to grab from the front so our next pointers on disk tend + to go in a forward direction which is important for performance. */ + int regionlen = r->lengthWithHeaders; + extentLoc.set(loc.a(), r->extentOfs); + assert( r->extentOfs < loc.getOfs() ); + + DEBUGGING out() << "TEMP: alloc() returns " << loc.toString() << ' ' << ns << " lentoalloc:" << lenToAlloc << " ext:" << extentLoc.toString() << endl; + + int left = regionlen - lenToAlloc; + if ( capped == 0 ) { + if ( left < 24 || left < (lenToAlloc >> 3) ) { + // you get the whole thing. + return loc; + } + } + + /* split off some for further use. */ + r->lengthWithHeaders = lenToAlloc; + DiskLoc newDelLoc = loc; + newDelLoc.inc(lenToAlloc); + DeletedRecord *newDel = newDelLoc.drec(); + newDel->extentOfs = r->extentOfs; + newDel->lengthWithHeaders = left; + newDel->nextDeleted.Null(); + + addDeletedRec(newDel, newDelLoc); + + return loc; + } + + /* for non-capped collections. + returned item is out of the deleted list upon return + */ + DiskLoc NamespaceDetails::__stdAlloc(int len) { + DiskLoc *prev; + DiskLoc *bestprev = 0; + DiskLoc bestmatch; + int bestmatchlen = 0x7fffffff; + int b = bucket(len); + DiskLoc cur = deletedList[b]; + prev = &deletedList[b]; + int extra = 5; // look for a better fit, a little. + int chain = 0; + while ( 1 ) { + { + int a = cur.a(); + if ( a < -1 || a >= 100000 ) { + problem() << "~~ Assertion - cur out of range in _alloc() " << cur.toString() << + " a:" << a << " b:" << b << " chain:" << chain << '\n'; + sayDbContext(); + if ( cur == *prev ) + prev->Null(); + cur.Null(); + } + } + if ( cur.isNull() ) { + // move to next bucket. if we were doing "extra", just break + if ( bestmatchlen < 0x7fffffff ) + break; + b++; + if ( b > MaxBucket ) { + // out of space. alloc a new extent. + return DiskLoc(); + } + cur = deletedList[b]; + prev = &deletedList[b]; + continue; + } + DeletedRecord *r = cur.drec(); + if ( r->lengthWithHeaders >= len && + r->lengthWithHeaders < bestmatchlen ) { + bestmatchlen = r->lengthWithHeaders; + bestmatch = cur; + bestprev = prev; + } + if ( bestmatchlen < 0x7fffffff && --extra <= 0 ) + break; + if ( ++chain > 30 && b < MaxBucket ) { + // too slow, force move to next bucket to grab a big chunk + //b++; + chain = 0; + cur.Null(); + } + else { + /*this defensive check only made sense for the mmap storage engine: + if ( r->nextDeleted.getOfs() == 0 ) { + problem() << "~~ Assertion - bad nextDeleted " << r->nextDeleted.toString() << + " b:" << b << " chain:" << chain << ", fixing.\n"; + r->nextDeleted.Null(); + }*/ + cur = r->nextDeleted; + prev = &r->nextDeleted; + } + } + + /* unlink ourself from the deleted list */ + { + DeletedRecord *bmr = bestmatch.drec(); + *bestprev = bmr->nextDeleted; + bmr->nextDeleted.setInvalid(); // defensive. + assert(bmr->extentOfs < bestmatch.getOfs()); + } + + return bestmatch; + } + + void NamespaceDetails::dumpDeleted(set<DiskLoc> *extents) { + for ( int i = 0; i < Buckets; i++ ) { + DiskLoc dl = deletedList[i]; + while ( !dl.isNull() ) { + DeletedRecord *r = dl.drec(); + DiskLoc extLoc(dl.a(), r->extentOfs); + if ( extents == 0 || extents->count(extLoc) <= 0 ) { + out() << " bucket " << i << endl; + out() << " " << dl.toString() << " ext:" << extLoc.toString(); + if ( extents && extents->count(extLoc) <= 0 ) + out() << '?'; + out() << " len:" << r->lengthWithHeaders << endl; + } + dl = r->nextDeleted; + } + } + } + + /* combine adjacent deleted records + + this is O(n^2) but we call it for capped tables where typically n==1 or 2! + (or 3...there will be a little unused sliver at the end of the extent.) + */ + void NamespaceDetails::compact() { + assert(capped); + + list<DiskLoc> drecs; + + // Pull out capExtent's DRs from deletedList + DiskLoc i = firstDeletedInCapExtent(); + for (; !i.isNull() && inCapExtent( i ); i = i.drec()->nextDeleted ) + drecs.push_back( i ); + firstDeletedInCapExtent() = i; + + // This is the O(n^2) part. + drecs.sort(); + + list<DiskLoc>::iterator j = drecs.begin(); + assert( j != drecs.end() ); + DiskLoc a = *j; + while ( 1 ) { + j++; + if ( j == drecs.end() ) { + DEBUGGING out() << "TEMP: compact adddelrec\n"; + addDeletedRec(a.drec(), a); + break; + } + DiskLoc b = *j; + while ( a.a() == b.a() && a.getOfs() + a.drec()->lengthWithHeaders == b.getOfs() ) { + // a & b are adjacent. merge. + a.drec()->lengthWithHeaders += b.drec()->lengthWithHeaders; + j++; + if ( j == drecs.end() ) { + DEBUGGING out() << "temp: compact adddelrec2\n"; + addDeletedRec(a.drec(), a); + return; + } + b = *j; + } + DEBUGGING out() << "temp: compact adddelrec3\n"; + addDeletedRec(a.drec(), a); + a = b; + } + } + + DiskLoc NamespaceDetails::firstRecord( const DiskLoc &startExtent ) const { + for (DiskLoc i = startExtent.isNull() ? firstExtent : startExtent; + !i.isNull(); i = i.ext()->xnext ) { + if ( !i.ext()->firstRecord.isNull() ) + return i.ext()->firstRecord; + } + return DiskLoc(); + } + + DiskLoc NamespaceDetails::lastRecord( const DiskLoc &startExtent ) const { + for (DiskLoc i = startExtent.isNull() ? lastExtent : startExtent; + !i.isNull(); i = i.ext()->xprev ) { + if ( !i.ext()->lastRecord.isNull() ) + return i.ext()->lastRecord; + } + return DiskLoc(); + } + + DiskLoc &NamespaceDetails::firstDeletedInCapExtent() { + if ( deletedList[ 1 ].isNull() ) + return deletedList[ 0 ]; + else + return deletedList[ 1 ].drec()->nextDeleted; + } + + bool NamespaceDetails::inCapExtent( const DiskLoc &dl ) const { + assert( !dl.isNull() ); + // We could have a rec or drec, doesn't matter. + return dl.drec()->myExtent( dl ) == capExtent.ext(); + } + + bool NamespaceDetails::nextIsInCapExtent( const DiskLoc &dl ) const { + assert( !dl.isNull() ); + DiskLoc next = dl.drec()->nextDeleted; + if ( next.isNull() ) + return false; + return inCapExtent( next ); + } + + void NamespaceDetails::advanceCapExtent( const char *ns ) { + // We want deletedList[ 1 ] to be the last DeletedRecord of the prev cap extent + // (or DiskLoc() if new capExtent == firstExtent) + if ( capExtent == lastExtent ) + deletedList[ 1 ] = DiskLoc(); + else { + DiskLoc i = firstDeletedInCapExtent(); + for (; !i.isNull() && nextIsInCapExtent( i ); i = i.drec()->nextDeleted ); + deletedList[ 1 ] = i; + } + + capExtent = theCapExtent()->xnext.isNull() ? firstExtent : theCapExtent()->xnext; + + /* this isn't true if a collection has been renamed...that is ok just used for diagnostics */ + //dassert( theCapExtent()->ns == ns ); + + theCapExtent()->assertOk(); + capFirstNewRecord = DiskLoc(); + } + + int n_complaints_cap = 0; + void NamespaceDetails::maybeComplain( const char *ns, int len ) const { + if ( ++n_complaints_cap < 8 ) { + out() << "couldn't make room for new record (len: " << len << ") in capped ns " << ns << '\n'; + int i = 0; + for ( DiskLoc e = firstExtent; !e.isNull(); e = e.ext()->xnext, ++i ) { + out() << " Extent " << i; + if ( e == capExtent ) + out() << " (capExtent)"; + out() << '\n'; + out() << " magic: " << hex << e.ext()->magic << dec << " extent->ns: " << e.ext()->nsDiagnostic.buf << '\n'; + out() << " fr: " << e.ext()->firstRecord.toString() << + " lr: " << e.ext()->lastRecord.toString() << " extent->len: " << e.ext()->length << '\n'; + } + assert( len * 5 > lastExtentSize ); // assume it is unusually large record; if not, something is broken + } + } + + DiskLoc NamespaceDetails::__capAlloc( int len ) { + DiskLoc prev = deletedList[ 1 ]; + DiskLoc i = firstDeletedInCapExtent(); + DiskLoc ret; + for (; !i.isNull() && inCapExtent( i ); prev = i, i = i.drec()->nextDeleted ) { + // We need to keep at least one DR per extent in deletedList[ 0 ], + // so make sure there's space to create a DR at the end. + if ( i.drec()->lengthWithHeaders >= len + 24 ) { + ret = i; + break; + } + } + + /* unlink ourself from the deleted list */ + if ( !ret.isNull() ) { + if ( prev.isNull() ) + deletedList[ 0 ] = ret.drec()->nextDeleted; + else + prev.drec()->nextDeleted = ret.drec()->nextDeleted; + ret.drec()->nextDeleted.setInvalid(); // defensive. + assert( ret.drec()->extentOfs < ret.getOfs() ); + } + + return ret; + } + + void NamespaceDetails::checkMigrate() { + // migrate old NamespaceDetails format + if ( capped && capExtent.a() == 0 && capExtent.getOfs() == 0 ) { + capFirstNewRecord = DiskLoc(); + capFirstNewRecord.setInvalid(); + // put all the DeletedRecords in deletedList[ 0 ] + for ( int i = 1; i < Buckets; ++i ) { + DiskLoc first = deletedList[ i ]; + if ( first.isNull() ) + continue; + DiskLoc last = first; + for (; !last.drec()->nextDeleted.isNull(); last = last.drec()->nextDeleted ); + last.drec()->nextDeleted = deletedList[ 0 ]; + deletedList[ 0 ] = first; + deletedList[ i ] = DiskLoc(); + } + // NOTE deletedList[ 1 ] set to DiskLoc() in above + + // Last, in case we're killed before getting here + capExtent = firstExtent; + } + } + + /* alloc with capped table handling. */ + DiskLoc NamespaceDetails::_alloc(const char *ns, int len) { + if ( !capped ) + return __stdAlloc(len); + + // capped. + + // signal done allocating new extents. + if ( !deletedList[ 1 ].isValid() ) + deletedList[ 1 ] = DiskLoc(); + + assert( len < 400000000 ); + int passes = 0; + DiskLoc loc; + + // delete records until we have room and the max # objects limit achieved. + + /* this fails on a rename -- that is ok but must keep commented out */ + //assert( theCapExtent()->ns == ns ); + + theCapExtent()->assertOk(); + DiskLoc firstEmptyExtent; + while ( 1 ) { + if ( nrecords < max ) { + loc = __capAlloc( len ); + if ( !loc.isNull() ) + break; + } + + // If on first iteration through extents, don't delete anything. + if ( !capFirstNewRecord.isValid() ) { + advanceCapExtent( ns ); + if ( capExtent != firstExtent ) + capFirstNewRecord.setInvalid(); + // else signal done with first iteration through extents. + continue; + } + + if ( !capFirstNewRecord.isNull() && + theCapExtent()->firstRecord == capFirstNewRecord ) { + // We've deleted all records that were allocated on the previous + // iteration through this extent. + advanceCapExtent( ns ); + continue; + } + + if ( theCapExtent()->firstRecord.isNull() ) { + if ( firstEmptyExtent.isNull() ) + firstEmptyExtent = capExtent; + advanceCapExtent( ns ); + if ( firstEmptyExtent == capExtent ) { + maybeComplain( ns, len ); + return DiskLoc(); + } + continue; + } + + massert( 10344 , "Capped collection full and delete not allowed", cappedMayDelete() ); + DiskLoc fr = theCapExtent()->firstRecord; + theDataFileMgr.deleteRecord(ns, fr.rec(), fr, true); + compact(); + if( ++passes >= 5000 ) { + log() << "passes ns:" << ns << " len:" << len << '\n'; + log() << "passes max:" << max << " nrecords:" << nrecords << " datasize: " << datasize << endl; + massert( 10345 , "passes >= 5000 in capped collection alloc", false ); + } + } + + // Remember first record allocated on this iteration through capExtent. + if ( capFirstNewRecord.isValid() && capFirstNewRecord.isNull() ) + capFirstNewRecord = loc; + + return loc; + } + + /* you MUST call when adding an index. see pdfile.cpp */ + IndexDetails& NamespaceDetails::addIndex(const char *thisns) { + assert( nsdetails(thisns) == this ); + + if( nIndexes == NIndexesBase && extraOffset == 0 ) { + nsindex(thisns)->allocExtra(thisns); + } + + IndexDetails& id = idx(nIndexes); + nIndexes++; + NamespaceDetailsTransient::get_w(thisns).addedIndex(); + return id; + } + + // must be called when renaming a NS to fix up extra + void NamespaceDetails::copyingFrom(const char *thisns, NamespaceDetails *src) { + if( extraOffset ) { + extraOffset = 0; // so allocExtra() doesn't assert. + Extra *e = nsindex(thisns)->allocExtra(thisns); + memcpy(e, src->extra(), sizeof(Extra)); + } + } + + /* returns index of the first index in which the field is present. -1 if not present. + (aug08 - this method not currently used) + */ + int NamespaceDetails::fieldIsIndexed(const char *fieldName) { + massert( 10346 , "not implemented", false); + /* + for ( int i = 0; i < nIndexes; i++ ) { + IndexDetails& idx = indexes[i]; + BSONObj idxKey = idx.info.obj().getObjectField("key"); // e.g., { ts : -1 } + if ( !idxKey.findElement(fieldName).eoo() ) + return i; + }*/ + return -1; + } + + long long NamespaceDetails::storageSize(){ + Extent * e = firstExtent.ext(); + assert( e ); + + long long total = 0; + while ( e ){ + total += e->length; + e = e->getNextExtent(); + } + return total; + } + + /* ------------------------------------------------------------------------- */ + + boost::mutex NamespaceDetailsTransient::_qcMutex; + map< string, shared_ptr< NamespaceDetailsTransient > > NamespaceDetailsTransient::_map; + typedef map< string, shared_ptr< NamespaceDetailsTransient > >::iterator ouriter; + + void NamespaceDetailsTransient::reset() { + clearQueryCache(); + _keysComputed = false; + _indexSpecs.clear(); + } + +/* NamespaceDetailsTransient& NamespaceDetailsTransient::get(const char *ns) { + shared_ptr< NamespaceDetailsTransient > &t = map_[ ns ]; + if ( t.get() == 0 ) + t.reset( new NamespaceDetailsTransient(ns) ); + return *t; + } +*/ + void NamespaceDetailsTransient::clearForPrefix(const char *prefix) { + assertInWriteLock(); + vector< string > found; + for( ouriter i = _map.begin(); i != _map.end(); ++i ) + if ( strncmp( i->first.c_str(), prefix, strlen( prefix ) ) == 0 ) + found.push_back( i->first ); + for( vector< string >::iterator i = found.begin(); i != found.end(); ++i ) { + _map[ *i ].reset(); + } + } + + void NamespaceDetailsTransient::computeIndexKeys() { + _keysComputed = true; + _indexKeys.clear(); + NamespaceDetails *d = nsdetails(_ns.c_str()); + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) + i.next().keyPattern().getFieldNames(_indexKeys); + } + + void NamespaceDetailsTransient::cllStart( int logSizeMb ) { + assertInWriteLock(); + _cll_ns = "local.temp.oplog." + _ns; + _cll_enabled = true; + stringstream spec; + // 128MB + spec << "{size:" << logSizeMb * 1024 * 1024 << ",capped:true,autoIndexId:false}"; + setClient( _cll_ns.c_str() ); + string err; + massert( 10347 , "Could not create log ns", userCreateNS( _cll_ns.c_str(), fromjson( spec.str() ), err, false ) ); + NamespaceDetails *d = nsdetails( _cll_ns.c_str() ); + d->cappedDisallowDelete(); + } + + void NamespaceDetailsTransient::cllInvalidate() { + assertInWriteLock(); + cllDrop(); + _cll_enabled = false; + } + + bool NamespaceDetailsTransient::cllValidateComplete() { + assertInWriteLock(); + cllDrop(); + bool ret = _cll_enabled; + _cll_enabled = false; + _cll_ns = ""; + return ret; + } + + void NamespaceDetailsTransient::cllDrop() { + assertInWriteLock(); + if ( !_cll_enabled ) + return; + setClient( _cll_ns.c_str() ); + dropNS( _cll_ns ); + } + + /* ------------------------------------------------------------------------- */ + + /* add a new namespace to the system catalog (<dbname>.system.namespaces). + options: { capped : ..., size : ... } + */ + void addNewNamespaceToCatalog(const char *ns, const BSONObj *options = 0) { + log(1) << "New namespace: " << ns << '\n'; + if ( strstr(ns, "system.namespaces") ) { + // system.namespaces holds all the others, so it is not explicitly listed in the catalog. + // TODO: fix above should not be strstr! + return; + } + + { + BSONObjBuilder b; + b.append("name", ns); + if ( options ) + b.append("options", *options); + BSONObj j = b.done(); + char database[256]; + nsToDatabase(ns, database); + string s = database; + s += ".system.namespaces"; + theDataFileMgr.insert(s.c_str(), j.objdata(), j.objsize(), true); + } + } + + void renameNamespace( const char *from, const char *to ) { + NamespaceIndex *ni = nsindex( from ); + assert( ni && ni->details( from ) && !ni->details( to ) ); + + // Our namespace and index details will move to a different + // memory location. The only references to namespace and + // index details across commands are in cursors and nsd + // transient (including query cache) so clear these. + ClientCursor::invalidate( from ); + NamespaceDetailsTransient::clearForPrefix( from ); + + NamespaceDetails *details = ni->details( from ); + ni->add_ns( to, *details ); + NamespaceDetails *todetails = ni->details( to ); + try { + todetails->copyingFrom(to, details); // fixes extraOffset + } + catch( DBException& ) { + // could end up here if .ns is full - if so try to clean up / roll back a little + ni->kill_ns(to); + throw; + } + ni->kill_ns( from ); + details = todetails; + + BSONObj oldSpec; + char database[MaxDatabaseLen]; + nsToDatabase(from, database); + string s = database; + s += ".system.namespaces"; + assert( Helpers::findOne( s.c_str(), BSON( "name" << from ), oldSpec ) ); + + BSONObjBuilder newSpecB; + BSONObjIterator i( oldSpec.getObjectField( "options" ) ); + while( i.more() ) { + BSONElement e = i.next(); + if ( strcmp( e.fieldName(), "create" ) != 0 ) + newSpecB.append( e ); + else + newSpecB << "create" << to; + } + BSONObj newSpec = newSpecB.done(); + addNewNamespaceToCatalog( to, newSpec.isEmpty() ? 0 : &newSpec ); + + deleteObjects( s.c_str(), BSON( "name" << from ), false, false, true ); + // oldSpec variable no longer valid memory + + BSONObj oldIndexSpec; + s = database; + s += ".system.indexes"; + while( Helpers::findOne( s.c_str(), BSON( "ns" << from ), oldIndexSpec ) ) { + BSONObjBuilder newIndexSpecB; + BSONObjIterator i( oldIndexSpec ); + while( i.more() ) { + BSONElement e = i.next(); + if ( strcmp( e.fieldName(), "ns" ) != 0 ) + newIndexSpecB.append( e ); + else + newIndexSpecB << "ns" << to; + } + BSONObj newIndexSpec = newIndexSpecB.done(); + DiskLoc newIndexSpecLoc = theDataFileMgr.insert( s.c_str(), newIndexSpec.objdata(), newIndexSpec.objsize(), true, BSONElement(), false ); + int indexI = details->findIndexByName( oldIndexSpec.getStringField( "name" ) ); + IndexDetails &indexDetails = details->idx(indexI); + string oldIndexNs = indexDetails.indexNamespace(); + indexDetails.info = newIndexSpecLoc; + string newIndexNs = indexDetails.indexNamespace(); + + BtreeBucket::renameIndexNamespace( oldIndexNs.c_str(), newIndexNs.c_str() ); + deleteObjects( s.c_str(), oldIndexSpec.getOwned(), true, false, true ); + } + } + + bool legalClientSystemNS( const string& ns , bool write ){ + if ( ns.find( ".system.users" ) != string::npos ) + return true; + + if ( ns.find( ".system.js" ) != string::npos ){ + if ( write ) + Scope::storedFuncMod(); + return true; + } + + return false; + } + +} // namespace mongo diff --git a/db/namespace.h b/db/namespace.h new file mode 100644 index 0000000..df4c62f --- /dev/null +++ b/db/namespace.h @@ -0,0 +1,653 @@ +// namespace.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "jsobj.h" +#include "queryutil.h" +#include "storage.h" +#include "../util/hashtab.h" +#include "../util/mmap.h" + +namespace mongo { + + class Cursor; + +#pragma pack(1) + + /* in the mongo source code, "client" means "database". */ + + const int MaxDatabaseLen = 256; // max str len for the db name, including null char + + // "database.a.b.c" -> "database" + inline void nsToDatabase(const char *ns, char *database) { + const char *p = ns; + char *q = database; + while ( *p != '.' ) { + if ( *p == 0 ) + break; + *q++ = *p++; + } + *q = 0; + if (q-database>=MaxDatabaseLen) { + log() << "nsToDatabase: ns too long. terminating, buf overrun condition" << endl; + dbexit( EXIT_POSSIBLE_CORRUPTION ); + } + } + inline string nsToDatabase(const char *ns) { + char buf[MaxDatabaseLen]; + nsToDatabase(ns, buf); + return buf; + } + + /* e.g. + NamespaceString ns("acme.orders"); + cout << ns.coll; // "orders" + */ + class NamespaceString { + public: + string db; + string coll; // note collection names can have periods in them for organizing purposes (e.g. "system.indexes") + private: + void init(const char *ns) { + const char *p = strchr(ns, '.'); + if( p == 0 ) return; + db = string(ns, p - ns); + coll = p + 1; + } + public: + NamespaceString( const char * ns ) { init(ns); } + NamespaceString( const string& ns ) { init(ns.c_str()); } + + bool isSystem() { + return strncmp(coll.c_str(), "system.", 7) == 0; + } + }; + + /* This helper class is used to make the HashMap below in NamespaceDetails */ + class Namespace { + public: + enum MaxNsLenValue { MaxNsLen = 128 }; + Namespace(const char *ns) { + *this = ns; + } + Namespace& operator=(const char *ns) { + uassert( 10080 , "ns name too long, max size is 128", strlen(ns) < MaxNsLen); + //memset(buf, 0, MaxNsLen); /* this is just to keep stuff clean in the files for easy dumping and reading */ + strcpy_s(buf, MaxNsLen, ns); + return *this; + } + + /* for more than 10 indexes -- see NamespaceDetails::Extra */ + string extraName() { + string s = string(buf) + "$extra"; + massert( 10348 , "ns name too long", s.size() < MaxNsLen); + return s; + } + + void kill() { + buf[0] = 0x7f; + } + + bool operator==(const char *r) { + return strcmp(buf, r) == 0; + } + bool operator==(const Namespace& r) { + return strcmp(buf, r.buf) == 0; + } + int hash() const { + unsigned x = 0; + const char *p = buf; + while ( *p ) { + x = x * 131 + *p; + p++; + } + return (x & 0x7fffffff) | 0x8000000; // must be > 0 + } + + /** + ( foo.bar ).getSisterNS( "blah" ) == foo.blah + perhaps this should move to the NamespaceString helper? + */ + string getSisterNS( const char * local ) { + assert( local && local[0] != '.' ); + string old(buf); + if ( old.find( "." ) != string::npos ) + old = old.substr( 0 , old.find( "." ) ); + return old + "." + local; + } + + operator string() const { + return (string)buf; + } + + char buf[MaxNsLen]; + }; + +} + +#include "index.h" + +namespace mongo { + + /** + @return true if a client can modify this namespace + things like *.system.users + */ + bool legalClientSystemNS( const string& ns , bool write ); + + + /* deleted lists -- linked lists of deleted records -- are placed in 'buckets' of various sizes + so you can look for a deleterecord about the right size. + */ + const int Buckets = 19; + const int MaxBucket = 18; + + extern int bucketSizes[]; + + /* this is the "header" for a collection that has all its details. in the .ns file. + */ + class NamespaceDetails { + friend class NamespaceIndex; + enum { NIndexesExtra = 30, + NIndexesBase = 10 + }; + struct Extra { + // note we could use this field for more chaining later, so don't waste it: + unsigned long long reserved1; + IndexDetails details[NIndexesExtra]; + unsigned reserved2; + unsigned reserved3; + }; + Extra* extra() { + assert( extraOffset ); + return (Extra *) (((char *) this) + extraOffset); + } + public: + void copyingFrom(const char *thisns, NamespaceDetails *src); // must be called when renaming a NS to fix up extra + + enum { NIndexesMax = 40 }; + + BOOST_STATIC_ASSERT( NIndexesMax == NIndexesBase + NIndexesExtra ); + + NamespaceDetails( const DiskLoc &loc, bool _capped ) { + /* be sure to initialize new fields here -- doesn't default to zeroes the way we use it */ + firstExtent = lastExtent = capExtent = loc; + datasize = nrecords = 0; + lastExtentSize = 0; + nIndexes = 0; + capped = _capped; + max = 0x7fffffff; + paddingFactor = 1.0; + flags = 0; + capFirstNewRecord = DiskLoc(); + // Signal that we are on first allocation iteration through extents. + capFirstNewRecord.setInvalid(); + // For capped case, signal that we are doing initial extent allocation. + if ( capped ) + deletedList[ 1 ].setInvalid(); + assert( sizeof(dataFileVersion) == 2 ); + dataFileVersion = 0; + indexFileVersion = 0; + multiKeyIndexBits = 0; + reservedA = 0; + extraOffset = 0; + backgroundIndexBuildInProgress = 0; + memset(reserved, 0, sizeof(reserved)); + } + DiskLoc firstExtent; + DiskLoc lastExtent; + + /* NOTE: capped collections override the meaning of deleted list. + deletedList[0] points to a list of free records (DeletedRecord's) for all extents in + the namespace. + deletedList[1] points to the last record in the prev extent. When the "current extent" + changes, this value is updated. !deletedList[1].isValid() when this value is not + yet computed. + */ + DiskLoc deletedList[Buckets]; + + long long datasize; + long long nrecords; + int lastExtentSize; + int nIndexes; + private: + IndexDetails _indexes[NIndexesBase]; + public: + int capped; + int max; // max # of objects for a capped table. + double paddingFactor; // 1.0 = no padding. + int flags; + DiskLoc capExtent; + DiskLoc capFirstNewRecord; + + /* NamespaceDetails version. So we can do backward compatibility in the future. + See filever.h + */ + unsigned short dataFileVersion; + unsigned short indexFileVersion; + + unsigned long long multiKeyIndexBits; + private: + unsigned long long reservedA; + long long extraOffset; // where the $extra info is located (bytes relative to this) + public: + int backgroundIndexBuildInProgress; // 1 if in prog + char reserved[76]; + + /* NOTE: be careful with flags. are we manipulating them in read locks? if so, + this isn't thread safe. TODO + */ + enum NamespaceFlags { + Flag_HaveIdIndex = 1 << 0, // set when we have _id index (ONLY if ensureIdIndex was called -- 0 if that has never been called) + Flag_CappedDisallowDelete = 1 << 1 // set when deletes not allowed during capped table allocation. + }; + + IndexDetails& idx(int idxNo) { + if( idxNo < NIndexesBase ) + return _indexes[idxNo]; + return extra()->details[idxNo-NIndexesBase]; + } + + class IndexIterator { + friend class NamespaceDetails; + int i; + int n; + NamespaceDetails *d; + Extra *e; + IndexIterator(NamespaceDetails *_d) { + d = _d; + i = 0; + n = d->nIndexes; + if( n > NIndexesBase ) + e = d->extra(); + } + public: + int pos() { return i; } // note this is the next one to come + bool more() { return i < n; } + IndexDetails& next() { + int k = i; + i++; + return k < NIndexesBase ? d->_indexes[k] : + e->details[k-10]; + } + }; + + IndexIterator ii() { + return IndexIterator(this); + } + + /* hackish - find our index # in the indexes array + */ + int idxNo(IndexDetails& idx) { + IndexIterator i = ii(); + while( i.more() ) { + if( &i.next() == &idx ) + return i.pos()-1; + } + massert( 10349 , "E12000 idxNo fails", false); + return -1; + } + + /* multikey indexes are indexes where there are more than one key in the index + for a single document. see multikey in wiki. + for these, we have to do some dedup work on queries. + */ + bool isMultikey(int i) { + return (multiKeyIndexBits & (((unsigned long long) 1) << i)) != 0; + } + void setIndexIsMultikey(int i) { + dassert( i < NIndexesMax ); + multiKeyIndexBits |= (((unsigned long long) 1) << i); + } + void clearIndexIsMultikey(int i) { + dassert( i < NIndexesMax ); + multiKeyIndexBits &= ~(((unsigned long long) 1) << i); + } + + /* add a new index. does not add to system.indexes etc. - just to NamespaceDetails. + caller must populate returned object. + */ + IndexDetails& addIndex(const char *thisns); + + void aboutToDeleteAnIndex() { + flags &= ~Flag_HaveIdIndex; + } + + void cappedDisallowDelete() { + flags |= Flag_CappedDisallowDelete; + } + + /* returns index of the first index in which the field is present. -1 if not present. */ + int fieldIsIndexed(const char *fieldName); + + void paddingFits() { + double x = paddingFactor - 0.01; + if ( x >= 1.0 ) + paddingFactor = x; + } + void paddingTooSmall() { + double x = paddingFactor + 0.6; + if ( x <= 2.0 ) + paddingFactor = x; + } + + //returns offset in indexes[] + int findIndexByName(const char *name) { + IndexIterator i = ii(); + while( i.more() ) { + if ( strcmp(i.next().info.obj().getStringField("name"),name) == 0 ) + return i.pos()-1; + } + return -1; + } + + //returns offset in indexes[] + int findIndexByKeyPattern(const BSONObj& keyPattern) { + IndexIterator i = ii(); + while( i.more() ) { + if( i.next().keyPattern() == keyPattern ) + return i.pos()-1; + } + return -1; + } + + /* @return -1 = not found + generally id is first index, so not that expensive an operation (assuming present). + */ + int findIdIndex() { + IndexIterator i = ii(); + while( i.more() ) { + if( i.next().isIdIndex() ) + return i.pos()-1; + } + return -1; + } + + /* return which "deleted bucket" for this size object */ + static int bucket(int n) { + for ( int i = 0; i < Buckets; i++ ) + if ( bucketSizes[i] > n ) + return i; + return Buckets-1; + } + + /* allocate a new record. lenToAlloc includes headers. */ + DiskLoc alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc); + + /* add a given record to the deleted chains for this NS */ + void addDeletedRec(DeletedRecord *d, DiskLoc dloc); + + void dumpDeleted(set<DiskLoc> *extents = 0); + + bool capLooped() const { + return capped && capFirstNewRecord.isValid(); + } + + // Start from firstExtent by default. + DiskLoc firstRecord( const DiskLoc &startExtent = DiskLoc() ) const; + + // Start from lastExtent by default. + DiskLoc lastRecord( const DiskLoc &startExtent = DiskLoc() ) const; + + bool inCapExtent( const DiskLoc &dl ) const; + + void checkMigrate(); + + long long storageSize(); + + private: + bool cappedMayDelete() const { + return !( flags & Flag_CappedDisallowDelete ); + } + Extent *theCapExtent() const { + return capExtent.ext(); + } + void advanceCapExtent( const char *ns ); + void maybeComplain( const char *ns, int len ) const; + DiskLoc __stdAlloc(int len); + DiskLoc __capAlloc(int len); + DiskLoc _alloc(const char *ns, int len); + void compact(); // combine adjacent deleted records + + DiskLoc &firstDeletedInCapExtent(); + bool nextIsInCapExtent( const DiskLoc &dl ) const; + }; + +#pragma pack() + + /* these are things we know / compute about a namespace that are transient -- things + we don't actually store in the .ns file. so mainly caching of frequently used + information. + + CAUTION: Are you maintaining this properly on a collection drop()? A dropdatabase()? Be careful. + The current field "allIndexKeys" may have too many keys in it on such an occurrence; + as currently used that does not cause anything terrible to happen. + + todo: cleanup code, need abstractions and separation + */ + class NamespaceDetailsTransient : boost::noncopyable { + /* general ------------------------------------------------------------- */ + private: + string _ns; + void reset(); + static std::map< string, shared_ptr< NamespaceDetailsTransient > > _map; + public: + NamespaceDetailsTransient(const char *ns) : _ns(ns), _keysComputed(false), _qcWriteCount(), _cll_enabled() { } + /* _get() is not threadsafe */ + static NamespaceDetailsTransient& _get(const char *ns); + /* use get_w() when doing write operations */ + static NamespaceDetailsTransient& get_w(const char *ns) { + DEV assertInWriteLock(); + return _get(ns); + } + void addedIndex() { reset(); } + void deletedIndex() { reset(); } + /* Drop cached information on all namespaces beginning with the specified prefix. + Can be useful as index namespaces share the same start as the regular collection. + SLOW - sequential scan of all NamespaceDetailsTransient objects */ + static void clearForPrefix(const char *prefix); + + /* indexKeys() cache ---------------------------------------------------- */ + /* assumed to be in write lock for this */ + private: + bool _keysComputed; + set<string> _indexKeys; + void computeIndexKeys(); + public: + /* get set of index keys for this namespace. handy to quickly check if a given + field is indexed (Note it might be a secondary component of a compound index.) + */ + set<string>& indexKeys() { + DEV assertInWriteLock(); + if ( !_keysComputed ) + computeIndexKeys(); + return _indexKeys; + } + + /* IndexSpec caching */ + private: + map<const IndexDetails*,IndexSpec> _indexSpecs; + public: + const IndexSpec& getIndexSpec( const IndexDetails * details ){ + DEV assertInWriteLock(); + IndexSpec& spec = _indexSpecs[details]; + if ( spec.meta.isEmpty() ){ + spec.reset( details->info ); + } + return spec; + } + + /* query cache (for query optimizer) ------------------------------------- */ + private: + int _qcWriteCount; + map< QueryPattern, pair< BSONObj, long long > > _qcCache; + public: + static boost::mutex _qcMutex; + /* you must be in the qcMutex when calling this (and using the returned val): */ + static NamespaceDetailsTransient& get_inlock(const char *ns) { + return _get(ns); + } + void clearQueryCache() { // public for unit tests + _qcCache.clear(); + _qcWriteCount = 0; + } + /* you must notify the cache if you are doing writes, as query plan optimality will change */ + void notifyOfWriteOp() { + if ( _qcCache.empty() ) + return; + if ( ++_qcWriteCount >= 100 ) + clearQueryCache(); + } + BSONObj indexForPattern( const QueryPattern &pattern ) { + return _qcCache[ pattern ].first; + } + long long nScannedForPattern( const QueryPattern &pattern ) { + return _qcCache[ pattern ].second; + } + void registerIndexForPattern( const QueryPattern &pattern, const BSONObj &indexKey, long long nScanned ) { + _qcCache[ pattern ] = make_pair( indexKey, nScanned ); + } + + /* for collection-level logging -- see CmdLogCollection ----------------- */ + /* assumed to be in write lock for this */ + private: + string _cll_ns; // "local.temp.oplog." + _ns; + bool _cll_enabled; + void cllDrop(); // drop _cll_ns + public: + string cllNS() const { return _cll_ns; } + bool cllEnabled() const { return _cll_enabled; } + void cllStart( int logSizeMb = 256 ); // begin collection level logging + void cllInvalidate(); + bool cllValidateComplete(); + + }; /* NamespaceDetailsTransient */ + + inline NamespaceDetailsTransient& NamespaceDetailsTransient::_get(const char *ns) { + shared_ptr< NamespaceDetailsTransient > &t = _map[ ns ]; + if ( t.get() == 0 ) + t.reset( new NamespaceDetailsTransient(ns) ); + return *t; + } + + /* NamespaceIndex is the ".ns" file you see in the data directory. It is the "system catalog" + if you will: at least the core parts. (Additional info in system.* collections.) + */ + class NamespaceIndex { + friend class NamespaceCursor; + BOOST_STATIC_ASSERT( sizeof(NamespaceDetails::Extra) <= sizeof(NamespaceDetails) ); + public: + NamespaceIndex(const string &dir, const string &database) : + ht( 0 ), + dir_( dir ), + database_( database ) {} + + /* returns true if new db will be created if we init lazily */ + bool exists() const; + + void init(); + + void add_ns(const char *ns, DiskLoc& loc, bool capped) { + NamespaceDetails details( loc, capped ); + add_ns( ns, details ); + } + + void add_ns( const char *ns, const NamespaceDetails &details ) { + init(); + Namespace n(ns); + uassert( 10081 , "too many namespaces/collections", ht->put(n, details)); + } + + /* just for diagnostics */ + size_t detailsOffset(NamespaceDetails *d) { + if ( !ht ) + return -1; + return ((char *) d) - (char *) ht->nodes; + } + + /* extra space for indexes when more than 10 */ + NamespaceDetails::Extra* allocExtra(const char *ns) { + Namespace n(ns); + Namespace extra(n.extraName().c_str()); // throws userexception if ns name too long + NamespaceDetails *d = details(ns); + massert( 10350 , "allocExtra: base ns missing?", d ); + assert( d->extraOffset == 0 ); + massert( 10351 , "allocExtra: extra already exists", ht->get(extra) == 0 ); + NamespaceDetails::Extra temp; + memset(&temp, 0, sizeof(temp)); + uassert( 10082 , "allocExtra: too many namespaces/collections", ht->put(extra, (NamespaceDetails&) temp)); + NamespaceDetails::Extra *e = (NamespaceDetails::Extra *) ht->get(extra); + d->extraOffset = ((char *) e) - ((char *) d); + assert( d->extra() == e ); + return e; + } + + NamespaceDetails* details(const char *ns) { + if ( !ht ) + return 0; + Namespace n(ns); + NamespaceDetails *d = ht->get(n); + if ( d ) + d->checkMigrate(); + return d; + } + + void kill_ns(const char *ns) { + if ( !ht ) + return; + Namespace n(ns); + ht->kill(n); + + try { + Namespace extra(n.extraName().c_str()); + ht->kill(extra); + } + catch(DBException&) { } + } + + bool find(const char *ns, DiskLoc& loc) { + NamespaceDetails *l = details(ns); + if ( l ) { + loc = l->firstExtent; + return true; + } + return false; + } + + bool allocated() const { + return ht != 0; + } + + private: + boost::filesystem::path path() const; + + MemoryMappedFile f; + HashTable<Namespace,NamespaceDetails> *ht; + string dir_; + string database_; + }; + + extern string dbpath; // --dbpath parm + + // Rename a namespace within current 'client' db. + // (Arguments should include db name) + void renameNamespace( const char *from, const char *to ); + +} // namespace mongo diff --git a/db/nonce.cpp b/db/nonce.cpp new file mode 100644 index 0000000..4c677be --- /dev/null +++ b/db/nonce.cpp @@ -0,0 +1,74 @@ +// nonce.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "nonce.h" + +extern int do_md5_test(void); + +namespace mongo { + + Security::Security() { + static int n; + massert( 10352 , "Security is a singleton class", ++n == 1); + init(); + } + + void Security::init(){ + if( _initialized ) return; + _initialized = true; + +#if defined(__linux__) + _devrandom = new ifstream("/dev/urandom", ios::binary|ios::in); + massert( 10353 , "can't open dev/urandom", _devrandom->is_open() ); +#elif defined(_WIN32) + srand(curTimeMicros()); +#else + srandomdev(); +#endif + assert( sizeof(nonce) == 8 ); + +#ifndef NDEBUG + if ( do_md5_test() ) + massert( 10354 , "md5 unit test fails", false); +#endif + } + + nonce Security::getNonce(){ + static boost::mutex m; + boostlock lk(m); + + /* question/todo: /dev/random works on OS X. is it better + to use that than random() / srandom()? + */ + + nonce n; +#if defined(__linux__) + _devrandom->read((char*)&n, sizeof(n)); + massert( 10355 , "devrandom failed", !_devrandom->fail()); +#elif defined(_WIN32) + n = (((unsigned long long)rand())<<32) | rand(); +#else + n = (((unsigned long long)random())<<32) | random(); +#endif + return n; + } + + bool Security::_initialized; + Security security; + +} // namespace mongo diff --git a/db/nonce.h b/db/nonce.h new file mode 100644 index 0000000..593931f --- /dev/null +++ b/db/nonce.h @@ -0,0 +1,42 @@ +// nonce.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + + typedef unsigned long long nonce; + + struct Security { + Security(); + + nonce getNonce(); + + /** safe during global var initialization */ + nonce getNonceInitSafe() { + init(); + return getNonce(); + } + private: + ifstream *_devrandom; + static bool _initialized; + void init(); // can call more than once + }; + + extern Security security; + +} // namespace mongo diff --git a/db/pcre.txt b/db/pcre.txt new file mode 100644 index 0000000..3e21047 --- /dev/null +++ b/db/pcre.txt @@ -0,0 +1,15 @@ + + +You need to install pcre. + +This could be scripted: + +cd /tmp +curl -O ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-7.4.tar.gz +tar -xzf pcre-7.4.tar.gz +./configure --enable-utf8 --with-match-limit=200000 --with-match-limit-recursion=4000 +make +make install + + +At that point is will be installed in /usr/*. the version in p/pcre-7.4 is for VC++. diff --git a/db/pdfile.cpp b/db/pdfile.cpp new file mode 100644 index 0000000..18df5f1 --- /dev/null +++ b/db/pdfile.cpp @@ -0,0 +1,1649 @@ +// pdfile.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +todo: +_ table scans must be sequential, not next/prev pointers +_ coalesce deleted + +_ disallow system* manipulations from the database. +*/ + +#include "stdafx.h" +#include "pdfile.h" +#include "db.h" +#include "../util/mmap.h" +#include "../util/hashtab.h" +#include "../util/file_allocator.h" +#include "btree.h" +#include <algorithm> +#include <list> +#include "query.h" +#include "repl.h" +#include "dbhelpers.h" +#include "namespace.h" +#include "queryutil.h" +#include "extsort.h" +#include "curop.h" + +namespace mongo { + + string dbpath = "/data/db/"; + + DataFileMgr theDataFileMgr; + DatabaseHolder dbHolder; + int MAGIC = 0x1000; +// int curOp = -2; + + extern int otherTraceLevel; + void addNewNamespaceToCatalog(const char *ns, const BSONObj *options = 0); + void ensureIdIndexForNewNs(const char *ns) { + if ( !strstr( ns, ".system." ) && !strstr( ns, ".$freelist" ) ) { + log( 1 ) << "adding _id index for new collection" << endl; + ensureHaveIdIndex( ns ); + } + } + + string getDbContext() { + stringstream ss; + Client * c = currentClient.get(); + if ( c ){ + Database *database = c->database(); + if ( database ) { + ss << database->name << ' '; + ss << cc().ns() << ' '; + } + } + return ss.str(); + } + + BSONObj::BSONObj(const Record *r) { + init(r->data, false); + } + + /*---------------------------------------------------------------------*/ + + int initialExtentSize(int len) { + long long sz = len * 16; + if ( len < 1000 ) sz = len * 64; + if ( sz > 1000000000 ) + sz = 1000000000; + int z = ((int)sz) & 0xffffff00; + assert( z > len ); + DEV log() << "initialExtentSize(" << len << ") returns " << z << endl; + return z; + } + + bool _userCreateNS(const char *ns, const BSONObj& j, string& err) { + if ( nsdetails(ns) ) { + err = "collection already exists"; + return false; + } + + log(1) << "create collection " << ns << ' ' << j << '\n'; + + /* todo: do this only when we have allocated space successfully? or we could insert with a { ok: 0 } field + and then go back and set to ok : 1 after we are done. + */ + bool isFreeList = strstr(ns, ".$freelist") != 0; + if( !isFreeList ) + addNewNamespaceToCatalog(ns, j.isEmpty() ? 0 : &j); + + long long size = initialExtentSize(128); + BSONElement e = j.findElement("size"); + if ( e.isNumber() ) { + size = (long long) e.number(); + size += 256; + size &= 0xffffffffffffff00LL; + } + + uassert( 10083 , "invalid size spec", size > 0 ); + + bool newCapped = false; + int mx = 0; + e = j.findElement("capped"); + if ( e.type() == Bool && e.boolean() ) { + newCapped = true; + e = j.findElement("max"); + if ( e.isNumber() ) { + mx = (int) e.number(); + } + } + + // $nExtents just for debug/testing. We create '$nExtents' extents, + // each of size 'size'. + e = j.findElement( "$nExtents" ); + int nExtents = int( e.number() ); + Database *database = cc().database(); + if ( nExtents > 0 ) { + assert( size <= 0x7fffffff ); + for ( int i = 0; i < nExtents; ++i ) { + assert( size <= 0x7fffffff ); + // $nExtents is just for testing - always allocate new extents + // rather than reuse existing extents so we have some predictibility + // in the extent size used by our tests + database->suitableFile( (int) size )->createExtent( ns, (int) size, newCapped ); + } + } else { + while ( size > 0 ) { + int max = MongoDataFile::maxSize() - MDFHeader::headerSize(); + int desiredExtentSize = (int) (size > max ? max : size); + Extent *e = database->allocExtent( ns, desiredExtentSize, newCapped ); + size -= e->length; + } + if ( !newCapped ) { + // check if it's time to preallocate a new file, and if so queue that job for a bg thread + // safe to call this multiple times - the implementation will only preallocate one file + database->preallocateAFile(); + } + } + + NamespaceDetails *d = nsdetails(ns); + assert(d); + + if ( j.getField( "autoIndexId" ).type() ) { + if ( j["autoIndexId"].trueValue() ){ + ensureIdIndexForNewNs( ns ); + } + } else { + if ( !newCapped ) { + ensureIdIndexForNewNs( ns ); + } + } + + if ( mx > 0 ) + d->max = mx; + + return true; + } + + // { ..., capped: true, size: ..., max: ... } + // returns true if successful + bool userCreateNS(const char *ns, BSONObj j, string& err, bool logForReplication) { + const char *coll = strchr( ns, '.' ) + 1; + massert( 10356 , "invalid ns", coll && *coll ); + char cl[ 256 ]; + nsToDatabase( ns, cl ); + bool ok = _userCreateNS(ns, j, err); + if ( logForReplication && ok ) { + if ( j.getField( "create" ).eoo() ) { + BSONObjBuilder b; + b << "create" << coll; + b.appendElements( j ); + j = b.obj(); + } + string logNs = string( cl ) + ".$cmd"; + logOp("c", logNs.c_str(), j); + } + return ok; + } + + /*---------------------------------------------------------------------*/ + + int MongoDataFile::maxSize() { + if ( sizeof( int* ) == 4 ) + return 512 * 1024 * 1024; + else + return 0x7ff00000; + } + + int MongoDataFile::defaultSize( const char *filename ) const { + int size; + + if ( fileNo <= 4 ) + size = (64*1024*1024) << fileNo; + else + size = 0x7ff00000; + + if ( strstr(filename, "_hudsonSmall") ) { + int mult = 1; + if ( fileNo > 1 && fileNo < 1000 ) + mult = fileNo; + size = 1024 * 512 * mult; + log() << "Warning : using small files for _hudsonSmall" << endl; + } + else if ( cmdLine.smallfiles ){ + size = size >> 2; + } + + + return size; + } + + void MongoDataFile::open( const char *filename, int minSize, bool preallocateOnly ) { + { + /* check quotas + very simple temporary implementation - we will in future look up + the quota from the grid database + */ + if ( cmdLine.quota && fileNo > cmdLine.quotaFiles && !boost::filesystem::exists(filename) ) { + /* todo: if we were adding / changing keys in an index did we do some + work previously that needs cleaning up? Possible. We should + check code like that and have it catch the exception and do + something reasonable. + */ + string s = "db disk space quota exceeded "; + Database *database = cc().database(); + if ( database ) + s += database->name; + uasserted(12501,s); + } + } + + long size = defaultSize( filename ); + while ( size < minSize ) { + if ( size < maxSize() / 2 ) + size *= 2; + else { + size = maxSize(); + break; + } + } + if ( size > maxSize() ) + size = maxSize(); + + assert( ( size >= 64*1024*1024 ) || cmdLine.smallfiles || ( strstr( filename, "_hudsonSmall" ) ) ); + assert( size % 4096 == 0 ); + + if ( preallocateOnly ) { + if ( cmdLine.prealloc ) { + theFileAllocator().requestAllocation( filename, size ); + } + return; + } + + header = (MDFHeader *) mmf.map(filename, size); + if( sizeof(char *) == 4 ) + uassert( 10084 , "can't map file memory - mongo requires 64 bit build for larger datasets", header); + else + uassert( 10085 , "can't map file memory", header); + header->init(fileNo, size); + } + + void addNewExtentToNamespace(const char *ns, Extent *e, DiskLoc eloc, DiskLoc emptyLoc, bool capped) { + DiskLoc oldExtentLoc; + NamespaceIndex *ni = nsindex(ns); + NamespaceDetails *details = ni->details(ns); + if ( details ) { + assert( !details->lastExtent.isNull() ); + assert( !details->firstExtent.isNull() ); + e->xprev = details->lastExtent; + details->lastExtent.ext()->xnext = eloc; + assert( !eloc.isNull() ); + details->lastExtent = eloc; + } + else { + ni->add_ns(ns, eloc, capped); + details = ni->details(ns); + } + + details->lastExtentSize = e->length; + DEBUGGING out() << "temp: newextent adddelrec " << ns << endl; + details->addDeletedRec(emptyLoc.drec(), emptyLoc); + } + + Extent* MongoDataFile::createExtent(const char *ns, int approxSize, bool newCapped, int loops) { + massert( 10357 , "shutdown in progress", !goingAway ); + massert( 10358 , "bad new extent size", approxSize >= 0 && approxSize <= 0x7ff00000 ); + massert( 10359 , "header==0 on new extent: 32 bit mmap space exceeded?", header ); // null if file open failed + int ExtentSize = approxSize <= header->unusedLength ? approxSize : header->unusedLength; + DiskLoc loc; + if ( ExtentSize <= 0 ) { + /* not there could be a lot of looping here is db just started and + no files are open yet. we might want to do something about that. */ + if ( loops > 8 ) { + assert( loops < 10000 ); + out() << "warning: loops=" << loops << " fileno:" << fileNo << ' ' << ns << '\n'; + } + log() << "newExtent: " << ns << " file " << fileNo << " full, adding a new file\n"; + return cc().database()->addAFile( 0, true )->createExtent(ns, approxSize, newCapped, loops+1); + } + int offset = header->unused.getOfs(); + header->unused.setOfs( fileNo, offset + ExtentSize ); + header->unusedLength -= ExtentSize; + loc.setOfs(fileNo, offset); + Extent *e = _getExtent(loc); + DiskLoc emptyLoc = e->init(ns, ExtentSize, fileNo, offset); + + addNewExtentToNamespace(ns, e, loc, emptyLoc, newCapped); + + DEV log() << "new extent " << ns << " size: 0x" << hex << ExtentSize << " loc: 0x" << hex << offset + << " emptyLoc:" << hex << emptyLoc.getOfs() << dec << endl; + return e; + } + + Extent* DataFileMgr::allocFromFreeList(const char *ns, int approxSize, bool capped) { + string s = cc().database()->name + ".$freelist"; + NamespaceDetails *f = nsdetails(s.c_str()); + if( f ) { + int low, high; + if( capped ) { + // be strict about the size + low = approxSize; + if( low > 2048 ) low -= 256; + high = (int) (approxSize * 1.05) + 256; + } + else { + low = (int) (approxSize * 0.8); + high = (int) (approxSize * 1.4); + } + if( high < 0 ) high = approxSize; + int n = 0; + Extent *best = 0; + int bestDiff = 0x7fffffff; + { + DiskLoc L = f->firstExtent; + while( !L.isNull() ) { + Extent * e = L.ext(); + if( e->length >= low && e->length <= high ) { + int diff = abs(e->length - approxSize); + if( diff < bestDiff ) { + bestDiff = diff; + best = e; + if( diff == 0 ) + break; + } + } + L = e->xnext; + ++n; + + } + } + OCCASIONALLY if( n > 512 ) log() << "warning: newExtent " << n << " scanned\n"; + if( best ) { + Extent *e = best; + // remove from the free list + if( !e->xprev.isNull() ) + e->xprev.ext()->xnext = e->xnext; + if( !e->xnext.isNull() ) + e->xnext.ext()->xprev = e->xprev; + if( f->firstExtent == e->myLoc ) + f->firstExtent = e->xnext; + if( f->lastExtent == e->myLoc ) + f->lastExtent = e->xprev; + + // use it + OCCASIONALLY if( n > 512 ) log() << "warning: newExtent " << n << " scanned\n"; + DiskLoc emptyLoc = e->reuse(ns); + addNewExtentToNamespace(ns, e, e->myLoc, emptyLoc, capped); + return e; + } + } + + return 0; + // return createExtent(ns, approxSize, capped); + } + + /*---------------------------------------------------------------------*/ + + DiskLoc Extent::reuse(const char *nsname) { + log(3) << "reset extent was:" << nsDiagnostic.buf << " now:" << nsname << '\n'; + massert( 10360 , "Extent::reset bad magic value", magic == 0x41424344 ); + xnext.Null(); + xprev.Null(); + nsDiagnostic = nsname; + firstRecord.Null(); + lastRecord.Null(); + + DiskLoc emptyLoc = myLoc; + emptyLoc.inc( (extentData-(char*)this) ); + + int delRecLength = length - (extentData - (char *) this); + DeletedRecord *empty1 = (DeletedRecord *) extentData; + DeletedRecord *empty = (DeletedRecord *) getRecord(emptyLoc); + assert( empty == empty1 ); + memset(empty, delRecLength, 1); + + empty->lengthWithHeaders = delRecLength; + empty->extentOfs = myLoc.getOfs(); + empty->nextDeleted.Null(); + + return emptyLoc; + } + + /* assumes already zeroed -- insufficient for block 'reuse' perhaps */ + DiskLoc Extent::init(const char *nsname, int _length, int _fileNo, int _offset) { + magic = 0x41424344; + myLoc.setOfs(_fileNo, _offset); + xnext.Null(); + xprev.Null(); + nsDiagnostic = nsname; + length = _length; + firstRecord.Null(); + lastRecord.Null(); + + DiskLoc emptyLoc = myLoc; + emptyLoc.inc( (extentData-(char*)this) ); + + DeletedRecord *empty1 = (DeletedRecord *) extentData; + DeletedRecord *empty = (DeletedRecord *) getRecord(emptyLoc); + assert( empty == empty1 ); + empty->lengthWithHeaders = _length - (extentData - (char *) this); + empty->extentOfs = myLoc.getOfs(); + return emptyLoc; + } + + /* + Record* Extent::newRecord(int len) { + if( firstEmptyRegion.isNull() ) + return 0; + + assert(len > 0); + int newRecSize = len + Record::HeaderSize; + DiskLoc newRecordLoc = firstEmptyRegion; + Record *r = getRecord(newRecordLoc); + int left = r->netLength() - len; + if( left < 0 ) { + // + firstEmptyRegion.Null(); + return 0; + } + + DiskLoc nextEmpty = r->next.getNextEmpty(firstEmptyRegion); + r->lengthWithHeaders = newRecSize; + r->next.markAsFirstOrLastInExtent(this); // we're now last in the extent + if( !lastRecord.isNull() ) { + assert(getRecord(lastRecord)->next.lastInExtent()); // it was the last one + getRecord(lastRecord)->next.set(newRecordLoc); // until now + r->prev.set(lastRecord); + } + else { + r->prev.markAsFirstOrLastInExtent(this); // we are the first in the extent + assert( firstRecord.isNull() ); + firstRecord = newRecordLoc; + } + lastRecord = newRecordLoc; + + if( left < Record::HeaderSize + 32 ) { + firstEmptyRegion.Null(); + } + else { + firstEmptyRegion.inc(newRecSize); + Record *empty = getRecord(firstEmptyRegion); + empty->next.set(nextEmpty); // not for empty records, unless in-use records, next and prev can be null. + empty->prev.Null(); + empty->lengthWithHeaders = left; + } + + return r; + } + */ + + /*---------------------------------------------------------------------*/ + + auto_ptr<Cursor> DataFileMgr::findAll(const char *ns, const DiskLoc &startLoc) { + DiskLoc loc; + bool found = nsindex(ns)->find(ns, loc); + if ( !found ) { + // out() << "info: findAll() namespace does not exist: " << ns << endl; + return auto_ptr<Cursor>(new BasicCursor(DiskLoc())); + } + + Extent *e = getExtent(loc); + + DEBUGGING { + out() << "listing extents for " << ns << endl; + DiskLoc tmp = loc; + set<DiskLoc> extents; + + while ( 1 ) { + Extent *f = getExtent(tmp); + out() << "extent: " << tmp.toString() << endl; + extents.insert(tmp); + tmp = f->xnext; + if ( tmp.isNull() ) + break; + f = f->getNextExtent(); + } + + out() << endl; + nsdetails(ns)->dumpDeleted(&extents); + } + + if ( !nsdetails( ns )->capped ) { + if ( !startLoc.isNull() ) + return auto_ptr<Cursor>(new BasicCursor( startLoc )); + while ( e->firstRecord.isNull() && !e->xnext.isNull() ) { + /* todo: if extent is empty, free it for reuse elsewhere. + that is a bit complicated have to clean up the freelists. + */ + RARELY out() << "info DFM::findAll(): extent " << loc.toString() << " was empty, skipping ahead " << ns << endl; + // find a nonempty extent + // it might be nice to free the whole extent here! but have to clean up free recs then. + e = e->getNextExtent(); + } + return auto_ptr<Cursor>(new BasicCursor( e->firstRecord )); + } else { + return auto_ptr< Cursor >( new ForwardCappedCursor( nsdetails( ns ), startLoc ) ); + } + } + + /* get a table scan cursor, but can be forward or reverse direction. + order.$natural - if set, > 0 means forward (asc), < 0 backward (desc). + */ + auto_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order, const DiskLoc &startLoc) { + BSONElement el = order.findElement("$natural"); // e.g., { $natural : -1 } + + if ( el.number() >= 0 ) + return DataFileMgr::findAll(ns, startLoc); + + // "reverse natural order" + NamespaceDetails *d = nsdetails(ns); + if ( !d ) + return auto_ptr<Cursor>(new BasicCursor(DiskLoc())); + if ( !d->capped ) { + if ( !startLoc.isNull() ) + return auto_ptr<Cursor>(new ReverseCursor( startLoc )); + Extent *e = d->lastExtent.ext(); + while ( e->lastRecord.isNull() && !e->xprev.isNull() ) { + OCCASIONALLY out() << " findTableScan: extent empty, skipping ahead" << endl; + e = e->getPrevExtent(); + } + return auto_ptr<Cursor>(new ReverseCursor( e->lastRecord )); + } else { + return auto_ptr< Cursor >( new ReverseCappedCursor( d, startLoc ) ); + } + } + + void printFreeList() { + string s = cc().database()->name + ".$freelist"; + log() << "dump freelist " << s << '\n'; + NamespaceDetails *freeExtents = nsdetails(s.c_str()); + if( freeExtents == 0 ) { + log() << " freeExtents==0" << endl; + return; + } + DiskLoc a = freeExtents->firstExtent; + while( !a.isNull() ) { + Extent *e = a.ext(); + log() << " " << a.toString() << " len:" << e->length << " prev:" << e->xprev.toString() << '\n'; + a = e->xnext; + } + + log() << " end freelist" << endl; + } + + /* drop a collection/namespace */ + void dropNS(const string& nsToDrop) { + NamespaceDetails* d = nsdetails(nsToDrop.c_str()); + uassert( 10086 , (string)"ns not found: " + nsToDrop , d ); + + NamespaceString s(nsToDrop); + assert( s.db == cc().database()->name ); + if( s.isSystem() ) { + if( s.coll == "system.profile" ) + uassert( 10087 , "turn off profiling before dropping system.profile collection", cc().database()->profile == 0 ); + else + uasserted( 12502, "can't drop system ns" ); + } + + { + // remove from the system catalog + BSONObj cond = BSON( "name" << nsToDrop ); // { name: "colltodropname" } + string system_namespaces = cc().database()->name + ".system.namespaces"; + /*int n = */ deleteObjects(system_namespaces.c_str(), cond, false, false, true); + // no check of return code as this ns won't exist for some of the new storage engines + } + + // free extents + if( !d->firstExtent.isNull() ) { + string s = cc().database()->name + ".$freelist"; + NamespaceDetails *freeExtents = nsdetails(s.c_str()); + if( freeExtents == 0 ) { + string err; + _userCreateNS(s.c_str(), BSONObj(), err); + freeExtents = nsdetails(s.c_str()); + massert( 10361 , "can't create .$freelist", freeExtents); + } + if( freeExtents->firstExtent.isNull() ) { + freeExtents->firstExtent = d->firstExtent; + freeExtents->lastExtent = d->lastExtent; + } + else { + DiskLoc a = freeExtents->firstExtent; + assert( a.ext()->xprev.isNull() ); + a.ext()->xprev = d->lastExtent; + d->lastExtent.ext()->xnext = a; + freeExtents->firstExtent = d->firstExtent; + + d->firstExtent.setInvalid(); + d->lastExtent.setInvalid(); + } + } + + // remove from the catalog hashtable + cc().database()->namespaceIndex.kill_ns(nsToDrop.c_str()); + } + + void dropCollection( const string &name, string &errmsg, BSONObjBuilder &result ) { + log(1) << "dropCollection: " << name << endl; + NamespaceDetails *d = nsdetails(name.c_str()); + assert( d ); + if ( d->nIndexes != 0 ) { + try { + assert( deleteIndexes(d, name.c_str(), "*", errmsg, result, true) ); + } + catch( DBException& ) { + uasserted(12503,"drop: deleteIndexes for collection failed - consider trying repair"); + } + assert( d->nIndexes == 0 ); + } + log(1) << "\t deleteIndexes done" << endl; + result.append("ns", name.c_str()); + ClientCursor::invalidate(name.c_str()); + dropNS(name); + } + + int nUnindexes = 0; + + void _unindexRecord(IndexDetails& id, BSONObj& obj, const DiskLoc& dl, bool logMissing = true) { + BSONObjSetDefaultOrder keys; + id.getKeysFromObject(obj, keys); + for ( BSONObjSetDefaultOrder::iterator i=keys.begin(); i != keys.end(); i++ ) { + BSONObj j = *i; + // out() << "UNINDEX: j:" << j.toString() << " head:" << id.head.toString() << dl.toString() << endl; + if ( otherTraceLevel >= 5 ) { + out() << "_unindexRecord() " << obj.toString(); + out() << "\n unindex:" << j.toString() << endl; + } + nUnindexes++; + bool ok = false; + try { + ok = id.head.btree()->unindex(id.head, id, j, dl); + } + catch (AssertionException&) { + problem() << "Assertion failure: _unindex failed " << id.indexNamespace() << endl; + out() << "Assertion failure: _unindex failed" << '\n'; + out() << " obj:" << obj.toString() << '\n'; + out() << " key:" << j.toString() << '\n'; + out() << " dl:" << dl.toString() << endl; + sayDbContext(); + } + + if ( !ok && logMissing ) { + out() << "unindex failed (key too big?) " << id.indexNamespace() << '\n'; + } + } + } + + /* unindex all keys in all indexes for this record. */ + void unindexRecord(NamespaceDetails *d, Record *todelete, const DiskLoc& dl, bool noWarn = false) { + if ( d->nIndexes == 0 ) return; + BSONObj obj(todelete); + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + _unindexRecord(i.next(), obj, dl, !noWarn); + } + } + + /* deletes a record, just the pdfile portion -- no index cleanup, no cursor cleanup, etc. + caller must check if capped + */ + void DataFileMgr::_deleteRecord(NamespaceDetails *d, const char *ns, Record *todelete, const DiskLoc& dl) + { + /* remove ourself from the record next/prev chain */ + { + if ( todelete->prevOfs != DiskLoc::NullOfs ) + todelete->getPrev(dl).rec()->nextOfs = todelete->nextOfs; + if ( todelete->nextOfs != DiskLoc::NullOfs ) + todelete->getNext(dl).rec()->prevOfs = todelete->prevOfs; + } + + /* remove ourself from extent pointers */ + { + Extent *e = todelete->myExtent(dl); + if ( e->firstRecord == dl ) { + if ( todelete->nextOfs == DiskLoc::NullOfs ) + e->firstRecord.Null(); + else + e->firstRecord.setOfs(dl.a(), todelete->nextOfs); + } + if ( e->lastRecord == dl ) { + if ( todelete->prevOfs == DiskLoc::NullOfs ) + e->lastRecord.Null(); + else + e->lastRecord.setOfs(dl.a(), todelete->prevOfs); + } + } + + /* add to the free list */ + { + d->nrecords--; + d->datasize -= todelete->netLength(); + /* temp: if in system.indexes, don't reuse, and zero out: we want to be + careful until validated more, as IndexDetails has pointers + to this disk location. so an incorrectly done remove would cause + a lot of problems. + */ + if ( strstr(ns, ".system.indexes") ) { + memset(todelete, 0, todelete->lengthWithHeaders); + } + else { + DEV memset(todelete->data, 0, todelete->netLength()); // attempt to notice invalid reuse. + d->addDeletedRec((DeletedRecord*)todelete, dl); + } + } + } + + void DataFileMgr::deleteRecord(const char *ns, Record *todelete, const DiskLoc& dl, bool cappedOK, bool noWarn) + { + dassert( todelete == dl.rec() ); + + NamespaceDetails* d = nsdetails(ns); + if ( d->capped && !cappedOK ) { + out() << "failing remove on a capped ns " << ns << endl; + uassert( 10089 , "can't remove from a capped collection" , 0 ); + return; + } + + /* check if any cursors point to us. if so, advance them. */ + ClientCursor::aboutToDelete(dl); + + unindexRecord(d, todelete, dl, noWarn); + + _deleteRecord(d, ns, todelete, dl); + NamespaceDetailsTransient::get_w( ns ).notifyOfWriteOp(); + } + + + /** Note: if the object shrinks a lot, we don't free up space, we leave extra at end of the record. + */ + const DiskLoc DataFileMgr::update(const char *ns, + Record *toupdate, const DiskLoc& dl, + const char *_buf, int _len, OpDebug& debug) + { + StringBuilder& ss = debug.str; + dassert( toupdate == dl.rec() ); + + NamespaceDetails *d = nsdetails(ns); + + BSONObj objOld(toupdate); + BSONObj objNew(_buf); + assert( objNew.objsize() == _len ); + assert( objNew.objdata() == _buf ); + + if( !objNew.hasElement("_id") && objOld.hasElement("_id") ) { + /* add back the old _id value if the update removes it. Note this implementation is slow + (copies entire object multiple times), but this shouldn't happen often, so going for simple + code, not speed. + */ + BSONObjBuilder b; + BSONElement e; + assert( objOld.getObjectID(e) ); + b.append(e); // put _id first, for best performance + b.appendElements(objNew); + objNew = b.obj(); + } + + /* duplicate key check. we descend the btree twice - once for this check, and once for the actual inserts, further + below. that is suboptimal, but it's pretty complicated to do it the other way without rollbacks... + */ + vector<IndexChanges> changes; + getIndexChanges(changes, *d, objNew, objOld); + dupCheck(changes, *d); + + if ( toupdate->netLength() < objNew.objsize() ) { + // doesn't fit. reallocate ----------------------------------------------------- + uassert( 10003 , "E10003 failing update: objects in a capped ns cannot grow", !(d && d->capped)); + d->paddingTooSmall(); + if ( cc().database()->profile ) + ss << " moved "; + deleteRecord(ns, toupdate, dl); + return insert(ns, objNew.objdata(), objNew.objsize(), false); + } + + NamespaceDetailsTransient::get_w( ns ).notifyOfWriteOp(); + d->paddingFits(); + + /* have any index keys changed? */ + { + unsigned keyUpdates = 0; + for ( int x = 0; x < d->nIndexes; x++ ) { + IndexDetails& idx = d->idx(x); + for ( unsigned i = 0; i < changes[x].removed.size(); i++ ) { + try { + idx.head.btree()->unindex(idx.head, idx, *changes[x].removed[i], dl); + } + catch (AssertionException&) { + ss << " exception update unindex "; + problem() << " caught assertion update unindex " << idx.indexNamespace() << endl; + } + } + assert( !dl.isNull() ); + BSONObj idxKey = idx.info.obj().getObjectField("key"); + keyUpdates += changes[x].added.size(); + for ( unsigned i = 0; i < changes[x].added.size(); i++ ) { + try { + /* we did the dupCheck() above. so we don't have to worry about it here. */ + idx.head.btree()->bt_insert( + idx.head, + dl, *changes[x].added[i], idxKey, /*dupsAllowed*/true, idx); + } + catch (AssertionException&) { + ss << " exception update index "; + out() << " caught assertion update index " << idx.indexNamespace() << '\n'; + problem() << " caught assertion update index " << idx.indexNamespace() << endl; + } + } + } + if( keyUpdates && cc().database()->profile ) + ss << '\n' << keyUpdates << " key updates "; + } + + // update in place + memcpy(toupdate->data, objNew.objdata(), objNew.objsize()); + return dl; + } + + int followupExtentSize(int len, int lastExtentLen) { + int x = initialExtentSize(len); + int y = (int) (lastExtentLen < 4000000 ? lastExtentLen * 4.0 : lastExtentLen * 1.2); + int sz = y > x ? y : x; + sz = ((int)sz) & 0xffffff00; + assert( sz > len ); + return sz; + } + + int deb=0; + + /* add keys to indexes for a new record */ + inline void _indexRecord(NamespaceDetails *d, int idxNo, BSONObj& obj, DiskLoc newRecordLoc, bool dupsAllowed) { + IndexDetails& idx = d->idx(idxNo); + BSONObjSetDefaultOrder keys; + idx.getKeysFromObject(obj, keys); + BSONObj order = idx.keyPattern(); + int n = 0; + for ( BSONObjSetDefaultOrder::iterator i=keys.begin(); i != keys.end(); i++ ) { + if( ++n == 2 ) { + d->setIndexIsMultikey(idxNo); + } + assert( !newRecordLoc.isNull() ); + try { + idx.head.btree()->bt_insert(idx.head, newRecordLoc, + *i, order, dupsAllowed, idx); + } + catch (AssertionException& ) { + if( !dupsAllowed ) { + // dup key exception, presumably. + throw; + } + problem() << " caught assertion _indexRecord " << idx.indexNamespace() << endl; + } + } + } + + void testSorting() + { + BSONObjBuilder b; + b.appendNull(""); + BSONObj x = b.obj(); + + BSONObjExternalSorter sorter; + + sorter.add(x, DiskLoc(3,7)); + sorter.add(x, DiskLoc(4,7)); + sorter.add(x, DiskLoc(2,7)); + sorter.add(x, DiskLoc(1,7)); + sorter.add(x, DiskLoc(3,77)); + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + while( i->more() ) { + BSONObjExternalSorter::Data d = i->next(); + cout << d.second.toString() << endl; + cout << d.first.objsize() << endl; + cout<<"SORTER next:" << d.first.toString() << endl; + } + } + + // throws DBException + /* _ TODO dropDups + */ + unsigned long long fastBuildIndex(const char *ns, NamespaceDetails *d, IndexDetails& idx, int idxNo) { + // testSorting(); + Timer t; + + log() << "Buildindex " << ns << " idxNo:" << idxNo << ' ' << idx.info.obj().toString() << endl; + + bool dupsAllowed = !idx.unique(); + bool dropDups = idx.dropDups(); + BSONObj order = idx.keyPattern(); + + idx.head.Null(); + + /* get and sort all the keys ----- */ + unsigned long long n = 0; + auto_ptr<Cursor> c = theDataFileMgr.findAll(ns); + BSONObjExternalSorter sorter(order); + unsigned long long nkeys = 0; + ProgressMeter pm( d->nrecords , 10 ); + while ( c->ok() ) { + BSONObj o = c->current(); + DiskLoc loc = c->currLoc(); + + BSONObjSetDefaultOrder keys; + idx.getKeysFromObject(o, keys); + int k = 0; + for ( BSONObjSetDefaultOrder::iterator i=keys.begin(); i != keys.end(); i++ ) { + if( ++k == 2 ) + d->setIndexIsMultikey(idxNo); + //cout<<"SORTER ADD " << i->toString() << ' ' << loc.toString() << endl; + sorter.add(*i, loc); + nkeys++; + } + + c->advance(); + n++; + pm.hit(); + }; + sorter.sort(); + + log(t.seconds() > 5 ? 0 : 1) << "\t external sort used : " << sorter.numFiles() << " files " << " in " << t.seconds() << " secs" << endl; + + list<DiskLoc> dupsToDrop; + + /* build index --- */ + { + BtreeBuilder btBuilder(dupsAllowed, idx); + BSONObj keyLast; + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + ProgressMeter pm2( nkeys , 10 ); + while( i->more() ) { + RARELY killCurrentOp.checkForInterrupt(); + BSONObjExternalSorter::Data d = i->next(); + + //cout<<"TEMP SORTER next " << d.first.toString() << endl; + try { + btBuilder.addKey(d.first, d.second); + } + catch( AssertionException& ) { + if ( dupsAllowed ){ + // unknow exception?? + throw; + } + + if ( ! dropDups ) + throw; + + /* we could queue these on disk, but normally there are very few dups, so instead we + keep in ram and have a limit. + */ + dupsToDrop.push_back(d.second); + uassert( 10092 , "too may dups on index build with dropDups=true", dupsToDrop.size() < 1000000 ); + } + pm2.hit(); + } + btBuilder.commit(); + wassert( btBuilder.getn() == nkeys || dropDups ); + } + + log(1) << "\t fastBuildIndex dupsToDrop:" << dupsToDrop.size() << endl; + + for( list<DiskLoc>::iterator i = dupsToDrop.begin(); i != dupsToDrop.end(); i++ ) + theDataFileMgr.deleteRecord( ns, i->rec(), *i, false, true ); + + return n; + } + + static class BackgroundIndexBuildJobs { + + unsigned long long addExistingToIndex(const char *ns, NamespaceDetails *d, IndexDetails& idx, int idxNo) { + bool dupsAllowed = !idx.unique(); + bool dropDups = idx.dropDups(); + + unsigned long long n = 0; + auto_ptr<Cursor> c = theDataFileMgr.findAll(ns); + while ( c->ok() ) { + BSONObj js = c->current(); + try { + _indexRecord(d, idxNo, js, c->currLoc(),dupsAllowed); + c->advance(); + } catch( AssertionException& e ) { + if ( dropDups ) { + DiskLoc toDelete = c->currLoc(); + c->advance(); + theDataFileMgr.deleteRecord( ns, toDelete.rec(), toDelete, false, true ); + } else { + _log() << endl; + log(2) << "addExistingToIndex exception " << e.what() << endl; + throw; + } + } + n++; + }; + return n; + } + + /* we do set a flag in the namespace for quick checking, but this is our authoritative info - + that way on a crash/restart, we don't think we are still building one. */ + set<NamespaceDetails*> bgJobsInProgress; + + void prep(NamespaceDetails *d) { + assertInWriteLock(); + assert( bgJobsInProgress.count(d) == 0 ); + bgJobsInProgress.insert(d); + d->backgroundIndexBuildInProgress = 1; + } + + public: + /* Note you cannot even do a foreground index build if a background is in progress, + as bg build assumes it is the last index in the array! + */ + void checkInProg(NamespaceDetails *d) { + assertInWriteLock(); + uassert(12580, "already building an index for this namespace in background", bgJobsInProgress.count(d) == 0); + } + +/* todo: clean bg flag on loading of NamespaceDetails */ + + unsigned long long go(string ns, NamespaceDetails *d, IndexDetails& idx, int idxNo) { + unsigned long long n; + prep(d); + try { + idx.head = BtreeBucket::addBucket(idx); + n = addExistingToIndex(ns.c_str(), d, idx, idxNo); + } + catch(...) { + assertInWriteLock(); + bgJobsInProgress.erase(d); + d->backgroundIndexBuildInProgress = 0; + throw; + } + return n; + } + } backgroundIndex; + + // throws DBException + static void buildAnIndex(string ns, NamespaceDetails *d, IndexDetails& idx, int idxNo) { + log() << "building new index on " << idx.keyPattern() << " for " << ns << "..." << endl; + Timer t; + unsigned long long n; + + BSONObj info = idx.info.obj(); + bool background = info["background"].trueValue(); + if( background ) { + log() << "WARNING: background index build not yet implemented" << endl; + } + + if( !background ) { + n = fastBuildIndex(ns.c_str(), d, idx, idxNo); + assert( !idx.head.isNull() ); + } + else { + n = backgroundIndex.go(ns, d, idx, idxNo); + } + log() << "done for " << n << " records " << t.millis() / 1000.0 << "secs" << endl; + } + + /* add keys to indexes for a new record */ + void indexRecord(NamespaceDetails *d, const void *buf, int len, DiskLoc newRecordLoc) { + BSONObj obj((const char *)buf); + + /*UNIQUE*/ + for ( int i = 0; i < d->nIndexes; i++ ) { + try { + bool unique = d->idx(i).unique(); + _indexRecord(d, i, obj, newRecordLoc, /*dupsAllowed*/!unique); + } + catch( DBException& ) { + /* try to roll back previously added index entries + note <= i (not < i) is important here as the index we were just attempted + may be multikey and require some cleanup. + */ + for( int j = 0; j <= i; j++ ) { + try { + _unindexRecord(d->idx(j), obj, newRecordLoc, false); + } + catch(...) { + log(3) << "unindex fails on rollback after unique failure\n"; + } + } + throw; + } + } + } + + extern BSONObj id_obj; // { _id : ObjectId("000000000000000000000000") } + + void ensureHaveIdIndex(const char *ns) { + NamespaceDetails *d = nsdetails(ns); + if ( d == 0 || (d->flags & NamespaceDetails::Flag_HaveIdIndex) ) + return; + + d->flags |= NamespaceDetails::Flag_HaveIdIndex; + + { + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + if( i.next().isIdIndex() ) + return; + } + } + + string system_indexes = cc().database()->name + ".system.indexes"; + + BSONObjBuilder b; + b.append("name", "_id_"); + b.append("ns", ns); + b.append("key", id_obj); + BSONObj o = b.done(); + + /* edge case: note the insert could fail if we have hit maxindexes already */ + theDataFileMgr.insert(system_indexes.c_str(), o.objdata(), o.objsize(), true); + } + +#pragma pack(1) + struct IDToInsert_ { + char type; + char _id[4]; + OID oid; + IDToInsert_() { + type = (char) jstOID; + strcpy(_id, "_id"); + assert( sizeof(IDToInsert_) == 17 ); + } + } idToInsert_; + struct IDToInsert : public BSONElement { + IDToInsert() : BSONElement( ( char * )( &idToInsert_ ) ) {} + } idToInsert; +#pragma pack() + + void DataFileMgr::insertAndLog( const char *ns, const BSONObj &o, bool god ) { + BSONObj tmp = o; + insert( ns, tmp, god ); + logOp( "i", ns, tmp ); + } + + DiskLoc DataFileMgr::insert(const char *ns, BSONObj &o, bool god) { + DiskLoc loc = insert( ns, o.objdata(), o.objsize(), god ); + if ( !loc.isNull() ) + o = BSONObj( loc.rec() ); + return loc; + } + + bool prepareToBuildIndex(const BSONObj& io, bool god, string& sourceNS, NamespaceDetails *&sourceCollection); + + /* note: if god==true, you may pass in obuf of NULL and then populate the returned DiskLoc + after the call -- that will prevent a double buffer copy in some cases (btree.cpp). + */ + DiskLoc DataFileMgr::insert(const char *ns, const void *obuf, int len, bool god, const BSONElement &writeId, bool mayAddIndex) { + bool wouldAddIndex = false; + uassert( 10093 , "cannot insert into reserved $ collection", god || strchr(ns, '$') == 0 ); + uassert( 10094 , "invalid ns", strchr( ns , '.' ) > 0 ); + const char *sys = strstr(ns, "system."); + if ( sys ) { + uassert( 10095 , "attempt to insert in reserved database name 'system'", sys != ns); + if ( strstr(ns, ".system.") ) { + // later:check for dba-type permissions here if have that at some point separate + if ( strstr(ns, ".system.indexes" ) ) + wouldAddIndex = true; + else if ( legalClientSystemNS( ns , true ) ) + ; + else if ( !god ) { + out() << "ERROR: attempt to insert in system namespace " << ns << endl; + return DiskLoc(); + } + } + else + sys = 0; + } + + bool addIndex = wouldAddIndex && mayAddIndex; + + NamespaceDetails *d = nsdetails(ns); + if ( d == 0 ) { + addNewNamespaceToCatalog(ns); + /* todo: shouldn't be in the namespace catalog until after the allocations here work. + also if this is an addIndex, those checks should happen before this! + */ + // This creates first file in the database. + cc().database()->newestFile()->createExtent(ns, initialExtentSize(len)); + d = nsdetails(ns); + if ( !god ) + ensureIdIndexForNewNs(ns); + } + d->paddingFits(); + + NamespaceDetails *tableToIndex = 0; + + string tabletoidxns; + if ( addIndex ) { + BSONObj io((const char *) obuf); + backgroundIndex.checkInProg(d); + if( !prepareToBuildIndex(io, god, tabletoidxns, tableToIndex) ) { + return DiskLoc(); + } + } + + const BSONElement *newId = &writeId; + int addID = 0; + if( !god ) { + /* Check if we have an _id field. If we don't, we'll add it. + Note that btree buckets which we insert aren't BSONObj's, but in that case god==true. + */ + BSONObj io((const char *) obuf); + BSONElement idField = io.getField( "_id" ); + uassert( 10099 , "_id cannot be an array", idField.type() != Array ); + if( idField.eoo() && !wouldAddIndex && strstr(ns, ".local.") == 0 ) { + addID = len; + if ( writeId.eoo() ) { + // Very likely we'll add this elt, so little harm in init'ing here. + idToInsert_.oid.init(); + newId = &idToInsert; + } + len += newId->size(); + } + + BSONElementManipulator::lookForTimestamps( io ); + } + + DiskLoc extentLoc; + int lenWHdr = len + Record::HeaderSize; + lenWHdr = (int) (lenWHdr * d->paddingFactor); + if ( lenWHdr == 0 ) { + // old datafiles, backward compatible here. + assert( d->paddingFactor == 0 ); + d->paddingFactor = 1.0; + lenWHdr = len + Record::HeaderSize; + } + DiskLoc loc = d->alloc(ns, lenWHdr, extentLoc); + if ( loc.isNull() ) { + // out of space + if ( d->capped == 0 ) { // size capped doesn't grow + log(1) << "allocating new extent for " << ns << " padding:" << d->paddingFactor << " lenWHdr: " << lenWHdr << endl; + cc().database()->allocExtent(ns, followupExtentSize(lenWHdr, d->lastExtentSize), false); + loc = d->alloc(ns, lenWHdr, extentLoc); + if ( loc.isNull() ){ + log() << "WARNING: alloc() failed after allocating new extent. lenWHdr: " << lenWHdr << " last extent size:" << d->lastExtentSize << "; trying again\n"; + for ( int zzz=0; zzz<10 && lenWHdr > d->lastExtentSize; zzz++ ){ + log() << "try #" << zzz << endl; + cc().database()->allocExtent(ns, followupExtentSize(len, d->lastExtentSize), false); + loc = d->alloc(ns, lenWHdr, extentLoc); + if ( ! loc.isNull() ) + break; + } + } + } + if ( loc.isNull() ) { + log() << "out of space in datafile " << ns << " capped:" << d->capped << endl; + assert(d->capped); + return DiskLoc(); + } + } + + Record *r = loc.rec(); + assert( r->lengthWithHeaders >= lenWHdr ); + if( addID ) { + /* a little effort was made here to avoid a double copy when we add an ID */ + ((int&)*r->data) = *((int*) obuf) + newId->size(); + memcpy(r->data+4, newId->rawdata(), newId->size()); + memcpy(r->data+4+newId->size(), ((char *)obuf)+4, addID-4); + } + else { + if( obuf ) + memcpy(r->data, obuf, len); + } + Extent *e = r->myExtent(loc); + if ( e->lastRecord.isNull() ) { + e->firstRecord = e->lastRecord = loc; + r->prevOfs = r->nextOfs = DiskLoc::NullOfs; + } + else { + + Record *oldlast = e->lastRecord.rec(); + r->prevOfs = e->lastRecord.getOfs(); + r->nextOfs = DiskLoc::NullOfs; + oldlast->nextOfs = loc.getOfs(); + e->lastRecord = loc; + } + + d->nrecords++; + d->datasize += r->netLength(); + + // we don't bother clearing those stats for the god tables - also god is true when adidng a btree bucket + if ( !god ) + NamespaceDetailsTransient::get_w( ns ).notifyOfWriteOp(); + + if ( tableToIndex ) { + int idxNo = tableToIndex->nIndexes; + IndexDetails& idx = tableToIndex->addIndex(tabletoidxns.c_str()); // clear transient info caches so they refresh; increments nIndexes + idx.info = loc; + try { + buildAnIndex(tabletoidxns, tableToIndex, idx, idxNo); + } catch( DBException& ) { + // save our error msg string as an exception on deleteIndexes will overwrite our message + LastError *le = lastError.get(); + assert( le ); + string saveerrmsg = le->msg; + assert( !saveerrmsg.empty() ); + + // roll back this index + string name = idx.indexName(); + BSONObjBuilder b; + string errmsg; + bool ok = deleteIndexes(tableToIndex, tabletoidxns.c_str(), name.c_str(), errmsg, b, true); + if( !ok ) { + log() << "failed to drop index after a unique key error building it: " << errmsg << ' ' << tabletoidxns << ' ' << name << endl; + } + raiseError(12506,saveerrmsg.c_str()); + throw; + } + } + + /* add this record to our indexes */ + if ( d->nIndexes ) { + try { + indexRecord(d, r->data/*buf*/, len, loc); + } + catch( AssertionException& e ) { + // should be a dup key error on _id index + if( tableToIndex || d->capped ) { + string s = e.toString(); + s += " : on addIndex/capped - collection and its index will not match"; + uassert_nothrow(s.c_str()); + log() << s << '\n'; + } + else { + // normal case -- we can roll back + _deleteRecord(d, ns, r, loc); + throw; + } + } + } + + // out() << " inserted at loc:" << hex << loc.getOfs() << " lenwhdr:" << hex << lenWHdr << dec << ' ' << ns << endl; + return loc; + } + + /* special version of insert for transaction logging -- streamlined a bit. + assumes ns is capped and no indexes + */ + Record* DataFileMgr::fast_oplog_insert(NamespaceDetails *d, const char *ns, int len) { + RARELY assert( d == nsdetails(ns) ); + + DiskLoc extentLoc; + int lenWHdr = len + Record::HeaderSize; + DiskLoc loc = d->alloc(ns, lenWHdr, extentLoc); + if ( loc.isNull() ) { + assert(false); + return 0; + } + + Record *r = loc.rec(); + assert( r->lengthWithHeaders >= lenWHdr ); + + Extent *e = r->myExtent(loc); + if ( e->lastRecord.isNull() ) { + e->firstRecord = e->lastRecord = loc; + r->prevOfs = r->nextOfs = DiskLoc::NullOfs; + } + else { + Record *oldlast = e->lastRecord.rec(); + r->prevOfs = e->lastRecord.getOfs(); + r->nextOfs = DiskLoc::NullOfs; + oldlast->nextOfs = loc.getOfs(); + e->lastRecord = loc; + } + + d->nrecords++; + + return r; + } + + void DataFileMgr::init(const string& path ) { + /* boost::filesystem::path path( dir ); + path /= "temp.dat"; + string pathString = path.string(); + temp.open(pathString.c_str(), 64 * 1024 * 1024); + */ + } + + void pdfileInit() { + // namespaceIndex.init(dbpath); + theDataFileMgr.init(dbpath); + } + +} // namespace mongo + +#include "clientcursor.h" + +namespace mongo { + + void dropDatabase(const char *ns) { + // ns is of the form "<dbname>.$cmd" + char cl[256]; + nsToDatabase(ns, cl); + log(1) << "dropDatabase " << cl << endl; + assert( cc().database()->name == cl ); + + closeDatabase( cl ); + _deleteDataFiles(cl); + } + + typedef boost::filesystem::path Path; + + // back up original database files to 'temp' dir + void _renameForBackup( const char *database, const Path &reservedPath ) { + class Renamer : public FileOp { + public: + Renamer( const Path &reservedPath ) : reservedPath_( reservedPath ) {} + private: + const boost::filesystem::path &reservedPath_; + virtual bool apply( const Path &p ) { + if ( !boost::filesystem::exists( p ) ) + return false; + boost::filesystem::rename( p, reservedPath_ / ( p.leaf() + ".bak" ) ); + return true; + } + virtual const char * op() const { + return "renaming"; + } + } renamer( reservedPath ); + _applyOpToDataFiles( database, renamer, true ); + } + + // move temp files to standard data dir + void _replaceWithRecovered( const char *database, const char *reservedPathString ) { + class : public FileOp { + virtual bool apply( const Path &p ) { + if ( !boost::filesystem::exists( p ) ) + return false; + boost::filesystem::rename( p, boost::filesystem::path(dbpath) / p.leaf() ); + return true; + } + virtual const char * op() const { + return "renaming"; + } + } renamer; + _applyOpToDataFiles( database, renamer, true, reservedPathString ); + } + + // generate a directory name for storing temp data files + Path uniqueReservedPath( const char *prefix ) { + Path dbPath = Path( dbpath ); + Path reservedPath; + int i = 0; + bool exists = false; + do { + stringstream ss; + ss << prefix << "_repairDatabase_" << i++; + reservedPath = dbPath / ss.str(); + BOOST_CHECK_EXCEPTION( exists = boost::filesystem::exists( reservedPath ) ); + } while ( exists ); + return reservedPath; + } + + boost::intmax_t dbSize( const char *database ) { + class SizeAccumulator : public FileOp { + public: + SizeAccumulator() : totalSize_( 0 ) {} + boost::intmax_t size() const { + return totalSize_; + } + private: + virtual bool apply( const boost::filesystem::path &p ) { + if ( !boost::filesystem::exists( p ) ) + return false; + totalSize_ += boost::filesystem::file_size( p ); + return true; + } + virtual const char *op() const { + return "checking size"; + } + boost::intmax_t totalSize_; + }; + SizeAccumulator sa; + _applyOpToDataFiles( database, sa ); + return sa.size(); + } + +#if !defined(_WIN32) +} // namespace mongo +#include <sys/statvfs.h> +namespace mongo { +#endif + boost::intmax_t freeSpace() { +#if !defined(_WIN32) + struct statvfs info; + assert( !statvfs( dbpath.c_str() , &info ) ); + return boost::intmax_t( info.f_bavail ) * info.f_frsize; +#else + return -1; +#endif + } + + bool repairDatabase( const char *ns, string &errmsg, + bool preserveClonedFilesOnFailure, bool backupOriginalFiles ) { + stringstream ss; + ss << "localhost:" << cmdLine.port; + string localhost = ss.str(); + + // ns is of the form "<dbname>.$cmd" + char dbName[256]; + nsToDatabase(ns, dbName); + problem() << "repairDatabase " << dbName << endl; + assert( cc().database()->name == dbName ); + + boost::intmax_t totalSize = dbSize( dbName ); + boost::intmax_t freeSize = freeSpace(); + if ( freeSize > -1 && freeSize < totalSize ) { + stringstream ss; + ss << "Cannot repair database " << dbName << " having size: " << totalSize + << " (bytes) because free disk space is: " << freeSize << " (bytes)"; + errmsg = ss.str(); + problem() << errmsg << endl; + return false; + } + + Path reservedPath = + uniqueReservedPath( ( preserveClonedFilesOnFailure || backupOriginalFiles ) ? + "backup" : "tmp" ); + BOOST_CHECK_EXCEPTION( boost::filesystem::create_directory( reservedPath ) ); + string reservedPathString = reservedPath.native_directory_string(); + assert( setClient( dbName, reservedPathString.c_str() ) ); + + bool res = cloneFrom(localhost.c_str(), errmsg, dbName, + /*logForReplication=*/false, /*slaveok*/false, /*replauth*/false, /*snapshot*/false); + closeDatabase( dbName, reservedPathString.c_str() ); + + if ( !res ) { + problem() << "clone failed for " << dbName << " with error: " << errmsg << endl; + if ( !preserveClonedFilesOnFailure ) + BOOST_CHECK_EXCEPTION( boost::filesystem::remove_all( reservedPath ) ); + return false; + } + + assert( !setClient( dbName ) ); + closeDatabase( dbName ); + + if ( backupOriginalFiles ) + _renameForBackup( dbName, reservedPath ); + else + _deleteDataFiles( dbName ); + + _replaceWithRecovered( dbName, reservedPathString.c_str() ); + + if ( !backupOriginalFiles ) + BOOST_CHECK_EXCEPTION( boost::filesystem::remove_all( reservedPath ) ); + + return true; + } + + void _applyOpToDataFiles( const char *database, FileOp &fo, bool afterAllocator, const string& path ) { + if ( afterAllocator ) + theFileAllocator().waitUntilFinished(); + string c = database; + c += '.'; + boost::filesystem::path p(path); + boost::filesystem::path q; + q = p / (c+"ns"); + bool ok = false; + BOOST_CHECK_EXCEPTION( ok = fo.apply( q ) ); + if ( ok ) + log(2) << fo.op() << " file " << q.string() << '\n'; + int i = 0; + int extra = 10; // should not be necessary, this is defensive in case there are missing files + while ( 1 ) { + assert( i <= DiskLoc::MaxFiles ); + stringstream ss; + ss << c << i; + q = p / ss.str(); + BOOST_CHECK_EXCEPTION( ok = fo.apply(q) ); + if ( ok ) { + if ( extra != 10 ){ + log(1) << fo.op() << " file " << q.string() << '\n'; + log() << " _applyOpToDataFiles() warning: extra == " << extra << endl; + } + } + else if ( --extra <= 0 ) + break; + i++; + } + } + + NamespaceDetails* nsdetails_notinline(const char *ns) { return nsdetails(ns); } + + bool DatabaseHolder::closeAll( const string& path , BSONObjBuilder& result ){ + log(2) << "DatabaseHolder::closeAll path:" << path << endl; + dbMutex.assertWriteLocked(); + + map<string,Database*>& m = _paths[path]; + _size -= m.size(); + + set< string > dbs; + for ( map<string,Database*>::iterator i = m.begin(); i != m.end(); i++ ) { + dbs.insert( i->first ); + } + + BSONObjBuilder bb( result.subarrayStart( "dbs" ) ); + int n = 0; + for( set< string >::iterator i = dbs.begin(); i != dbs.end(); ++i ) { + string name = *i; + log(2) << "DatabaseHolder::closeAll path:" << path << " name:" << name << endl; + setClient( name.c_str() , path ); + closeDatabase( name.c_str() , path ); + bb.append( bb.numStr( n++ ).c_str() , name ); + } + bb.done(); + + return true; + } + + +} // namespace mongo diff --git a/db/pdfile.h b/db/pdfile.h new file mode 100644 index 0000000..19a8322 --- /dev/null +++ b/db/pdfile.h @@ -0,0 +1,448 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* pdfile.h + + Files: + database.ns - namespace index + database.1 - data files + database.2 + ... +*/ + +#pragma once + +#include "../stdafx.h" +#include "../util/mmap.h" +#include "storage.h" +#include "jsobjmanipulator.h" +#include "namespace.h" +#include "client.h" + +namespace mongo { + + class MDFHeader; + class Extent; + class Record; + class Cursor; + class OpDebug; + + void dropDatabase(const char *ns); + bool repairDatabase(const char *ns, string &errmsg, bool preserveClonedFilesOnFailure = false, bool backupOriginalFiles = false); + + /* low level - only drops this ns */ + void dropNS(const string& dropNs); + + /* deletes this ns, indexes and cursors */ + void dropCollection( const string &name, string &errmsg, BSONObjBuilder &result ); + bool userCreateNS(const char *ns, BSONObj j, string& err, bool logForReplication); + auto_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order, const DiskLoc &startLoc=DiskLoc()); + +// -1 if library unavailable. + boost::intmax_t freeSpace(); + + /*---------------------------------------------------------------------*/ + + class MDFHeader; + class MongoDataFile { + friend class DataFileMgr; + friend class BasicCursor; + public: + MongoDataFile(int fn) : fileNo(fn) { } + void open(const char *filename, int requestedDataSize = 0, bool preallocateOnly = false); + + /* allocate a new extent from this datafile. + @param capped - true if capped collection + @param loops is our recursion check variable - you want to pass in zero + */ + Extent* createExtent(const char *ns, int approxSize, bool capped = false, int loops = 0); + + MDFHeader *getHeader() { + return header; + } + + /* return max size an extent may be */ + static int maxSize(); + + private: + int defaultSize( const char *filename ) const; + + Extent* getExtent(DiskLoc loc); + Extent* _getExtent(DiskLoc loc); + Record* recordAt(DiskLoc dl); + + MemoryMappedFile mmf; + MDFHeader *header; + int fileNo; + }; + + class DataFileMgr { + friend class BasicCursor; + public: + void init(const string& path ); + + /* see if we can find an extent of the right size in the freelist. */ + static Extent* allocFromFreeList(const char *ns, int approxSize, bool capped = false); + + /** @return DiskLoc where item ends up */ + const DiskLoc update( + const char *ns, + Record *toupdate, const DiskLoc& dl, + const char *buf, int len, OpDebug& debug); + // The object o may be updated if modified on insert. + void insertAndLog( const char *ns, const BSONObj &o, bool god = false ); + DiskLoc insert(const char *ns, BSONObj &o, bool god = false); + DiskLoc insert(const char *ns, const void *buf, int len, bool god = false, const BSONElement &writeId = BSONElement(), bool mayAddIndex = true); + void deleteRecord(const char *ns, Record *todelete, const DiskLoc& dl, bool cappedOK = false, bool noWarn = false); + static auto_ptr<Cursor> findAll(const char *ns, const DiskLoc &startLoc = DiskLoc()); + + /* special version of insert for transaction logging -- streamlined a bit. + assumes ns is capped and no indexes + no _id field check + */ + Record* fast_oplog_insert(NamespaceDetails *d, const char *ns, int len); + + static Extent* getExtent(const DiskLoc& dl); + static Record* getRecord(const DiskLoc& dl); + + /* does not clean up indexes, etc. : just deletes the record in the pdfile. */ + void _deleteRecord(NamespaceDetails *d, const char *ns, Record *todelete, const DiskLoc& dl); + + private: + vector<MongoDataFile *> files; + }; + + extern DataFileMgr theDataFileMgr; + +#pragma pack(1) + + class DeletedRecord { + public: + int lengthWithHeaders; + int extentOfs; + DiskLoc nextDeleted; + Extent* myExtent(const DiskLoc& myLoc) { + return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); + } + }; + + /* Record is a record in a datafile. DeletedRecord is similar but for deleted space. + + *11:03:20 AM) dm10gen: regarding extentOfs... + (11:03:42 AM) dm10gen: an extent is a continugous disk area, which contains many Records and DeleteRecords + (11:03:56 AM) dm10gen: a DiskLoc has two pieces, the fileno and ofs. (64 bit total) + (11:04:16 AM) dm10gen: to keep the headesr small, instead of storing a 64 bit ptr to the full extent address, we keep just the offset + (11:04:29 AM) dm10gen: we can do this as we know the record's address, and it has the same fileNo + (11:04:33 AM) dm10gen: see class DiskLoc for more info + (11:04:43 AM) dm10gen: so that is how Record::myExtent() works + (11:04:53 AM) dm10gen: on an alloc(), when we build a new Record, we must popular its extentOfs then + */ + class Record { + public: + enum HeaderSizeValue { HeaderSize = 16 }; + int lengthWithHeaders; + int extentOfs; + int nextOfs; + int prevOfs; + char data[4]; + int netLength() { + return lengthWithHeaders - HeaderSize; + } + //void setNewLength(int netlen) { lengthWithHeaders = netlen + HeaderSize; } + + /* use this when a record is deleted. basically a union with next/prev fields */ + DeletedRecord& asDeleted() { + return *((DeletedRecord*) this); + } + + Extent* myExtent(const DiskLoc& myLoc) { + return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); + } + /* get the next record in the namespace, traversing extents as necessary */ + DiskLoc getNext(const DiskLoc& myLoc); + DiskLoc getPrev(const DiskLoc& myLoc); + }; + + /* extents are datafile regions where all the records within the region + belong to the same namespace. + + (11:12:35 AM) dm10gen: when the extent is allocated, all its empty space is stuck into one big DeletedRecord + (11:12:55 AM) dm10gen: and that is placed on the free list + */ + class Extent { + public: + unsigned magic; + DiskLoc myLoc; + DiskLoc xnext, xprev; /* next/prev extent for this namespace */ + + /* which namespace this extent is for. this is just for troubleshooting really + and won't even be correct if the collection were renamed! + */ + Namespace nsDiagnostic; + + int length; /* size of the extent, including these fields */ + DiskLoc firstRecord, lastRecord; + char extentData[4]; + + bool validates() { + return !(firstRecord.isNull() ^ lastRecord.isNull()) && + length >= 0 && !myLoc.isNull(); + } + + void dump(iostream& s) { + s << " loc:" << myLoc.toString() << " xnext:" << xnext.toString() << " xprev:" << xprev.toString() << '\n'; + s << " nsdiag:" << nsDiagnostic.buf << '\n'; + s << " size:" << length << " firstRecord:" << firstRecord.toString() << " lastRecord:" << lastRecord.toString() << '\n'; + } + + /* assumes already zeroed -- insufficient for block 'reuse' perhaps + Returns a DeletedRecord location which is the data in the extent ready for us. + Caller will need to add that to the freelist structure in namespacedetail. + */ + DiskLoc init(const char *nsname, int _length, int _fileNo, int _offset); + + /* like init(), but for a reuse case */ + DiskLoc reuse(const char *nsname); + + void assertOk() { + assert(magic == 0x41424344); + } + + Record* newRecord(int len); + + Record* getRecord(DiskLoc dl) { + assert( !dl.isNull() ); + assert( dl.sameFile(myLoc) ); + int x = dl.getOfs() - myLoc.getOfs(); + assert( x > 0 ); + return (Record *) (((char *) this) + x); + } + + Extent* getNextExtent() { + return xnext.isNull() ? 0 : DataFileMgr::getExtent(xnext); + } + Extent* getPrevExtent() { + return xprev.isNull() ? 0 : DataFileMgr::getExtent(xprev); + } + }; + + /* + ---------------------- + Header + ---------------------- + Extent (for a particular namespace) + Record + ... + Record (some chained for unused space) + ---------------------- + more Extents... + ---------------------- + */ + + /* data file header */ + class MDFHeader { + public: + int version; + int versionMinor; + int fileLength; + DiskLoc unused; /* unused is the portion of the file that doesn't belong to any allocated extents. -1 = no more */ + int unusedLength; + char reserved[8192 - 4*4 - 8]; + + char data[4]; + + static int headerSize() { + return sizeof(MDFHeader) - 4; + } + + bool currentVersion() const { + return ( version == VERSION ) && ( versionMinor == VERSION_MINOR ); + } + + bool uninitialized() const { + if ( version == 0 ) return true; + return false; + } + + Record* getRecord(DiskLoc dl) { + int ofs = dl.getOfs(); + assert( ofs >= headerSize() ); + return (Record*) (((char *) this) + ofs); + } + + void init(int fileno, int filelength) { + if ( uninitialized() ) { + assert(filelength > 32768 ); + assert( headerSize() == 8192 ); + fileLength = filelength; + version = VERSION; + versionMinor = VERSION_MINOR; + unused.setOfs( fileno, headerSize() ); + assert( (data-(char*)this) == headerSize() ); + unusedLength = fileLength - headerSize() - 16; + memcpy(data+unusedLength, " \nthe end\n", 16); + } + } + + bool isEmpty() const { + return uninitialized() || ( unusedLength == fileLength - headerSize() - 16 ); + } + }; + +#pragma pack() + + inline Extent* MongoDataFile::_getExtent(DiskLoc loc) { + loc.assertOk(); + Extent *e = (Extent *) (((char *)header) + loc.getOfs()); + return e; + } + + inline Extent* MongoDataFile::getExtent(DiskLoc loc) { + Extent *e = _getExtent(loc); + e->assertOk(); + return e; + } + +} // namespace mongo + +#include "cursor.h" + +namespace mongo { + + inline Record* MongoDataFile::recordAt(DiskLoc dl) { + return header->getRecord(dl); + } + + inline DiskLoc Record::getNext(const DiskLoc& myLoc) { + if ( nextOfs != DiskLoc::NullOfs ) { + /* defensive */ + if ( nextOfs >= 0 && nextOfs < 10 ) { + sayDbContext("Assertion failure - Record::getNext() referencing a deleted record?"); + return DiskLoc(); + } + + return DiskLoc(myLoc.a(), nextOfs); + } + Extent *e = myExtent(myLoc); + while ( 1 ) { + if ( e->xnext.isNull() ) + return DiskLoc(); // end of table. + e = e->xnext.ext(); + if ( !e->firstRecord.isNull() ) + break; + // entire extent could be empty, keep looking + } + return e->firstRecord; + } + inline DiskLoc Record::getPrev(const DiskLoc& myLoc) { + if ( prevOfs != DiskLoc::NullOfs ) + return DiskLoc(myLoc.a(), prevOfs); + Extent *e = myExtent(myLoc); + if ( e->xprev.isNull() ) + return DiskLoc(); + return e->xprev.ext()->lastRecord; + } + + inline Record* DiskLoc::rec() const { + return DataFileMgr::getRecord(*this); + } + inline BSONObj DiskLoc::obj() const { + return BSONObj(rec()); + } + inline DeletedRecord* DiskLoc::drec() const { + assert( fileNo != -1 ); + return (DeletedRecord*) rec(); + } + inline Extent* DiskLoc::ext() const { + return DataFileMgr::getExtent(*this); + } + + /*---------------------------------------------------------------------*/ + +} // namespace mongo + +#include "rec.h" +#include "database.h" + +namespace mongo { + + // Heritable class to implement an operation that may be applied to all + // files in a database using _applyOpToDataFiles() + class FileOp { + public: + virtual ~FileOp() {} + // Return true if file exists and operation successful + virtual bool apply( const boost::filesystem::path &p ) = 0; + virtual const char * op() const = 0; + }; + + void _applyOpToDataFiles( const char *database, FileOp &fo, bool afterAllocator = false, const string& path = dbpath ); + + inline void _deleteDataFiles(const char *database) { + class : public FileOp { + virtual bool apply( const boost::filesystem::path &p ) { + return boost::filesystem::remove( p ); + } + virtual const char * op() const { + return "remove"; + } + } deleter; + _applyOpToDataFiles( database, deleter, true ); + } + + boost::intmax_t dbSize( const char *database ); + + inline NamespaceIndex* nsindex(const char *ns) { + Database *database = cc().database(); + assert( database ); + DEV { + char buf[256]; + nsToDatabase(ns, buf); + if ( database->name != buf ) { + out() << "ERROR: attempt to write to wrong database database\n"; + out() << " ns:" << ns << '\n'; + out() << " database->name:" << database->name << endl; + assert( database->name == buf ); + } + } + return &database->namespaceIndex; + } + + inline NamespaceDetails* nsdetails(const char *ns) { + // if this faults, did you set the current db first? (Client::Context + dblock) + return nsindex(ns)->details(ns); + } + + inline MongoDataFile& DiskLoc::pdf() const { + assert( fileNo != -1 ); + return *cc().database()->getFile(fileNo); + } + + inline Extent* DataFileMgr::getExtent(const DiskLoc& dl) { + assert( dl.a() != -1 ); + return cc().database()->getFile(dl.a())->getExtent(dl); + } + + inline Record* DataFileMgr::getRecord(const DiskLoc& dl) { + assert( dl.a() != -1 ); + return cc().database()->getFile(dl.a())->recordAt(dl); + } + + void ensureHaveIdIndex(const char *ns); + + bool deleteIndexes( NamespaceDetails *d, const char *ns, const char *name, string &errmsg, BSONObjBuilder &anObjBuilder, bool maydeleteIdIndex ); + +} // namespace mongo diff --git a/db/query.cpp b/db/query.cpp new file mode 100644 index 0000000..9c82609 --- /dev/null +++ b/db/query.cpp @@ -0,0 +1,921 @@ +// query.cpp + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "query.h" +#include "pdfile.h" +#include "jsobjmanipulator.h" +#include "../util/builder.h" +#include <time.h> +#include "introspect.h" +#include "btree.h" +#include "../util/lruishmap.h" +#include "json.h" +#include "repl.h" +#include "replset.h" +#include "scanandorder.h" +#include "security.h" +#include "curop.h" +#include "commands.h" +#include "queryoptimizer.h" +#include "lasterror.h" + +namespace mongo { + + /* We cut off further objects once we cross this threshold; thus, you might get + a little bit more than this, it is a threshold rather than a limit. + */ + const int MaxBytesToReturnToClientAtOnce = 4 * 1024 * 1024; + + //ns->query->DiskLoc +// LRUishMap<BSONObj,DiskLoc,5> lrutest(123); + + extern bool useCursors; + extern bool useHints; + + // Just try to identify best plan. + class DeleteOp : public QueryOp { + public: + DeleteOp( bool justOne, int& bestCount ) : + justOne_( justOne ), + count_(), + bestCount_( bestCount ), + nScanned_() { + } + virtual void init() { + c_ = qp().newCursor(); + matcher_.reset( new CoveredIndexMatcher( qp().query(), qp().indexKey() ) ); + } + virtual void next() { + if ( !c_->ok() ) { + setComplete(); + return; + } + + DiskLoc rloc = c_->currLoc(); + + if ( matcher_->matches(c_->currKey(), rloc ) ) { + if ( !c_->getsetdup(rloc) ) + ++count_; + } + + c_->advance(); + ++nScanned_; + if ( count_ > bestCount_ ) + bestCount_ = count_; + + if ( count_ > 0 ) { + if ( justOne_ ) + setComplete(); + else if ( nScanned_ >= 100 && count_ == bestCount_ ) + setComplete(); + } + } + virtual bool mayRecordPlan() const { return !justOne_; } + virtual QueryOp *clone() const { + return new DeleteOp( justOne_, bestCount_ ); + } + auto_ptr< Cursor > newCursor() const { return qp().newCursor(); } + private: + bool justOne_; + int count_; + int &bestCount_; + long long nScanned_; + auto_ptr< Cursor > c_; + auto_ptr< CoveredIndexMatcher > matcher_; + }; + + /* ns: namespace, e.g. <database>.<collection> + pattern: the "where" clause / criteria + justOne: stop after 1 match + */ + int deleteObjects(const char *ns, BSONObj pattern, bool justOne, bool logop, bool god) { + if( !god ) { + if ( strstr(ns, ".system.") ) { + /* note a delete from system.indexes would corrupt the db + if done here, as there are pointers into those objects in + NamespaceDetails. + */ + uassert(12050, "cannot delete from system namespace", legalClientSystemNS( ns , true ) ); + } + if ( strchr( ns , '$' ) ){ + log() << "cannot delete from collection with reserved $ in name: " << ns << endl; + uassert( 10100 , "cannot delete from collection with reserved $ in name", strchr(ns, '$') == 0 ); + } + } + + NamespaceDetails *d = nsdetails( ns ); + if ( ! d ) + return 0; + uassert( 10101 , "can't remove from a capped collection" , ! d->capped ); + + int nDeleted = 0; + QueryPlanSet s( ns, pattern, BSONObj() ); + int best = 0; + DeleteOp original( justOne, best ); + shared_ptr< DeleteOp > bestOp = s.runOp( original ); + auto_ptr< Cursor > creal = bestOp->newCursor(); + + if( !creal->ok() ) + return nDeleted; + + CoveredIndexMatcher matcher(pattern, creal->indexKeyPattern()); + + auto_ptr<ClientCursor> cc; + cc.reset( new ClientCursor() ); + cc->c = creal; + cc->ns = ns; + cc->noTimeout(); + cc->setDoingDeletes( true ); + + CursorId id = cc->cursorid; + + unsigned long long nScanned = 0; + do { + if ( ++nScanned % 128 == 0 && !matcher.docMatcher().atomic() ) { + if ( ! cc->yield() ){ + cc.release(); // has already been deleted elsewhere + break; + } + } + + // this way we can avoid calling updateLocation() every time (expensive) + // as well as some other nuances handled + cc->setDoingDeletes( true ); + + DiskLoc rloc = cc->c->currLoc(); + BSONObj key = cc->c->currKey(); + + cc->c->advance(); + + if ( ! matcher.matches( key , rloc ) ) + continue; + + assert( !cc->c->getsetdup(rloc) ); // can't be a dup, we deleted it! + + if ( !justOne ) { + /* NOTE: this is SLOW. this is not good, noteLocation() was designed to be called across getMore + blocks. here we might call millions of times which would be bad. + */ + cc->c->noteLocation(); + } + + if ( logop ) { + BSONElement e; + if( BSONObj( rloc.rec() ).getObjectID( e ) ) { + BSONObjBuilder b; + b.append( e ); + bool replJustOne = true; + logOp( "d", ns, b.done(), 0, &replJustOne ); + } else { + problem() << "deleted object without id, not logging" << endl; + } + } + + theDataFileMgr.deleteRecord(ns, rloc.rec(), rloc); + nDeleted++; + if ( justOne ) + break; + cc->c->checkLocation(); + + } while ( cc->c->ok() ); + + if ( cc.get() && ClientCursor::find( id , false ) == 0 ){ + cc.release(); + } + + return nDeleted; + } + + int otherTraceLevel = 0; + + int initialExtentSize(int len); + + bool runCommands(const char *ns, BSONObj& jsobj, CurOp& curop, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) { + try { + return _runCommands(ns, jsobj, b, anObjBuilder, fromRepl, queryOptions); + } + catch ( AssertionException& e ) { + if ( !e.msg.empty() ) + anObjBuilder.append("assertion", e.msg); + } + curop.debug().str << " assertion "; + anObjBuilder.append("errmsg", "db assertion failure"); + anObjBuilder.append("ok", 0.0); + BSONObj x = anObjBuilder.done(); + b.append((void*) x.objdata(), x.objsize()); + return true; + } + + int nCaught = 0; + + void killCursors(int n, long long *ids) { + int k = 0; + for ( int i = 0; i < n; i++ ) { + if ( ClientCursor::erase(ids[i]) ) + k++; + } + log( k == n ) << "killcursors: found " << k << " of " << n << '\n'; + } + + BSONObj id_obj = fromjson("{\"_id\":ObjectId( \"000000000000000000000000\" )}"); + BSONObj empty_obj = fromjson("{}"); + + /* This is for languages whose "objects" are not well ordered (JSON is well ordered). + [ { a : ... } , { b : ... } ] -> { a : ..., b : ... } + */ + inline BSONObj transformOrderFromArrayFormat(BSONObj order) { + /* note: this is slow, but that is ok as order will have very few pieces */ + BSONObjBuilder b; + char p[2] = "0"; + + while ( 1 ) { + BSONObj j = order.getObjectField(p); + if ( j.isEmpty() ) + break; + BSONElement e = j.firstElement(); + uassert( 10102 , "bad order array", !e.eoo()); + uassert( 10103 , "bad order array [2]", e.isNumber()); + b.append(e); + (*p)++; + uassert( 10104 , "too many ordering elements", *p <= '9'); + } + + return b.obj(); + } + + + //int dump = 0; + + /* empty result for error conditions */ + QueryResult* emptyMoreResult(long long cursorid) { + BufBuilder b(32768); + b.skip(sizeof(QueryResult)); + QueryResult *qr = (QueryResult *) b.buf(); + qr->cursorId = 0; // 0 indicates no more data to retrieve. + qr->startingFrom = 0; + qr->len = b.len(); + qr->setOperation(opReply); + qr->nReturned = 0; + b.decouple(); + return qr; + } + + QueryResult* getMore(const char *ns, int ntoreturn, long long cursorid , CurOp& curop ) { + StringBuilder& ss = curop.debug().str; + ClientCursor::Pointer p(cursorid); + ClientCursor *cc = p._c; + + int bufSize = 512; + if ( cc ){ + bufSize += sizeof( QueryResult ); + bufSize += ( ntoreturn ? 4 : 1 ) * 1024 * 1024; + } + BufBuilder b( bufSize ); + + b.skip(sizeof(QueryResult)); + + int resultFlags = 0; //QueryResult::ResultFlag_AwaitCapable; + int start = 0; + int n = 0; + + if ( !cc ) { + log() << "getMore: cursorid not found " << ns << " " << cursorid << endl; + cursorid = 0; + resultFlags = QueryResult::ResultFlag_CursorNotFound; + } + else { + ss << " query: " << cc->query << " "; + start = cc->pos; + Cursor *c = cc->c.get(); + c->checkLocation(); + while ( 1 ) { + if ( !c->ok() ) { + if ( c->tailable() ) { + if ( c->advance() ) { + continue; + } + break; + } + p.release(); + bool ok = ClientCursor::erase(cursorid); + assert(ok); + cursorid = 0; + cc = 0; + break; + } + if ( !cc->matcher->matches(c->currKey(), c->currLoc() ) ) { + } + else { + //out() << "matches " << c->currLoc().toString() << '\n'; + if( c->getsetdup(c->currLoc()) ) { + //out() << " but it's a dup \n"; + } + else { + BSONObj js = c->current(); + fillQueryResultFromObj(b, cc->filter.get(), js); + n++; + if ( (ntoreturn>0 && (n >= ntoreturn || b.len() > MaxBytesToReturnToClientAtOnce)) || + (ntoreturn==0 && b.len()>1*1024*1024) ) { + c->advance(); + cc->pos += n; + //cc->updateLocation(); + break; + } + } + } + c->advance(); + } + if ( cc ) { + cc->updateLocation(); + cc->mayUpgradeStorage(); + } + } + + QueryResult *qr = (QueryResult *) b.buf(); + qr->len = b.len(); + qr->setOperation(opReply); + qr->_resultFlags() = resultFlags; + qr->cursorId = cursorid; + qr->startingFrom = start; + qr->nReturned = n; + b.decouple(); + + return qr; + } + + class CountOp : public QueryOp { + public: + CountOp( const BSONObj &spec ) : spec_( spec ), count_(), bc_() {} + virtual void init() { + query_ = spec_.getObjectField( "query" ); + c_ = qp().newCursor(); + matcher_.reset( new CoveredIndexMatcher( query_, c_->indexKeyPattern() ) ); + if ( qp().exactKeyMatch() && ! matcher_->needRecord() ) { + query_ = qp().simplifiedQuery( qp().indexKey() ); + bc_ = dynamic_cast< BtreeCursor* >( c_.get() ); + bc_->forgetEndKey(); + } + + skip_ = spec_["skip"].numberLong(); + limit_ = spec_["limit"].numberLong(); + } + + virtual void next() { + if ( !c_->ok() ) { + setComplete(); + return; + } + if ( bc_ ) { + if ( firstMatch_.isEmpty() ) { + firstMatch_ = bc_->currKeyNode().key; + // if not match + if ( query_.woCompare( firstMatch_, BSONObj(), false ) ) { + setComplete(); + return; + } + _gotOne(); + } else { + if ( !firstMatch_.woEqual( bc_->currKeyNode().key ) ) { + setComplete(); + return; + } + _gotOne(); + } + } else { + if ( !matcher_->matches(c_->currKey(), c_->currLoc() ) ) { + } + else if( !c_->getsetdup(c_->currLoc()) ) { + _gotOne(); + } + } + c_->advance(); + } + virtual QueryOp *clone() const { + return new CountOp( spec_ ); + } + long long count() const { return count_; } + virtual bool mayRecordPlan() const { return true; } + private: + + void _gotOne(){ + if ( skip_ ){ + skip_--; + return; + } + + if ( limit_ > 0 && count_ >= limit_ ){ + setComplete(); + return; + } + + count_++; + } + + BSONObj spec_; + long long count_; + long long skip_; + long long limit_; + auto_ptr< Cursor > c_; + BSONObj query_; + BtreeCursor *bc_; + auto_ptr< CoveredIndexMatcher > matcher_; + BSONObj firstMatch_; + }; + + /* { count: "collectionname"[, query: <query>] } + returns -1 on ns does not exist error. + */ + long long runCount( const char *ns, const BSONObj &cmd, string &err ) { + NamespaceDetails *d = nsdetails( ns ); + if ( !d ) { + err = "ns missing"; + return -1; + } + BSONObj query = cmd.getObjectField("query"); + + // count of all objects + if ( query.isEmpty() ){ + long long num = d->nrecords; + num = num - cmd["skip"].numberLong(); + if ( num < 0 ) { + num = 0; + } + if ( cmd["limit"].isNumber() ){ + long long limit = cmd["limit"].numberLong(); + if ( limit < num ){ + num = limit; + } + } + return num; + } + QueryPlanSet qps( ns, query, BSONObj() ); + CountOp original( cmd ); + shared_ptr< CountOp > res = qps.runOp( original ); + if ( !res->complete() ) { + log() << "Count with ns: " << ns << " and query: " << query + << " failed with exception: " << res->exceptionMessage() + << endl; + return 0; + } + return res->count(); + } + + // Implements database 'query' requests using the query optimizer's QueryOp interface + class UserQueryOp : public QueryOp { + public: + UserQueryOp( int ntoskip, int ntoreturn, const BSONObj &order, bool wantMore, + bool explain, FieldMatcher *filter, int queryOptions ) : + b_( 32768 ), + ntoskip_( ntoskip ), + ntoreturn_( ntoreturn ), + order_( order ), + wantMore_( wantMore ), + explain_( explain ), + filter_( filter ), + ordering_(), + nscanned_(), + queryOptions_( queryOptions ), + n_(), + soSize_(), + saveClientCursor_(), + findingStart_( (queryOptions & QueryOption_OplogReplay) != 0 ), + findingStartCursor_() + { + uassert( 10105 , "bad skip value in query", ntoskip >= 0); + } + + virtual void init() { + b_.skip( sizeof( QueryResult ) ); + + // findingStart mode is used to find the first operation of interest when + // we are scanning through a repl log. For efficiency in the common case, + // where the first operation of interest is closer to the tail than the head, + // we start from the tail of the log and work backwards until we find the + // first operation of interest. Then we scan forward from that first operation, + // actually returning results to the client. During the findingStart phase, + // we release the db mutex occasionally to avoid blocking the db process for + // an extended period of time. + if ( findingStart_ ) { + // Use a ClientCursor here so we can release db mutex while scanning + // oplog (can take quite a while with large oplogs). + findingStartCursor_ = new ClientCursor(); + findingStartCursor_->noTimeout(); + findingStartCursor_->c = qp().newReverseCursor(); + findingStartCursor_->ns = qp().ns(); + } else { + c_ = qp().newCursor(); + } + + matcher_.reset(new CoveredIndexMatcher(qp().query(), qp().indexKey())); + + if ( qp().scanAndOrderRequired() ) { + ordering_ = true; + so_.reset( new ScanAndOrder( ntoskip_, ntoreturn_, order_ ) ); + wantMore_ = false; + } + } + virtual void next() { + if ( findingStart_ ) { + if ( !findingStartCursor_ || !findingStartCursor_->c->ok() ) { + findingStart_ = false; + c_ = qp().newCursor(); + } else if ( !matcher_->matches( findingStartCursor_->c->currKey(), findingStartCursor_->c->currLoc() ) ) { + findingStart_ = false; + c_ = qp().newCursor( findingStartCursor_->c->currLoc() ); + } else { + findingStartCursor_->c->advance(); + RARELY { + CursorId id = findingStartCursor_->cursorid; + findingStartCursor_->updateLocation(); + { + dbtemprelease t; + } + findingStartCursor_ = ClientCursor::find( id, false ); + } + return; + } + } + + if ( findingStartCursor_ ) { + ClientCursor::erase( findingStartCursor_->cursorid ); + findingStartCursor_ = 0; + } + + if ( !c_->ok() ) { + finish(); + return; + } + + bool mayCreateCursor1 = wantMore_ && ntoreturn_ != 1 && useCursors; + + if( 0 ) { + BSONObj js = c_->current(); + cout << "SCANNING " << js << endl; + } + + nscanned_++; + if ( !matcher_->matches(c_->currKey(), c_->currLoc() ) ) { + ; + } + else { + DiskLoc cl = c_->currLoc(); + if( !c_->getsetdup(cl) ) { + BSONObj js = c_->current(); + // got a match. + assert( js.objsize() >= 0 ); //defensive for segfaults + if ( ordering_ ) { + // note: no cursors for non-indexed, ordered results. results must be fairly small. + so_->add(js); + } + else if ( ntoskip_ > 0 ) { + ntoskip_--; + } else { + if ( explain_ ) { + n_++; + if ( n_ >= ntoreturn_ && !wantMore_ ) { + // .limit() was used, show just that much. + finish(); + return; + } + } + else { + fillQueryResultFromObj(b_, filter_, js); + n_++; + if ( (ntoreturn_>0 && (n_ >= ntoreturn_ || b_.len() > MaxBytesToReturnToClientAtOnce)) || + (ntoreturn_==0 && (b_.len()>1*1024*1024 || n_>=101)) ) { + /* if ntoreturn is zero, we return up to 101 objects. on the subsequent getmore, there + is only a size limit. The idea is that on a find() where one doesn't use much results, + we don't return much, but once getmore kicks in, we start pushing significant quantities. + + The n limit (vs. size) is important when someone fetches only one small field from big + objects, which causes massive scanning server-side. + */ + /* if only 1 requested, no cursor saved for efficiency...we assume it is findOne() */ + if ( mayCreateCursor1 ) { + c_->advance(); + if ( c_->ok() ) { + // more...so save a cursor + saveClientCursor_ = true; + } + } + finish(); + return; + } + } + } + } + } + c_->advance(); + } + void finish() { + if ( explain_ ) { + n_ = ordering_ ? so_->size() : n_; + } else if ( ordering_ ) { + so_->fill(b_, filter_, n_); + } + if ( mayCreateCursor2() ) { + c_->setTailable(); + } + // If the tailing request succeeded. + if ( c_->tailable() ) { + saveClientCursor_ = true; + } + setComplete(); + } + virtual bool mayRecordPlan() const { return ntoreturn_ != 1; } + virtual QueryOp *clone() const { + return new UserQueryOp( ntoskip_, ntoreturn_, order_, wantMore_, explain_, filter_, queryOptions_ ); + } + BufBuilder &builder() { return b_; } + bool scanAndOrderRequired() const { return ordering_; } + auto_ptr< Cursor > cursor() { return c_; } + auto_ptr< CoveredIndexMatcher > matcher() { return matcher_; } + int n() const { return n_; } + long long nscanned() const { return nscanned_; } + bool saveClientCursor() const { return saveClientCursor_; } + bool mayCreateCursor2() const { return ( queryOptions_ & QueryOption_CursorTailable ) && ntoreturn_ != 1; } + private: + BufBuilder b_; + int ntoskip_; + int ntoreturn_; + BSONObj order_; + bool wantMore_; + bool explain_; + FieldMatcher *filter_; + bool ordering_; + auto_ptr< Cursor > c_; + long long nscanned_; + int queryOptions_; + auto_ptr< CoveredIndexMatcher > matcher_; + int n_; + int soSize_; + bool saveClientCursor_; + auto_ptr< ScanAndOrder > so_; + bool findingStart_; + ClientCursor * findingStartCursor_; + }; + + /* run a query -- includes checking for and running a Command */ + auto_ptr< QueryResult > runQuery(Message& m, QueryMessage& q, CurOp& curop ) { + StringBuilder& ss = curop.debug().str; + const char *ns = q.ns; + int ntoskip = q.ntoskip; + int _ntoreturn = q.ntoreturn; + BSONObj jsobj = q.query; + auto_ptr< FieldMatcher > filter = q.fields; // what fields to return (unspecified = full object) + int queryOptions = q.queryOptions; + BSONObj snapshotHint; + + Timer t; + if( logLevel >= 2 ) + log() << "runQuery: " << ns << jsobj << endl; + + long long nscanned = 0; + bool wantMore = true; + int ntoreturn = _ntoreturn; + if ( _ntoreturn < 0 ) { + /* _ntoreturn greater than zero is simply a hint on how many objects to send back per + "cursor batch". + A negative number indicates a hard limit. + */ + ntoreturn = -_ntoreturn; + wantMore = false; + } + ss << "query " << ns << " ntoreturn:" << ntoreturn; + curop.setQuery(jsobj); + + BufBuilder bb; + BSONObjBuilder cmdResBuf; + long long cursorid = 0; + + bb.skip(sizeof(QueryResult)); + + auto_ptr< QueryResult > qr; + int n = 0; + + Client& c = cc(); + /* we assume you are using findOne() for running a cmd... */ + if ( ntoreturn == 1 && runCommands(ns, jsobj, curop, bb, cmdResBuf, false, queryOptions) ) { + n = 1; + qr.reset( (QueryResult *) bb.buf() ); + bb.decouple(); + qr->setResultFlagsToOk(); + qr->len = bb.len(); + ss << " reslen:" << bb.len(); + // qr->channel = 0; + qr->setOperation(opReply); + qr->cursorId = cursorid; + qr->startingFrom = 0; + qr->nReturned = n; + } + else { + /* regular query */ + + AuthenticationInfo *ai = currentClient.get()->ai; + uassert( 10106 , "unauthorized", ai->isAuthorized(c.database()->name.c_str())); + + /* we allow queries to SimpleSlave's -- but not to the slave (nonmaster) member of a replica pair + so that queries to a pair are realtime consistent as much as possible. use setSlaveOk() to + query the nonmaster member of a replica pair. + */ + uassert( 10107 , "not master", isMaster() || (queryOptions & QueryOption_SlaveOk) || slave == SimpleSlave ); + + BSONElement hint; + BSONObj min; + BSONObj max; + bool explain = false; + bool _gotquery = false; + bool snapshot = false; + BSONObj query; + { + BSONElement e = jsobj.findElement("$query"); + if ( e.eoo() ) + e = jsobj.findElement("query"); + if ( !e.eoo() && (e.type() == Object || e.type() == Array) ) { + query = e.embeddedObject(); + _gotquery = true; + } + } + BSONObj order; + { + BSONElement e = jsobj.findElement("$orderby"); + if ( e.eoo() ) + e = jsobj.findElement("orderby"); + if ( !e.eoo() ) { + order = e.embeddedObjectUserCheck(); + if ( e.type() == Array ) + order = transformOrderFromArrayFormat(order); + } + } + if ( !_gotquery && order.isEmpty() ) + query = jsobj; + else { + explain = jsobj.getBoolField("$explain"); + if ( useHints ) + hint = jsobj.getField("$hint"); + min = jsobj.getObjectField("$min"); + max = jsobj.getObjectField("$max"); + BSONElement e = jsobj.getField("$snapshot"); + snapshot = !e.eoo() && e.trueValue(); + if( snapshot ) { + uassert( 12001 , "E12001 can't sort with $snapshot", order.isEmpty()); + uassert( 12002 , "E12002 can't use hint with $snapshot", hint.eoo()); + NamespaceDetails *d = nsdetails(ns); + if ( d ){ + int i = d->findIdIndex(); + if( i < 0 ) { + if ( strstr( ns , ".system." ) == 0 ) + log() << "warning: no _id index on $snapshot query, ns:" << ns << endl; + } + else { + /* [dm] the name of an _id index tends to vary, so we build the hint the hard way here. + probably need a better way to specify "use the _id index" as a hint. if someone is + in the query optimizer please fix this then! + */ + BSONObjBuilder b; + b.append("$hint", d->idx(i).indexName()); + snapshotHint = b.obj(); + hint = snapshotHint.firstElement(); + } + } + } + } + + /* The ElemIter will not be happy if this isn't really an object. So throw exception + here when that is true. + (Which may indicate bad data from client.) + */ + if ( query.objsize() == 0 ) { + out() << "Bad query object?\n jsobj:"; + out() << jsobj.toString() << "\n query:"; + out() << query.toString() << endl; + uassert( 10110 , "bad query object", false); + } + + bool idHackWorked = false; + + if ( strcmp( query.firstElement().fieldName() , "_id" ) == 0 && query.nFields() == 1 && query.firstElement().isSimpleType() ){ + nscanned = 1; + + bool nsFound = false; + bool indexFound = false; + + BSONObj resObject; + bool found = Helpers::findById( c, ns , query , resObject , &nsFound , &indexFound ); + if ( nsFound == false || indexFound == true ){ + idHackWorked = true; + if ( found ){ + n = 1; + fillQueryResultFromObj( bb , filter.get() , resObject ); + } + qr.reset( (QueryResult *) bb.buf() ); + bb.decouple(); + qr->setResultFlagsToOk(); + qr->len = bb.len(); + ss << " reslen:" << bb.len(); + qr->setOperation(opReply); + qr->cursorId = cursorid; + qr->startingFrom = 0; + qr->nReturned = n; + } + } + + if ( ! idHackWorked ){ // non-simple _id lookup + BSONObj oldPlan; + if ( explain && hint.eoo() && min.isEmpty() && max.isEmpty() ) { + QueryPlanSet qps( ns, query, order ); + if ( qps.usingPrerecordedPlan() ) + oldPlan = qps.explain(); + } + QueryPlanSet qps( ns, query, order, &hint, !explain, min, max ); + UserQueryOp original( ntoskip, ntoreturn, order, wantMore, explain, filter.get(), queryOptions ); + shared_ptr< UserQueryOp > o = qps.runOp( original ); + UserQueryOp &dqo = *o; + massert( 10362 , dqo.exceptionMessage(), dqo.complete() ); + n = dqo.n(); + nscanned = dqo.nscanned(); + if ( dqo.scanAndOrderRequired() ) + ss << " scanAndOrder "; + auto_ptr< Cursor > c = dqo.cursor(); + log( 5 ) << " used cursor: " << c.get() << endl; + if ( dqo.saveClientCursor() ) { + ClientCursor *cc = new ClientCursor(); + if ( queryOptions & QueryOption_NoCursorTimeout ) + cc->noTimeout(); + cc->c = c; + cursorid = cc->cursorid; + cc->query = jsobj.getOwned(); + DEV out() << " query has more, cursorid: " << cursorid << endl; + cc->matcher = dqo.matcher(); + cc->ns = ns; + cc->pos = n; + cc->filter = filter; + cc->originalMessage = m; + cc->updateLocation(); + if ( !cc->c->ok() && cc->c->tailable() ) { + DEV out() << " query has no more but tailable, cursorid: " << cursorid << endl; + } else { + DEV out() << " query has more, cursorid: " << cursorid << endl; + } + } + if ( explain ) { + BSONObjBuilder builder; + builder.append("cursor", c->toString()); + builder.append("startKey", c->prettyStartKey()); + builder.append("endKey", c->prettyEndKey()); + builder.append("nscanned", double( dqo.nscanned() ) ); + builder.append("n", n); + if ( dqo.scanAndOrderRequired() ) + builder.append("scanAndOrder", true); + builder.append("millis", t.millis()); + if ( !oldPlan.isEmpty() ) + builder.append( "oldPlan", oldPlan.firstElement().embeddedObject().firstElement().embeddedObject() ); + if ( hint.eoo() ) + builder.appendElements(qps.explain()); + BSONObj obj = builder.done(); + fillQueryResultFromObj(dqo.builder(), 0, obj); + n = 1; + } + qr.reset( (QueryResult *) dqo.builder().buf() ); + dqo.builder().decouple(); + qr->cursorId = cursorid; + qr->setResultFlagsToOk(); + qr->len = dqo.builder().len(); + ss << " reslen:" << qr->len; + qr->setOperation(opReply); + qr->startingFrom = 0; + qr->nReturned = n; + } + } + + int duration = t.millis(); + Database *database = c.database(); + if ( (database && database->profile) || duration >= 100 ) { + ss << " nscanned:" << nscanned << ' '; + if ( ntoskip ) + ss << " ntoskip:" << ntoskip; + if ( database && database->profile ) + ss << " \nquery: "; + ss << jsobj << ' '; + } + ss << " nreturned:" << n; + return qr; + } + +} // namespace mongo diff --git a/db/query.h b/db/query.h new file mode 100644 index 0000000..d69b6d9 --- /dev/null +++ b/db/query.h @@ -0,0 +1,115 @@ +// query.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "../util/message.h" +#include "dbmessage.h" +#include "jsobj.h" +#include "storage.h" + +/* db request message format + + unsigned opid; // arbitary; will be echoed back + byte operation; + int options; + + then for: + + dbInsert: + string collection; + a series of JSObjects + dbDelete: + string collection; + int flags=0; // 1=DeleteSingle + JSObject query; + dbUpdate: + string collection; + int flags; // 1=upsert + JSObject query; + JSObject objectToUpdate; + objectToUpdate may include { $inc: <field> } or { $set: ... }, see struct Mod. + dbQuery: + string collection; + int nToSkip; + int nToReturn; // how many you want back as the beginning of the cursor data (0=no limit) + // greater than zero is simply a hint on how many objects to send back per "cursor batch". + // a negative number indicates a hard limit. + JSObject query; + [JSObject fieldsToReturn] + dbGetMore: + string collection; // redundant, might use for security. + int nToReturn; + int64 cursorID; + dbKillCursors=2007: + int n; + int64 cursorIDs[n]; + + Note that on Update, there is only one object, which is different + from insert where you can pass a list of objects to insert in the db. + Note that the update field layout is very similar layout to Query. +*/ + +// struct QueryOptions, QueryResult, QueryResultFlags in: +#include "../client/dbclient.h" + +namespace mongo { + + // for an existing query (ie a ClientCursor), send back additional information. + QueryResult* getMore(const char *ns, int ntoreturn, long long cursorid , CurOp& op); + + struct UpdateResult { + bool existing; + bool mod; + unsigned long long num; + + UpdateResult( bool e, bool m, unsigned long long n ) + : existing(e) , mod(m), num(n ){} + + int oldCode(){ + if ( ! num ) + return 0; + + if ( existing ){ + if ( mod ) + return 2; + return 1; + } + + if ( mod ) + return 3; + return 4; + } + }; + + /* returns true if an existing object was updated, false if no existing object was found. + multi - update multiple objects - mostly useful with things like $set + */ + UpdateResult updateObjects(const char *ns, BSONObj updateobj, BSONObj pattern, bool upsert, bool multi , bool logop , OpDebug& debug ); + + // If justOne is true, deletedId is set to the id of the deleted object. + int deleteObjects(const char *ns, BSONObj pattern, bool justOne, bool logop = false, bool god=false); + + long long runCount(const char *ns, const BSONObj& cmd, string& err); + + auto_ptr< QueryResult > runQuery(Message& m, QueryMessage& q, CurOp& curop ); + +} // namespace mongo + +#include "clientcursor.h" diff --git a/db/queryoptimizer.cpp b/db/queryoptimizer.cpp new file mode 100644 index 0000000..499417a --- /dev/null +++ b/db/queryoptimizer.cpp @@ -0,0 +1,624 @@ +/* queryoptimizer.cpp */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +#include "db.h" +#include "btree.h" +#include "pdfile.h" +#include "queryoptimizer.h" +#include "cmdline.h" + +namespace mongo { + + void checkTableScanAllowed( const char * ns ){ + if ( ! cmdLine.notablescan ) + return; + + if ( strstr( ns , ".system." ) || + strstr( ns , "local." ) ) + return; + + if ( ! nsdetails( ns ) ) + return; + + uassert( 10111 , (string)"table scans not allowed:" + ns , ! cmdLine.notablescan ); + } + + double elementDirection( const BSONElement &e ) { + if ( e.isNumber() ) + return e.number(); + return 1; + } + + QueryPlan::QueryPlan( + NamespaceDetails *_d, int _idxNo, + const FieldRangeSet &fbs, const BSONObj &order, const BSONObj &startKey, const BSONObj &endKey ) : + d(_d), idxNo(_idxNo), + fbs_( fbs ), + order_( order ), + index_( 0 ), + optimal_( false ), + scanAndOrderRequired_( true ), + exactKeyMatch_( false ), + direction_( 0 ), + endKeyInclusive_( endKey.isEmpty() ), + unhelpful_( false ) { + + if ( !fbs_.matchPossible() ) { + unhelpful_ = true; + scanAndOrderRequired_ = false; + return; + } + + if( idxNo >= 0 ) { + index_ = &d->idx(idxNo); + } else { + // full table scan case + if ( order_.isEmpty() || !strcmp( order_.firstElement().fieldName(), "$natural" ) ) + scanAndOrderRequired_ = false; + return; + } + + BSONObj idxKey = index_->keyPattern(); + BSONObjIterator o( order ); + BSONObjIterator k( idxKey ); + if ( !o.moreWithEOO() ) + scanAndOrderRequired_ = false; + while( o.moreWithEOO() ) { + BSONElement oe = o.next(); + if ( oe.eoo() ) { + scanAndOrderRequired_ = false; + break; + } + if ( !k.moreWithEOO() ) + break; + BSONElement ke; + while( 1 ) { + ke = k.next(); + if ( ke.eoo() ) + goto doneCheckOrder; + if ( strcmp( oe.fieldName(), ke.fieldName() ) == 0 ) + break; + if ( !fbs.range( ke.fieldName() ).equality() ) + goto doneCheckOrder; + } + int d = elementDirection( oe ) == elementDirection( ke ) ? 1 : -1; + if ( direction_ == 0 ) + direction_ = d; + else if ( direction_ != d ) + break; + } + doneCheckOrder: + if ( scanAndOrderRequired_ ) + direction_ = 0; + BSONObjIterator i( idxKey ); + int exactIndexedQueryCount = 0; + int optimalIndexedQueryCount = 0; + bool stillOptimalIndexedQueryCount = true; + set< string > orderFieldsUnindexed; + order.getFieldNames( orderFieldsUnindexed ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + const FieldRange &fb = fbs.range( e.fieldName() ); + if ( stillOptimalIndexedQueryCount ) { + if ( fb.nontrivial() ) + ++optimalIndexedQueryCount; + if ( !fb.equality() ) + stillOptimalIndexedQueryCount = false; + } else { + if ( fb.nontrivial() ) + optimalIndexedQueryCount = -1; + } + if ( fb.equality() ) { + BSONElement e = fb.max(); + if ( !e.isNumber() && !e.mayEncapsulate() && e.type() != RegEx ) + ++exactIndexedQueryCount; + } + orderFieldsUnindexed.erase( e.fieldName() ); + } + if ( !scanAndOrderRequired_ && + ( optimalIndexedQueryCount == fbs.nNontrivialRanges() ) ) + optimal_ = true; + if ( exactIndexedQueryCount == fbs.nNontrivialRanges() && + orderFieldsUnindexed.size() == 0 && + exactIndexedQueryCount == index_->keyPattern().nFields() && + exactIndexedQueryCount == fbs.query().nFields() ) { + exactKeyMatch_ = true; + } + indexBounds_ = fbs.indexBounds( idxKey, direction_ ); + if ( !startKey.isEmpty() || !endKey.isEmpty() ) { + BSONObj newStart, newEnd; + if ( !startKey.isEmpty() ) + newStart = startKey; + else + newStart = indexBounds_[ 0 ].first; + if ( !endKey.isEmpty() ) + newEnd = endKey; + else + newEnd = indexBounds_[ indexBounds_.size() - 1 ].second; + BoundList newBounds; + newBounds.push_back( make_pair( newStart, newEnd ) ); + indexBounds_ = newBounds; + } + if ( ( scanAndOrderRequired_ || order_.isEmpty() ) && + !fbs.range( idxKey.firstElement().fieldName() ).nontrivial() ) + unhelpful_ = true; + } + + auto_ptr< Cursor > QueryPlan::newCursor( const DiskLoc &startLoc ) const { + if ( !fbs_.matchPossible() ){ + if ( fbs_.nNontrivialRanges() ) + checkTableScanAllowed( fbs_.ns() ); + return auto_ptr< Cursor >( new BasicCursor( DiskLoc() ) ); + } + if ( !index_ ){ + if ( fbs_.nNontrivialRanges() ) + checkTableScanAllowed( fbs_.ns() ); + return findTableScan( fbs_.ns(), order_, startLoc ); + } + + massert( 10363 , "newCursor() with start location not implemented for indexed plans", startLoc.isNull() ); + + if ( indexBounds_.size() < 2 ) { + // we are sure to spec endKeyInclusive_ + return auto_ptr< Cursor >( new BtreeCursor( d, idxNo, *index_, indexBounds_[ 0 ].first, indexBounds_[ 0 ].second, endKeyInclusive_, direction_ >= 0 ? 1 : -1 ) ); + } else { + return auto_ptr< Cursor >( new BtreeCursor( d, idxNo, *index_, indexBounds_, direction_ >= 0 ? 1 : -1 ) ); + } + } + + auto_ptr< Cursor > QueryPlan::newReverseCursor() const { + if ( !fbs_.matchPossible() ) + return auto_ptr< Cursor >( new BasicCursor( DiskLoc() ) ); + if ( !index_ ) { + int orderSpec = order_.getIntField( "$natural" ); + if ( orderSpec == INT_MIN ) + orderSpec = 1; + return findTableScan( fbs_.ns(), BSON( "$natural" << -orderSpec ) ); + } + massert( 10364 , "newReverseCursor() not implemented for indexed plans", false ); + return auto_ptr< Cursor >( 0 ); + } + + BSONObj QueryPlan::indexKey() const { + if ( !index_ ) + return BSON( "$natural" << 1 ); + return index_->keyPattern(); + } + + void QueryPlan::registerSelf( long long nScanned ) const { + if ( fbs_.matchPossible() ) { + boostlock lk(NamespaceDetailsTransient::_qcMutex); + NamespaceDetailsTransient::get_inlock( ns() ).registerIndexForPattern( fbs_.pattern( order_ ), indexKey(), nScanned ); + } + } + + QueryPlanSet::QueryPlanSet( const char *_ns, const BSONObj &query, const BSONObj &order, const BSONElement *hint, bool honorRecordedPlan, const BSONObj &min, const BSONObj &max ) : + ns(_ns), + fbs_( _ns, query ), + mayRecordPlan_( true ), + usingPrerecordedPlan_( false ), + hint_( BSONObj() ), + order_( order.getOwned() ), + oldNScanned_( 0 ), + honorRecordedPlan_( honorRecordedPlan ), + min_( min.getOwned() ), + max_( max.getOwned() ) { + if ( hint && !hint->eoo() ) { + BSONObjBuilder b; + b.append( *hint ); + hint_ = b.obj(); + } + init(); + } + + void QueryPlanSet::addHint( IndexDetails &id ) { + if ( !min_.isEmpty() || !max_.isEmpty() ) { + string errmsg; + BSONObj keyPattern = id.keyPattern(); + // This reformats min_ and max_ to be used for index lookup. + massert( 10365 , errmsg, indexDetailsForRange( fbs_.ns(), errmsg, min_, max_, keyPattern ) ); + } + NamespaceDetails *d = nsdetails(ns); + plans_.push_back( PlanPtr( new QueryPlan( d, d->idxNo(id), fbs_, order_, min_, max_ ) ) ); + } + + void QueryPlanSet::init() { + plans_.clear(); + mayRecordPlan_ = true; + usingPrerecordedPlan_ = false; + + const char *ns = fbs_.ns(); + NamespaceDetails *d = nsdetails( ns ); + if ( !d || !fbs_.matchPossible() ) { + // Table scan plan, when no matches are possible + plans_.push_back( PlanPtr( new QueryPlan( d, -1, fbs_, order_ ) ) ); + return; + } + + BSONElement hint = hint_.firstElement(); + if ( !hint.eoo() ) { + mayRecordPlan_ = false; + if( hint.type() == String ) { + string hintstr = hint.valuestr(); + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + IndexDetails& ii = i.next(); + if ( ii.indexName() == hintstr ) { + addHint( ii ); + return; + } + } + } + else if( hint.type() == Object ) { + BSONObj hintobj = hint.embeddedObject(); + uassert( 10112 , "bad hint", !hintobj.isEmpty() ); + if ( !strcmp( hintobj.firstElement().fieldName(), "$natural" ) ) { + massert( 10366 , "natural order cannot be specified with $min/$max", min_.isEmpty() && max_.isEmpty() ); + // Table scan plan + plans_.push_back( PlanPtr( new QueryPlan( d, -1, fbs_, order_ ) ) ); + return; + } + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + IndexDetails& ii = i.next(); + if( ii.keyPattern().woCompare(hintobj) == 0 ) { + addHint( ii ); + return; + } + } + } + uassert( 10113 , "bad hint", false ); + } + + if ( !min_.isEmpty() || !max_.isEmpty() ) { + string errmsg; + BSONObj keyPattern; + IndexDetails *idx = indexDetailsForRange( ns, errmsg, min_, max_, keyPattern ); + massert( 10367 , errmsg, idx ); + plans_.push_back( PlanPtr( new QueryPlan( d, d->idxNo(*idx), fbs_, order_, min_, max_ ) ) ); + return; + } + + if ( honorRecordedPlan_ ) { + boostlock lk(NamespaceDetailsTransient::_qcMutex); + NamespaceDetailsTransient& nsd = NamespaceDetailsTransient::get_inlock( ns ); + BSONObj bestIndex = nsd.indexForPattern( fbs_.pattern( order_ ) ); + if ( !bestIndex.isEmpty() ) { + usingPrerecordedPlan_ = true; + mayRecordPlan_ = false; + oldNScanned_ = nsd.nScannedForPattern( fbs_.pattern( order_ ) ); + if ( !strcmp( bestIndex.firstElement().fieldName(), "$natural" ) ) { + // Table scan plan + plans_.push_back( PlanPtr( new QueryPlan( d, -1, fbs_, order_ ) ) ); + return; + } + + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + int j = i.pos(); + IndexDetails& ii = i.next(); + if( ii.keyPattern().woCompare(bestIndex) == 0 ) { + plans_.push_back( PlanPtr( new QueryPlan( d, j, fbs_, order_ ) ) ); + return; + } + } + massert( 10368 , "Unable to locate previously recorded index", false ); + } + } + + addOtherPlans( false ); + } + + void QueryPlanSet::addOtherPlans( bool checkFirst ) { + const char *ns = fbs_.ns(); + NamespaceDetails *d = nsdetails( ns ); + if ( !d ) + return; + + // If table scan is optimal or natural order requested + if ( !fbs_.matchPossible() || ( fbs_.nNontrivialRanges() == 0 && order_.isEmpty() ) || + ( !order_.isEmpty() && !strcmp( order_.firstElement().fieldName(), "$natural" ) ) ) { + // Table scan plan + addPlan( PlanPtr( new QueryPlan( d, -1, fbs_, order_ ) ), checkFirst ); + return; + } + + PlanSet plans; + for( int i = 0; i < d->nIndexes; ++i ) { + PlanPtr p( new QueryPlan( d, i, fbs_, order_ ) ); + if ( p->optimal() ) { + addPlan( p, checkFirst ); + return; + } else if ( !p->unhelpful() ) { + plans.push_back( p ); + } + } + for( PlanSet::iterator i = plans.begin(); i != plans.end(); ++i ) + addPlan( *i, checkFirst ); + + // Table scan plan + addPlan( PlanPtr( new QueryPlan( d, -1, fbs_, order_ ) ), checkFirst ); + } + + shared_ptr< QueryOp > QueryPlanSet::runOp( QueryOp &op ) { + if ( usingPrerecordedPlan_ ) { + Runner r( *this, op ); + shared_ptr< QueryOp > res = r.run(); + // plans_.size() > 1 if addOtherPlans was called in Runner::run(). + if ( res->complete() || plans_.size() > 1 ) + return res; + { + boostlock lk(NamespaceDetailsTransient::_qcMutex); + NamespaceDetailsTransient::get_inlock( fbs_.ns() ).registerIndexForPattern( fbs_.pattern( order_ ), BSONObj(), 0 ); + } + init(); + } + Runner r( *this, op ); + return r.run(); + } + + BSONObj QueryPlanSet::explain() const { + vector< BSONObj > arr; + for( PlanSet::const_iterator i = plans_.begin(); i != plans_.end(); ++i ) { + auto_ptr< Cursor > c = (*i)->newCursor(); + arr.push_back( BSON( "cursor" << c->toString() << "startKey" << c->prettyStartKey() << "endKey" << c->prettyEndKey() ) ); + } + BSONObjBuilder b; + b.append( "allPlans", arr ); + return b.obj(); + } + + QueryPlanSet::Runner::Runner( QueryPlanSet &plans, QueryOp &op ) : + op_( op ), + plans_( plans ) { + } + + shared_ptr< QueryOp > QueryPlanSet::Runner::run() { + massert( 10369 , "no plans", plans_.plans_.size() > 0 ); + + if ( plans_.plans_.size() > 1 ) + log(1) << " running multiple plans" << endl; + + vector< shared_ptr< QueryOp > > ops; + for( PlanSet::iterator i = plans_.plans_.begin(); i != plans_.plans_.end(); ++i ) { + shared_ptr< QueryOp > op( op_.clone() ); + op->setQueryPlan( i->get() ); + ops.push_back( op ); + } + + for( vector< shared_ptr< QueryOp > >::iterator i = ops.begin(); i != ops.end(); ++i ) { + initOp( **i ); + if ( (*i)->complete() ) + return *i; + } + + long long nScanned = 0; + long long nScannedBackup = 0; + while( 1 ) { + ++nScanned; + unsigned errCount = 0; + bool first = true; + for( vector< shared_ptr< QueryOp > >::iterator i = ops.begin(); i != ops.end(); ++i ) { + QueryOp &op = **i; + nextOp( op ); + if ( op.complete() ) { + if ( first ) + nScanned += nScannedBackup; + if ( plans_.mayRecordPlan_ && op.mayRecordPlan() ) + op.qp().registerSelf( nScanned ); + return *i; + } + if ( op.error() ) + ++errCount; + first = false; + } + if ( errCount == ops.size() ) + break; + if ( plans_.usingPrerecordedPlan_ && nScanned > plans_.oldNScanned_ * 10 ) { + plans_.addOtherPlans( true ); + PlanSet::iterator i = plans_.plans_.begin(); + ++i; + for( ; i != plans_.plans_.end(); ++i ) { + shared_ptr< QueryOp > op( op_.clone() ); + op->setQueryPlan( i->get() ); + ops.push_back( op ); + initOp( *op ); + if ( op->complete() ) + return op; + } + plans_.mayRecordPlan_ = true; + plans_.usingPrerecordedPlan_ = false; + nScannedBackup = nScanned; + nScanned = 0; + } + } + return ops[ 0 ]; + } + + void QueryPlanSet::Runner::initOp( QueryOp &op ) { + try { + op.init(); + } catch ( const std::exception &e ) { + op.setExceptionMessage( e.what() ); + } catch ( ... ) { + op.setExceptionMessage( "Caught unknown exception" ); + } + } + + void QueryPlanSet::Runner::nextOp( QueryOp &op ) { + try { + if ( !op.error() ) + op.next(); + } catch ( const std::exception &e ) { + op.setExceptionMessage( e.what() ); + } catch ( ... ) { + op.setExceptionMessage( "Caught unknown exception" ); + } + } + + bool indexWorks( const BSONObj &idxPattern, const BSONObj &sampleKey, int direction, int firstSignificantField ) { + BSONObjIterator p( idxPattern ); + BSONObjIterator k( sampleKey ); + int i = 0; + while( 1 ) { + BSONElement pe = p.next(); + BSONElement ke = k.next(); + if ( pe.eoo() && ke.eoo() ) + return true; + if ( pe.eoo() || ke.eoo() ) + return false; + if ( strcmp( pe.fieldName(), ke.fieldName() ) != 0 ) + return false; + if ( ( i == firstSignificantField ) && !( ( direction > 0 ) == ( pe.number() > 0 ) ) ) + return false; + ++i; + } + return false; + } + + BSONObj extremeKeyForIndex( const BSONObj &idxPattern, int baseDirection ) { + BSONObjIterator i( idxPattern ); + BSONObjBuilder b; + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + int idxDirection = e.number() >= 0 ? 1 : -1; + int direction = idxDirection * baseDirection; + switch( direction ) { + case 1: + b.appendMaxKey( e.fieldName() ); + break; + case -1: + b.appendMinKey( e.fieldName() ); + break; + default: + assert( false ); + } + } + return b.obj(); + } + + pair< int, int > keyAudit( const BSONObj &min, const BSONObj &max ) { + int direction = 0; + int firstSignificantField = 0; + BSONObjIterator i( min ); + BSONObjIterator a( max ); + while( 1 ) { + BSONElement ie = i.next(); + BSONElement ae = a.next(); + if ( ie.eoo() && ae.eoo() ) + break; + if ( ie.eoo() || ae.eoo() || strcmp( ie.fieldName(), ae.fieldName() ) != 0 ) { + return make_pair( -1, -1 ); + } + int cmp = ie.woCompare( ae ); + if ( cmp < 0 ) + direction = 1; + if ( cmp > 0 ) + direction = -1; + if ( direction != 0 ) + break; + ++firstSignificantField; + } + return make_pair( direction, firstSignificantField ); + } + + pair< int, int > flexibleKeyAudit( const BSONObj &min, const BSONObj &max ) { + if ( min.isEmpty() || max.isEmpty() ) { + return make_pair( 1, -1 ); + } else { + return keyAudit( min, max ); + } + } + + // NOTE min, max, and keyPattern will be updated to be consistent with the selected index. + IndexDetails *indexDetailsForRange( const char *ns, string &errmsg, BSONObj &min, BSONObj &max, BSONObj &keyPattern ) { + if ( min.isEmpty() && max.isEmpty() ) { + errmsg = "one of min or max must be specified"; + return 0; + } + + setClient( ns ); + IndexDetails *id = 0; + NamespaceDetails *d = nsdetails( ns ); + if ( !d ) { + errmsg = "ns not found"; + return 0; + } + + pair< int, int > ret = flexibleKeyAudit( min, max ); + if ( ret == make_pair( -1, -1 ) ) { + errmsg = "min and max keys do not share pattern"; + return 0; + } + if ( keyPattern.isEmpty() ) { + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + IndexDetails& ii = i.next(); + if ( indexWorks( ii.keyPattern(), min.isEmpty() ? max : min, ret.first, ret.second ) ) { + id = ⅈ + keyPattern = ii.keyPattern(); + break; + } + } + + } else { + if ( !indexWorks( keyPattern, min.isEmpty() ? max : min, ret.first, ret.second ) ) { + errmsg = "requested keyPattern does not match specified keys"; + return 0; + } + NamespaceDetails::IndexIterator i = d->ii(); + while( i.more() ) { + IndexDetails& ii = i.next(); + if( ii.keyPattern().woCompare(keyPattern) == 0 ) { + id = ⅈ + break; + } + if ( keyPattern.nFields() == 1 && ii.keyPattern().nFields() == 1 && + IndexDetails::isIdIndexPattern( keyPattern ) && + ii.isIdIndex() ){ + id = ⅈ + break; + } + + } + } + + if ( min.isEmpty() ) { + min = extremeKeyForIndex( keyPattern, -1 ); + } else if ( max.isEmpty() ) { + max = extremeKeyForIndex( keyPattern, 1 ); + } + + if ( !id ) { + errmsg = (string)"no index found for specified keyPattern: " + keyPattern.toString(); + return 0; + } + + min = min.extractFieldsUnDotted( keyPattern ); + max = max.extractFieldsUnDotted( keyPattern ); + + return id; + } + +} // namespace mongo diff --git a/db/queryoptimizer.h b/db/queryoptimizer.h new file mode 100644 index 0000000..e4a79d8 --- /dev/null +++ b/db/queryoptimizer.h @@ -0,0 +1,161 @@ +/* queryoptimizer.h */ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "cursor.h" +#include "jsobj.h" +#include "queryutil.h" + +namespace mongo { + + class IndexDetails; + class QueryPlan : boost::noncopyable { + public: + QueryPlan(NamespaceDetails *_d, + int _idxNo, // -1 = no index + const FieldRangeSet &fbs, + const BSONObj &order, + const BSONObj &startKey = BSONObj(), + const BSONObj &endKey = BSONObj() ); + + /* If true, no other index can do better. */ + bool optimal() const { return optimal_; } + /* ScanAndOrder processing will be required if true */ + bool scanAndOrderRequired() const { return scanAndOrderRequired_; } + /* When true, the index we are using has keys such that it can completely resolve the + query expression to match by itself without ever checking the main object. + */ + bool exactKeyMatch() const { return exactKeyMatch_; } + /* If true, the startKey and endKey are unhelpful and the index order doesn't match the + requested sort order */ + bool unhelpful() const { return unhelpful_; } + int direction() const { return direction_; } + auto_ptr< Cursor > newCursor( const DiskLoc &startLoc = DiskLoc() ) const; + auto_ptr< Cursor > newReverseCursor() const; + BSONObj indexKey() const; + const char *ns() const { return fbs_.ns(); } + BSONObj query() const { return fbs_.query(); } + BSONObj simplifiedQuery( const BSONObj& fields = BSONObj() ) const { return fbs_.simplifiedQuery( fields ); } + const FieldRange &range( const char *fieldName ) const { return fbs_.range( fieldName ); } + void registerSelf( long long nScanned ) const; + // just for testing + BoundList indexBounds() const { return indexBounds_; } + private: + NamespaceDetails *d; + int idxNo; + const FieldRangeSet &fbs_; + const BSONObj &order_; + const IndexDetails *index_; + bool optimal_; + bool scanAndOrderRequired_; + bool exactKeyMatch_; + int direction_; + BoundList indexBounds_; + bool endKeyInclusive_; + bool unhelpful_; + }; + + // Inherit from this interface to implement a new query operation. + // The query optimizer will clone the QueryOp that is provided, giving + // each clone its own query plan. + class QueryOp { + public: + QueryOp() : complete_(), qp_(), error_() {} + virtual ~QueryOp() {} + virtual void init() = 0; + virtual void next() = 0; + virtual bool mayRecordPlan() const = 0; + // Return a copy of the inheriting class, which will be run with its own + // query plan. + virtual QueryOp *clone() const = 0; + bool complete() const { return complete_; } + bool error() const { return error_; } + string exceptionMessage() const { return exceptionMessage_; } + const QueryPlan &qp() const { return *qp_; } + // To be called by QueryPlanSet::Runner only. + void setQueryPlan( const QueryPlan *qp ) { qp_ = qp; } + void setExceptionMessage( const string &exceptionMessage ) { + error_ = true; + exceptionMessage_ = exceptionMessage; + } + protected: + void setComplete() { complete_ = true; } + private: + bool complete_; + string exceptionMessage_; + const QueryPlan *qp_; + bool error_; + }; + + // Set of candidate query plans for a particular query. Used for running + // a QueryOp on these plans. + class QueryPlanSet { + public: + QueryPlanSet( const char *ns, + const BSONObj &query, + const BSONObj &order, + const BSONElement *hint = 0, + bool honorRecordedPlan = true, + const BSONObj &min = BSONObj(), + const BSONObj &max = BSONObj() ); + int nPlans() const { return plans_.size(); } + shared_ptr< QueryOp > runOp( QueryOp &op ); + template< class T > + shared_ptr< T > runOp( T &op ) { + return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp& >( op ) ) ); + } + const FieldRangeSet &fbs() const { return fbs_; } + BSONObj explain() const; + bool usingPrerecordedPlan() const { return usingPrerecordedPlan_; } + private: + void addOtherPlans( bool checkFirst ); + typedef boost::shared_ptr< QueryPlan > PlanPtr; + typedef vector< PlanPtr > PlanSet; + void addPlan( PlanPtr plan, bool checkFirst ) { + if ( checkFirst && plan->indexKey().woCompare( plans_[ 0 ]->indexKey() ) == 0 ) + return; + plans_.push_back( plan ); + } + void init(); + void addHint( IndexDetails &id ); + struct Runner { + Runner( QueryPlanSet &plans, QueryOp &op ); + shared_ptr< QueryOp > run(); + QueryOp &op_; + QueryPlanSet &plans_; + static void initOp( QueryOp &op ); + static void nextOp( QueryOp &op ); + }; + const char *ns; + FieldRangeSet fbs_; + PlanSet plans_; + bool mayRecordPlan_; + bool usingPrerecordedPlan_; + BSONObj hint_; + BSONObj order_; + long long oldNScanned_; + bool honorRecordedPlan_; + BSONObj min_; + BSONObj max_; + }; + + // NOTE min, max, and keyPattern will be updated to be consistent with the selected index. + IndexDetails *indexDetailsForRange( const char *ns, string &errmsg, BSONObj &min, BSONObj &max, BSONObj &keyPattern ); + +} // namespace mongo diff --git a/db/queryutil.cpp b/db/queryutil.cpp new file mode 100644 index 0000000..d8854be --- /dev/null +++ b/db/queryutil.cpp @@ -0,0 +1,594 @@ +// queryutil.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" + +#include "btree.h" +#include "matcher.h" +#include "pdfile.h" +#include "queryoptimizer.h" +#include "../util/unittest.h" + +namespace mongo { + namespace { + /** returns a string that when used as a matcher, would match a super set of regex() + returns "" for complex regular expressions + used to optimize queries in some simple regex cases that start with '^' + */ + inline string simpleRegexHelper(const char* regex, const char* flags){ + string r = ""; + + bool extended = false; + while (*flags){ + switch (*(flags++)){ + case 'm': // multiline + continue; + case 'x': // extended + extended = true; + break; + default: + return r; // cant use index + } + } + + if ( *(regex++) != '^' ) + return r; + + stringstream ss; + + while(*regex){ + char c = *(regex++); + if ( c == '*' || c == '?' ){ + // These are the only two symbols that make the last char optional + r = ss.str(); + r = r.substr( 0 , r.size() - 1 ); + return r; //breaking here fails with /^a?/ + } else if (c == '\\'){ + // slash followed by non-alphanumeric represents the following char + c = *(regex++); + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '0') || + (c == '\0')) + { + r = ss.str(); + break; + } else { + ss << c; + } + } else if (strchr("^$.[|()+{", c)){ + // list of "metacharacters" from man pcrepattern + r = ss.str(); + break; + } else if (extended && c == '#'){ + // comment + r = ss.str(); + break; + } else if (extended && isspace(c)){ + continue; + } else { + // self-matching char + ss << c; + } + } + + if ( r.size() == 0 && *regex == 0 ) + r = ss.str(); + + return r; + } + inline string simpleRegex(const BSONElement& e){ + switch(e.type()){ + case RegEx: + return simpleRegexHelper(e.regex(), e.regexFlags()); + case Object:{ + BSONObj o = e.embeddedObject(); + return simpleRegexHelper(o["$regex"].valuestrsafe(), o["$options"].valuestrsafe()); + } + default: assert(false); return ""; //return squashes compiler warning + } + } + } + + FieldRange::FieldRange( const BSONElement &e, bool optimize ) { + if ( !e.eoo() && e.type() != RegEx && e.getGtLtOp() == BSONObj::opIN ) { + set< BSONElement, element_lt > vals; + BSONObjIterator i( e.embeddedObject() ); + while( i.more() ) + vals.insert( i.next() ); + + for( set< BSONElement, element_lt >::const_iterator i = vals.begin(); i != vals.end(); ++i ) + intervals_.push_back( FieldInterval(*i) ); + + return; + } + + if ( e.type() == Array && e.getGtLtOp() == BSONObj::Equality ){ + + intervals_.push_back( FieldInterval(e) ); + + const BSONElement& temp = e.embeddedObject().firstElement(); + if ( ! temp.eoo() ){ + if ( temp < e ) + intervals_.insert( intervals_.begin() , temp ); + else + intervals_.push_back( FieldInterval(temp) ); + } + + return; + } + + intervals_.push_back( FieldInterval() ); + FieldInterval &initial = intervals_[ 0 ]; + BSONElement &lower = initial.lower_.bound_; + bool &lowerInclusive = initial.lower_.inclusive_; + BSONElement &upper = initial.upper_.bound_; + bool &upperInclusive = initial.upper_.inclusive_; + lower = minKey.firstElement(); + lowerInclusive = true; + upper = maxKey.firstElement(); + upperInclusive = true; + + if ( e.eoo() ) + return; + if ( e.type() == RegEx + || (e.type() == Object && !e.embeddedObject()["$regex"].eoo()) + ) + { + const string r = simpleRegex(e); + if ( r.size() ) { + lower = addObj( BSON( "" << r ) ).firstElement(); + upper = addObj( BSON( "" << simpleRegexEnd( r ) ) ).firstElement(); + upperInclusive = false; + } + return; + } + switch( e.getGtLtOp() ) { + case BSONObj::Equality: + lower = upper = e; + break; + case BSONObj::LT: + upperInclusive = false; + case BSONObj::LTE: + upper = e; + break; + case BSONObj::GT: + lowerInclusive = false; + case BSONObj::GTE: + lower = e; + break; + case BSONObj::opALL: { + massert( 10370 , "$all requires array", e.type() == Array ); + BSONObjIterator i( e.embeddedObject() ); + if ( i.more() ) + lower = upper = i.next(); + break; + } + case BSONObj::opMOD: { + { + BSONObjBuilder b; + b.appendMinForType( "" , NumberDouble ); + lower = addObj( b.obj() ).firstElement(); + } + { + BSONObjBuilder b; + b.appendMaxForType( "" , NumberDouble ); + upper = addObj( b.obj() ).firstElement(); + } + break; + } + case BSONObj::opTYPE: { + BSONType t = (BSONType)e.numberInt(); + { + BSONObjBuilder b; + b.appendMinForType( "" , t ); + lower = addObj( b.obj() ).firstElement(); + } + { + BSONObjBuilder b; + b.appendMaxForType( "" , t ); + upper = addObj( b.obj() ).firstElement(); + } + + break; + } + case BSONObj::opELEM_MATCH: { + log() << "warning: shouldn't get here?" << endl; + break; + } + default: + break; + } + + if ( optimize ){ + if ( lower.type() != MinKey && upper.type() == MaxKey && lower.isSimpleType() ){ // TODO: get rid of isSimpleType + BSONObjBuilder b; + b.appendMaxForType( lower.fieldName() , lower.type() ); + upper = addObj( b.obj() ).firstElement(); + } + else if ( lower.type() == MinKey && upper.type() != MaxKey && upper.isSimpleType() ){ // TODO: get rid of isSimpleType + BSONObjBuilder b; + b.appendMinForType( upper.fieldName() , upper.type() ); + lower = addObj( b.obj() ).firstElement(); + } + } + + } + + // as called, these functions find the max/min of a bound in the + // opposite direction, so inclusive bounds are considered less + // superlative + FieldBound maxFieldBound( const FieldBound &a, const FieldBound &b ) { + int cmp = a.bound_.woCompare( b.bound_, false ); + if ( ( cmp == 0 && !b.inclusive_ ) || cmp < 0 ) + return b; + return a; + } + + FieldBound minFieldBound( const FieldBound &a, const FieldBound &b ) { + int cmp = a.bound_.woCompare( b.bound_, false ); + if ( ( cmp == 0 && !b.inclusive_ ) || cmp > 0 ) + return b; + return a; + } + + bool fieldIntervalOverlap( const FieldInterval &one, const FieldInterval &two, FieldInterval &result ) { + result.lower_ = maxFieldBound( one.lower_, two.lower_ ); + result.upper_ = minFieldBound( one.upper_, two.upper_ ); + return result.valid(); + } + + // NOTE Not yet tested for complex $or bounds, just for simple bounds generated by $in + const FieldRange &FieldRange::operator&=( const FieldRange &other ) { + vector< FieldInterval > newIntervals; + vector< FieldInterval >::const_iterator i = intervals_.begin(); + vector< FieldInterval >::const_iterator j = other.intervals_.begin(); + while( i != intervals_.end() && j != other.intervals_.end() ) { + FieldInterval overlap; + if ( fieldIntervalOverlap( *i, *j, overlap ) ) + newIntervals.push_back( overlap ); + if ( i->upper_ == minFieldBound( i->upper_, j->upper_ ) ) + ++i; + else + ++j; + } + intervals_ = newIntervals; + for( vector< BSONObj >::const_iterator i = other.objData_.begin(); i != other.objData_.end(); ++i ) + objData_.push_back( *i ); + return *this; + } + + string FieldRange::simpleRegexEnd( string regex ) { + ++regex[ regex.length() - 1 ]; + return regex; + } + + BSONObj FieldRange::addObj( const BSONObj &o ) { + objData_.push_back( o ); + return o; + } + + FieldRangeSet::FieldRangeSet( const char *ns, const BSONObj &query , bool optimize ) + : ns_( ns ), query_( query.getOwned() ) { + BSONObjIterator i( query_ ); + + while( i.more() ) { + BSONElement e = i.next(); + // e could be x:1 or x:{$gt:1} + + if ( strcmp( e.fieldName(), "$where" ) == 0 ) + continue; + + int op = getGtLtOp( e ); + + if ( op == BSONObj::Equality || op == BSONObj::opREGEX || op == BSONObj::opOPTIONS ) { + ranges_[ e.fieldName() ] &= FieldRange( e , optimize ); + } + else if ( op == BSONObj::opELEM_MATCH ){ + BSONObjIterator i( e.embeddedObjectUserCheck().firstElement().embeddedObjectUserCheck() ); + while ( i.more() ){ + BSONElement f = i.next(); + StringBuilder buf(32); + buf << e.fieldName() << "." << f.fieldName(); + string fullname = buf.str(); + + int op2 = getGtLtOp( f ); + if ( op2 == BSONObj::Equality ){ + ranges_[ fullname ] &= FieldRange( f , optimize ); + } + else { + BSONObjIterator j( f.embeddedObject() ); + while ( j.more() ){ + ranges_[ fullname ] &= FieldRange( j.next() , optimize ); + } + } + } + } + else { + BSONObjIterator i( e.embeddedObject() ); + while( i.more() ) { + BSONElement f = i.next(); + ranges_[ e.fieldName() ] &= FieldRange( f , optimize ); + } + } + } + } + + FieldRange *FieldRangeSet::trivialRange_ = 0; + FieldRange &FieldRangeSet::trivialRange() { + if ( trivialRange_ == 0 ) + trivialRange_ = new FieldRange(); + return *trivialRange_; + } + + BSONObj FieldRangeSet::simplifiedQuery( const BSONObj &_fields ) const { + BSONObj fields = _fields; + if ( fields.isEmpty() ) { + BSONObjBuilder b; + for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) { + b.append( i->first.c_str(), 1 ); + } + fields = b.obj(); + } + BSONObjBuilder b; + BSONObjIterator i( fields ); + while( i.more() ) { + BSONElement e = i.next(); + const char *name = e.fieldName(); + const FieldRange &range = ranges_[ name ]; + assert( !range.empty() ); + if ( range.equality() ) + b.appendAs( range.min(), name ); + else if ( range.nontrivial() ) { + BSONObjBuilder c; + if ( range.min().type() != MinKey ) + c.appendAs( range.min(), range.minInclusive() ? "$gte" : "$gt" ); + if ( range.max().type() != MaxKey ) + c.appendAs( range.max(), range.maxInclusive() ? "$lte" : "$lt" ); + b.append( name, c.done() ); + } + } + return b.obj(); + } + + QueryPattern FieldRangeSet::pattern( const BSONObj &sort ) const { + QueryPattern qp; + for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) { + assert( !i->second.empty() ); + if ( i->second.equality() ) { + qp.fieldTypes_[ i->first ] = QueryPattern::Equality; + } else if ( i->second.nontrivial() ) { + bool upper = i->second.max().type() != MaxKey; + bool lower = i->second.min().type() != MinKey; + if ( upper && lower ) + qp.fieldTypes_[ i->first ] = QueryPattern::UpperAndLowerBound; + else if ( upper ) + qp.fieldTypes_[ i->first ] = QueryPattern::UpperBound; + else if ( lower ) + qp.fieldTypes_[ i->first ] = QueryPattern::LowerBound; + } + } + qp.setSort( sort ); + return qp; + } + + BoundList FieldRangeSet::indexBounds( const BSONObj &keyPattern, int direction ) const { + BSONObjBuilder equalityBuilder; + typedef vector< pair< shared_ptr< BSONObjBuilder >, shared_ptr< BSONObjBuilder > > > BoundBuilders; + BoundBuilders builders; + BSONObjIterator i( keyPattern ); + while( i.more() ) { + BSONElement e = i.next(); + const FieldRange &fr = range( e.fieldName() ); + int number = (int) e.number(); // returns 0.0 if not numeric + bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction >= 0 ? 1 : -1 ) > 0 ); + if ( builders.empty() ) { + if ( fr.equality() ) { + equalityBuilder.appendAs( fr.min(), "" ); + } else { + BSONObj equalityObj = equalityBuilder.done(); + const vector< FieldInterval > &intervals = fr.intervals(); + if ( forward ) { + for( vector< FieldInterval >::const_iterator j = intervals.begin(); j != intervals.end(); ++j ) { + builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); + builders.back().first->appendElements( equalityObj ); + builders.back().second->appendElements( equalityObj ); + builders.back().first->appendAs( j->lower_.bound_, "" ); + builders.back().second->appendAs( j->upper_.bound_, "" ); + } + } else { + for( vector< FieldInterval >::const_reverse_iterator j = intervals.rbegin(); j != intervals.rend(); ++j ) { + builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); + builders.back().first->appendElements( equalityObj ); + builders.back().second->appendElements( equalityObj ); + builders.back().first->appendAs( j->upper_.bound_, "" ); + builders.back().second->appendAs( j->lower_.bound_, "" ); + } + } + } + } else { + for( BoundBuilders::const_iterator j = builders.begin(); j != builders.end(); ++j ) { + j->first->appendAs( forward ? fr.min() : fr.max(), "" ); + j->second->appendAs( forward ? fr.max() : fr.min(), "" ); + } + } + } + if ( builders.empty() ) { + BSONObj equalityObj = equalityBuilder.done(); + assert( !equalityObj.isEmpty() ); + builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); + builders.back().first->appendElements( equalityObj ); + builders.back().second->appendElements( equalityObj ); + } + BoundList ret; + for( BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i ) + ret.push_back( make_pair( i->first->obj(), i->second->obj() ) ); + return ret; + } + + /////////////////// + // FieldMatcher // + /////////////////// + + void FieldMatcher::add( const BSONObj& o ){ + massert( 10371 , "can only add to FieldMatcher once", source_.isEmpty()); + source_ = o; + + BSONObjIterator i( o ); + int true_false = -1; + while ( i.more() ){ + BSONElement e = i.next(); + add (e.fieldName(), e.trueValue()); + + // validate input + if (true_false == -1){ + true_false = e.trueValue(); + include_ = !e.trueValue(); + }else{ + if((bool) true_false != e.trueValue()) + errmsg = "You cannot currently mix including and excluding fields. Contact us if this is an issue."; + } + } + } + + void FieldMatcher::add(const string& field, bool include){ + if (field.empty()){ // this is the field the user referred to + include_ = include; + } else { + const size_t dot = field.find('.'); + const string subfield = field.substr(0,dot); + const string rest = (dot == string::npos ? "" : field.substr(dot+1,string::npos)); + + boost::shared_ptr<FieldMatcher>& fm = fields_[subfield]; + if (!fm) + fm.reset(new FieldMatcher(!include)); + + fm->add(rest, include); + } + } + + BSONObj FieldMatcher::getSpec() const{ + return source_; + } + + //b will be the value part of an array-typed BSONElement + void FieldMatcher::appendArray( BSONObjBuilder& b , const BSONObj& a ) const { + int i=0; + BSONObjIterator it(a); + while (it.more()){ + BSONElement e = it.next(); + + switch(e.type()){ + case Array:{ + BSONObjBuilder subb; + appendArray(subb , e.embeddedObject()); + b.appendArray(b.numStr(i++).c_str(), subb.obj()); + break; + } + case Object:{ + BSONObjBuilder subb; + BSONObjIterator jt(e.embeddedObject()); + while (jt.more()){ + append(subb , jt.next()); + } + b.append(b.numStr(i++), subb.obj()); + break; + } + default: + if (include_) + b.appendAs(e, b.numStr(i++).c_str()); + } + + + } + } + + void FieldMatcher::append( BSONObjBuilder& b , const BSONElement& e ) const { + FieldMap::const_iterator field = fields_.find( e.fieldName() ); + + if (field == fields_.end()){ + if (include_) + b.append(e); + } else { + FieldMatcher& subfm = *field->second; + + if (subfm.fields_.empty() || !(e.type()==Object || e.type()==Array) ){ + if (subfm.include_) + b.append(e); + } else if (e.type() == Object){ + BSONObjBuilder subb; + BSONObjIterator it(e.embeddedObject()); + while (it.more()){ + subfm.append(subb, it.next()); + } + b.append(e.fieldName(), subb.obj()); + + } else { //Array + BSONObjBuilder subb; + subfm.appendArray(subb, e.embeddedObject()); + b.appendArray(e.fieldName(), subb.obj()); + } + } + } + + struct SimpleRegexUnitTest : UnitTest { + void run(){ + { + BSONObjBuilder b; + b.appendRegex("r", "^foo"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "foo" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^f?oo"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^fz?oo"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "f" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^f", ""); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "f" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^f", "m"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "f" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^f", "mi"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "" ); + } + { + BSONObjBuilder b; + b.appendRegex("r", "^f \t\vo\n\ro \\ \\# #comment", "mx"); + BSONObj o = b.done(); + assert( simpleRegex(o.firstElement()) == "foo #" ); + } + } + } simple_regex_unittest; +} // namespace mongo diff --git a/db/queryutil.h b/db/queryutil.h new file mode 100644 index 0000000..2122a7f --- /dev/null +++ b/db/queryutil.h @@ -0,0 +1,210 @@ +// queryutil.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "jsobj.h" + +namespace mongo { + + struct FieldBound { + BSONElement bound_; + bool inclusive_; + bool operator==( const FieldBound &other ) const { + return bound_.woCompare( other.bound_ ) == 0 && + inclusive_ == other.inclusive_; + } + }; + + struct FieldInterval { + FieldInterval(){} + FieldInterval( const BSONElement& e ){ + lower_.bound_ = upper_.bound_ = e; + lower_.inclusive_ = upper_.inclusive_ = true; + } + FieldBound lower_; + FieldBound upper_; + bool valid() const { + int cmp = lower_.bound_.woCompare( upper_.bound_, false ); + return ( cmp < 0 || ( cmp == 0 && lower_.inclusive_ && upper_.inclusive_ ) ); + } + }; + + // range of a field's value that may be determined from query -- used to + // determine index limits + class FieldRange { + public: + FieldRange( const BSONElement &e = BSONObj().firstElement() , bool optimize=true ); + const FieldRange &operator&=( const FieldRange &other ); + BSONElement min() const { assert( !empty() ); return intervals_[ 0 ].lower_.bound_; } + BSONElement max() const { assert( !empty() ); return intervals_[ intervals_.size() - 1 ].upper_.bound_; } + bool minInclusive() const { assert( !empty() ); return intervals_[ 0 ].lower_.inclusive_; } + bool maxInclusive() const { assert( !empty() ); return intervals_[ intervals_.size() - 1 ].upper_.inclusive_; } + bool equality() const { + return + !empty() && + min().woCompare( max(), false ) == 0 && + maxInclusive() && + minInclusive(); + } + bool nontrivial() const { + return + ! empty() && + ( minKey.firstElement().woCompare( min(), false ) != 0 || + maxKey.firstElement().woCompare( max(), false ) != 0 ); + } + bool empty() const { return intervals_.empty(); } + const vector< FieldInterval > &intervals() const { return intervals_; } + private: + BSONObj addObj( const BSONObj &o ); + string simpleRegexEnd( string regex ); + vector< FieldInterval > intervals_; + vector< BSONObj > objData_; + }; + + // implements query pattern matching, used to determine if a query is + // similar to an earlier query and should use the same plan + class QueryPattern { + public: + friend class FieldRangeSet; + enum Type { + Equality, + LowerBound, + UpperBound, + UpperAndLowerBound + }; + // for testing only, speed unimportant + bool operator==( const QueryPattern &other ) const { + bool less = operator<( other ); + bool more = other.operator<( *this ); + assert( !( less && more ) ); + return !( less || more ); + } + bool operator!=( const QueryPattern &other ) const { + return !operator==( other ); + } + bool operator<( const QueryPattern &other ) const { + map< string, Type >::const_iterator i = fieldTypes_.begin(); + map< string, Type >::const_iterator j = other.fieldTypes_.begin(); + while( i != fieldTypes_.end() ) { + if ( j == other.fieldTypes_.end() ) + return false; + if ( i->first < j->first ) + return true; + else if ( i->first > j->first ) + return false; + if ( i->second < j->second ) + return true; + else if ( i->second > j->second ) + return false; + ++i; + ++j; + } + if ( j != other.fieldTypes_.end() ) + return true; + return sort_.woCompare( other.sort_ ) < 0; + } + private: + QueryPattern() {} + void setSort( const BSONObj sort ) { + sort_ = normalizeSort( sort ); + } + BSONObj static normalizeSort( const BSONObj &spec ) { + if ( spec.isEmpty() ) + return spec; + int direction = ( spec.firstElement().number() >= 0 ) ? 1 : -1; + BSONObjIterator i( spec ); + BSONObjBuilder b; + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + b.append( e.fieldName(), direction * ( ( e.number() >= 0 ) ? -1 : 1 ) ); + } + return b.obj(); + } + map< string, Type > fieldTypes_; + BSONObj sort_; + }; + + // ranges of fields' value that may be determined from query -- used to + // determine index limits + class FieldRangeSet { + public: + FieldRangeSet( const char *ns, const BSONObj &query , bool optimize=true ); + const FieldRange &range( const char *fieldName ) const { + map< string, FieldRange >::const_iterator f = ranges_.find( fieldName ); + if ( f == ranges_.end() ) + return trivialRange(); + return f->second; + } + int nNontrivialRanges() const { + int count = 0; + for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) + if ( i->second.nontrivial() ) + ++count; + return count; + } + const char *ns() const { return ns_; } + BSONObj query() const { return query_; } + // if fields is specified, order fields of returned object to match those of 'fields' + BSONObj simplifiedQuery( const BSONObj &fields = BSONObj() ) const; + bool matchPossible() const { + for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) + if ( i->second.empty() ) + return false; + return true; + } + QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; + BoundList indexBounds( const BSONObj &keyPattern, int direction ) const; + private: + static FieldRange *trivialRange_; + static FieldRange &trivialRange(); + mutable map< string, FieldRange > ranges_; + const char *ns_; + BSONObj query_; + }; + + /** + used for doing field limiting + */ + class FieldMatcher { + public: + + FieldMatcher(bool include=false) : errmsg(NULL), include_(include) {} + + void add( const BSONObj& o ); + + void append( BSONObjBuilder& b , const BSONElement& e ) const; + + BSONObj getSpec() const; + + const char* errmsg; //null if FieldMatcher is valid + private: + + void add( const string& field, bool include ); + void appendArray( BSONObjBuilder& b , const BSONObj& a ) const; + + bool include_; // true if default at this level is to include + //TODO: benchmark vector<pair> vs map + typedef map<string, boost::shared_ptr<FieldMatcher> > FieldMap; + FieldMap fields_; + BSONObj source_; + }; + + +} // namespace mongo diff --git a/db/rec.h b/db/rec.h new file mode 100644 index 0000000..b749dd8 --- /dev/null +++ b/db/rec.h @@ -0,0 +1,119 @@ +// rec.h + +/* TODO for _RECSTORE + + _ support > 2GB data per file + _ multiple files, not just indexes.dat + _ lazier writes? (may be done?) + _ configurable cache size + _ fix on abnormal terminations to be able to restart some +*/ + +#pragma once + +#include "reci.h" +#include "reccache.h" + +namespace mongo { + +/* -------------------------------------------------------------------------- + A RecStoreInterface for the normal mongo mem mapped file (MongoDataFile) + storage +*/ + +NamespaceDetails* nsdetails_notinline(const char *ns); + +class MongoMemMapped_RecStore : public RecStoreInterface { +public: + virtual char* get(DiskLoc d, unsigned len) { return d.rec()->data; } + + virtual DiskLoc insert(const char *ns, const void *obuf, int len, bool god) { + return theDataFileMgr.insert(ns, obuf, len, god); + } + + virtual void deleteRecord(const char *ns, DiskLoc d) { + theDataFileMgr._deleteRecord(nsdetails_notinline(ns), ns, d.rec(), d); + } + + virtual void modified(DiskLoc d) { } + + virtual void drop(const char *ns) { + dropNS(ns); + } + + virtual void rename(const char *fromNs, const char *toNs) { + renameNamespace( fromNs, toNs ); + } + + /* close datafiles associated with the db specified. */ + virtual void closeFiles(string dbname, string path) { + /* as this is only used for indexes so far, and we are in the same + PDFiles as the nonindex data, we just rely on them having been closed + at the same time. one day this may need to change. + */ + } + +}; + +/* An in memory RecStoreInterface implementation ---------------------------- +*/ + +#if 0 +class InMem_RecStore : public RecStoreInterface { + enum InmemfileValue { INMEMFILE = 0x70000000 }; +public: + static char* get(DiskLoc d, unsigned len) { + assert( d.a() == INMEMFILE ); +#ifdef __LP64__ + massert( 10372 , "64 bit not done", false); + return 0; +#else + return (char *) d.getOfs(); +#endif + } + + static DiskLoc insert(const char *ns, const void *obuf, int len, bool god) { +#ifdef __LP64__ + assert( 0 ); + throw -1; +#else + char *p = (char *) malloc(len); + assert( p ); + memcpy(p, obuf, len); + int b = (int) p; + assert( b > 0 ); + return DiskLoc(INMEMFILE, b); +#endif + } + + static void modified(DiskLoc d) { } + + static void drop(const char *ns) { + log() << "warning: drop() not yet implemented for InMem_RecStore" << endl; + } + + virtual void rename(const char *fromNs, const char *toNs) { + massert( 10373 , "rename not yet implemented for InMem_RecStore", false ); + } +}; +#endif + +/* Glue btree to RecStoreInterface: ---------------------------- */ + +extern RecStoreInterface *btreeStore; + +const int BucketSize = 8192; + +inline BtreeBucket* DiskLoc::btree() const { + assert( fileNo != -1 ); + return (BtreeBucket*) btreeStore->get(*this, BucketSize); +} + +inline BtreeBucket* DiskLoc::btreemod() const { + assert( fileNo != -1 ); + BtreeBucket *b = (BtreeBucket*) btreeStore->get(*this, BucketSize); + btreeStore->modified(*this); + return b; +} + +} diff --git a/db/reccache.cpp b/db/reccache.cpp new file mode 100644 index 0000000..66dd4e3 --- /dev/null +++ b/db/reccache.cpp @@ -0,0 +1,401 @@ +// storage.cpp
+
+#include "stdafx.h"
+#include "pdfile.h"
+#include "reccache.h"
+#include "rec.h"
+#include "db.h"
+
+namespace mongo {
+
+RecCache theRecCache(BucketSize);
+
+// 100k * 8KB = 800MB
+unsigned RecCache::MAXNODES = 50000;
+
+void setRecCacheSize(unsigned mb) {
+ unsigned long long MB = mb;
+ log(2) << "reccache size: " << MB << "MB\n";
+ uassert( 10114 , "bad cache size", MB > 0 && MB < 1000000 );
+ RecCache::MAXNODES = (unsigned) MB * 1024 * 1024 / 8192;
+ log(3) << "RecCache::MAXNODES=" << RecCache::MAXNODES << '\n';
+}
+
+void writerThread() {
+ sleepsecs(10);
+ while( 1 ) {
+ try {
+ theRecCache.writeLazily();
+ }
+ catch(...) {
+ log() << "exception in writerThread()" << endl;
+ sleepsecs(3);
+ }
+ }
+}
+
+// called on program exit. +void recCacheCloseAll() {
+#if defined(_RECSTORE)
+ theRecCache.closing();
+#endif
+}
+
+int ndirtywritten;
+
+inline static string escape(const char *ns) {
+ char buf[256];
+ char *p = buf;
+ while( 1 ) {
+ if( *ns == '$' ) *p = '~';
+ else
+ *p = *ns;
+ if( *ns == 0 )
+ break;
+ p++; ns++;
+ }
+ assert( p - buf < (int) sizeof(buf) );
+ return buf;
+}
+
+inline static string unescape(const char *ns) {
+ char buf[256];
+ char *p = buf;
+ while( 1 ) {
+ if( *ns == '~' ) *p = '$';
+ else
+ *p = *ns;
+ if( *ns == 0 )
+ break;
+ p++; ns++;
+ }
+ assert( p - buf < (int) sizeof(buf) );
+ return buf;
+}
+
+string RecCache::directory() {
+ return cc().database()->path;
+}
+
+/* filename format is
+
+ <n>-<ns>.idx
+*/
+
+BasicRecStore* RecCache::_initStore(string fname) {
+
+ assert( strchr(fname.c_str(), '/') == 0 );
+ assert( strchr(fname.c_str(), '\\') == 0 );
+
+ stringstream ss(fname);
+ int n;
+ ss >> n;
+ assert( n >= 0 );
+ char ch;
+ ss >> ch;
+ assert( ch == '-' );
+ string rest;
+ ss >> rest;
+ const char *p = rest.c_str();
+ const char *q = strstr(p, ".idx");
+ assert( q );
+ string escaped_ns(p, q-p);
+
+ // arbitrary limit. if you are hitting, we should use fewer files and put multiple
+ // indexes in a single file (which is easy to do)
+ massert( 10374 , "too many index files", n < 10000 );
+
+ if( stores.size() < (unsigned)n+1 )
+ stores.resize(n+1);
+ assert( stores[n] == 0 );
+ BasicRecStore *rs = new BasicRecStore(n);
+ path pf(directory());
+ pf /= fname;
+ string full = pf.string();
+ rs->init(full.c_str(), recsize);
+ stores[n] = rs;
+ string ns = unescape(escaped_ns.c_str());
+ storesByNsKey[mknskey(ns.c_str())] = rs;
+ return rs;
+}
+
+BasicRecStore* RecCache::initStore(int n) {
+ string ns;
+ {
+ stringstream ss;
+ ss << '/' << n << '-';
+ ns = ss.str();
+ }
+
+ /* this will be slow if there are thousands of files */
+ path dir(directory());
+ directory_iterator end; + try { + directory_iterator i(dir); + while ( i != end ) { + string s = i->string(); + const char *p = strstr(s.c_str(), ns.c_str()); + if( p && strstr(p, ".idx") ) { + // found it + path P = *i; + return _initStore(P.leaf()); + } + i++; + } + } + catch( DBException & ) { + throw; + } + catch (...) { + string s = string("i/o error looking for .idx file in ") + directory(); + massert( 10375 , s, false); + } + stringstream ss; + ss << "index datafile missing? n=" << n; + uasserted(12500,ss.str());
+ return 0;
+}
+
+/* find the filename for a given ns.
+ format is
+ <n>-<escaped_ns>.idx
+ returns filename. found is true if found. If false, a proposed name is returned for (optional) creation
+ of the file.
+*/
+string RecCache::findStoreFilename(const char *_ns, bool& found) {
+ string namefrag;
+ {
+ stringstream ss;
+ ss << '-';
+ ss << escape(_ns);
+ ss << ".idx";
+ namefrag = ss.str();
+ }
+
+ path dir(directory());
+ directory_iterator end; + int nmax = -1; + try { + directory_iterator i(dir); + while ( i != end ) { + string s = path(*i).leaf(); + const char *p = strstr(s.c_str(), namefrag.c_str()); + if( p ) { + found = true; + return s; + } + if( strstr(s.c_str(), ".idx") ) { + stringstream ss(s); + int n = -1; + ss >> n; + if( n > nmax ) + nmax = n; + } + i++; + } + } + catch (...) { + string s = string("i/o error looking for .idx file in ") + directory(); + massert( 10376 , s, false); + } + + // DNE. return a name that would work. + stringstream ss; + ss << nmax+1 << namefrag; + found = false; + return ss.str(); +}
+
+void RecCache::initStoreByNs(const char *_ns, const string& nskey) {
+ bool found;
+ string fn = findStoreFilename(_ns, found);
+ _initStore(fn); +}
+
+inline void RecCache::writeIfDirty(Node *n) {
+ if( n->dirty ) {
+ ndirtywritten++;
+ n->dirty = false;
+ store(n->loc).update(fileOfs(n->loc), n->data, recsize);
+ }
+}
+
+void RecCache::closeFiles(string dbname, string path) {
+ assertInWriteLock();
+ boostlock lk(rcmutex);
+
+ // first we write all dirty pages. it is not easy to check which Nodes are for a particular
+ // db, so we just write them all.
+ writeDirty( dirtyl.begin(), true );
+
+ string key = path + dbname + '.';
+ unsigned sz = key.size();
+ for( map<string, BasicRecStore*>::iterator i = storesByNsKey.begin(); i != storesByNsKey.end(); i++ ) {
+ map<string, BasicRecStore*>::iterator j = i;
+ i++;
+ if( strncmp(j->first.c_str(), key.c_str(), sz) == 0 ) {
+ assert( stores[j->second->fileNumber] != 0 );
+ stores[j->second->fileNumber] = 0;
+ delete j->second;
+ storesByNsKey.erase(j);
+ }
+ }
+}
+
+void RecCache::closing() {
+ boostlock lk(rcmutex);
+ (cout << "TEMP: recCacheCloseAll() writing dirty pages...\n").flush();
+ writeDirty( dirtyl.begin(), true );
+ for( unsigned i = 0; i < stores.size(); i++ ) {
+ if( stores[i] ) {
+ delete stores[i];
+ }
+ }
+ (cout << "TEMP: write dirty done\n").flush();
+}
+
+/* note that this is written in order, as much as possible, given that dirtyl is of type set. */
+void RecCache::writeDirty( set<DiskLoc>::iterator startAt, bool rawLog ) {
+ try {
+ ndirtywritten=0;
+ for( set<DiskLoc>::iterator i = startAt; i != dirtyl.end(); i++ ) {
+ map<DiskLoc, Node*>::iterator j = m.find(*i);
+ if( j != m.end() )
+ writeIfDirty(j->second);
+ }
+ OCCASIONALLY out() << "TEMP: ndirtywritten: " << ndirtywritten << endl;
+ }
+ catch(...) {
+ const char *message = "Problem: bad() in RecCache::writeDirty, file io error\n"; + + if ( rawLog ) + rawOut( message ); + else + ( log() << message ).flush(); + }
+ dirtyl.clear();
+}
+
+void RecCache::writeLazily() {
+ int sleep = 0;
+ int k;
+ {
+ boostlock lk(rcmutex);
+ Timer t;
+ set<DiskLoc>::iterator i = dirtyl.end();
+ for( k = 0; k < 100; k++ ) {
+ if( i == dirtyl.begin() ) {
+ // we're not very far behind
+ sleep = k < 20 ? 2000 : 1000;
+ break;
+ }
+ i--;
+ }
+ writeDirty(i);
+ if( sleep == 0 ) {
+ sleep = t.millis() * 4 + 10;
+ }
+ }
+
+ OCCASIONALLY cout << "writeLazily " << k << " sleep:" << sleep << '\n';
+ sleepmillis(sleep);
+}
+
+void RecCache::_ejectOld() {
+ boostlock lk(rcmutex);
+ if( nnodes <= MAXNODES )
+ return;
+ Node *n = oldest;
+ while( 1 ) {
+ if( nnodes <= MAXNODES - 4 ) {
+ n->older = 0;
+ oldest = n;
+ assert( oldest ) ;
+ break;
+ }
+ nnodes--;
+ assert(n);
+ Node *nxt = n->newer;
+ writeIfDirty(n);
+ m.erase(n->loc);
+ delete n;
+ n = nxt;
+ }
+}
+
+void RecCache::dump() {
+ Node *n = oldest;
+ Node *last = 0;
+ while( n ) {
+ assert( n->older == last );
+ last = n;
+// cout << n << ' ' << n->older << ' ' << n->newer << '\n';
+ n=n->newer;
+ }
+ assert( newest == last );
+// cout << endl;
+}
+
+/* cleans up everything EXCEPT storesByNsKey.
+ note this function is slow should not be invoked often
+*/
+void RecCache::closeStore(BasicRecStore *rs) {
+ int n = rs->fileNumber + Base;
+ for( set<DiskLoc>::iterator i = dirtyl.begin(); i != dirtyl.end(); ) {
+ DiskLoc k = *i++;
+ if( k.a() == n )
+ dirtyl.erase(k);
+ }
+
+ for( map<DiskLoc,Node*>::iterator i = m.begin(); i != m.end(); ) {
+ DiskLoc k = i->first;
+ i++;
+ if( k.a() == n )
+ m.erase(k);
+ }
+
+ assert( stores[rs->fileNumber] != 0 );
+ stores[rs->fileNumber] = 0;
+/*
+ for( unsigned i = 0; i < stores.size(); i++ ) {
+ if( stores[i] == rs ) {
+ stores[i] = 0;
+ break;
+ }
+ }*/
+ delete rs; // closes file
+}
+
+void RecCache::drop(const char *_ns) {
+ // todo: test with a non clean shutdown file
+ boostlock lk(rcmutex);
+
+ map<string, BasicRecStore*>::iterator it = storesByNsKey.find(mknskey(_ns));
+ string fname;
+ if( it != storesByNsKey.end() ) {
+ fname = it->second->filename;
+ closeStore(it->second); // cleans up stores[] etc.
+ storesByNsKey.erase(it);
+ }
+ else {
+ bool found;
+ fname = findStoreFilename(_ns, found);
+ if( !found ) {
+ log() << "RecCache::drop: no idx file found for " << _ns << endl;
+ return;
+ }
+ path pf(directory());
+ pf /= fname;
+ fname = pf.string();
+ }
+ try {
+ if( !boost::filesystem::exists(fname) )
+ log() << "RecCache::drop: can't find file to remove " << fname << endl;
+ boost::filesystem::remove(fname);
+ }
+ catch(...) {
+ log() << "RecCache::drop: exception removing file " << fname << endl;
+ }
+}
+
+}
diff --git a/db/reccache.h b/db/reccache.h new file mode 100644 index 0000000..42943c5 --- /dev/null +++ b/db/reccache.h @@ -0,0 +1,242 @@ +// reccache.h + +/* CachedBasicRecStore + This is our store which implements a traditional page-cache type of storage + (not memory mapped files). +*/ + +/* LOCK HIERARCHY + + dblock + RecCache::rcmutex + + i.e. always lock dblock first if you lock both + +*/ + +#pragma once + +#include "reci.h" +#include "recstore.h" + +namespace mongo { + +class RecCache { + struct Node { + Node(void* _data) : data((char *) _data) { dirty = false; newer = 0; } + ~Node() { + free(data); + data = 0; + } + char *data; + DiskLoc loc; + bool dirty; + Node *older, *newer; // lru + }; + boost::mutex &rcmutex; // mainly to coordinate with the lazy writer thread + unsigned recsize; + map<DiskLoc, Node*> m; // the cache + Node *newest, *oldest; + unsigned nnodes; + set<DiskLoc> dirtyl; + vector<BasicRecStore*> stores; // DiskLoc::a() indicates the index into this vector + map<string, BasicRecStore*> storesByNsKey; // nskey -> BasicRecStore* +public: + static unsigned MAXNODES; + enum BaseValue { Base = 10000 }; +private: + BasicRecStore* _initStore(string fname); + BasicRecStore* initStore(int n); + string findStoreFilename(const char *_ns, bool& found); + void initStoreByNs(const char *ns, const string& nskey); + void closeStore(BasicRecStore *rs); + + static string directory(); + static string mknskey(const char *ns) { + return directory() + ns; + } + + /* get the right file for a given diskloc */ + BasicRecStore& store(DiskLoc& d) { + int n = d.a() - Base; + if( (int) stores.size() > n ) { + BasicRecStore *rs = stores[n]; + if( rs ) { + assert( rs->fileNumber == n ); + return *rs; + } + } + return *initStore(n); + } + BasicRecStore& store(const char *ns) { + string nskey = mknskey(ns); + BasicRecStore *&rs = storesByNsKey[nskey]; + if( rs ) + return *rs; + initStoreByNs(ns, nskey); + return *rs; + } + + void writeDirty( set<DiskLoc>::iterator i, bool rawLog = false ); + void writeIfDirty(Node *n); + void touch(Node* n) { + if( n == newest ) + return; + if( n == oldest ) { + oldest = oldest->newer; + assert( oldest || nnodes == 1 ); + } + if( n->older ) + n->older->newer = n->newer; + if( n->newer ) + n->newer->older = n->older; + n->newer = 0; + n->older = newest; + newest->newer = n; + newest = n; + } + Node* mkNode() { + Node *n = new Node(calloc(recsize,1)); // calloc is TEMP for testing. change to malloc + n->older = newest; + if( newest ) + newest->newer = n; + else { + assert( oldest == 0 ); + oldest = n; + } + newest = n; + nnodes++; + return n; + } + fileofs fileOfs(DiskLoc d) { + return ((fileofs) d.getOfs()) * recsize; + } + + void dump(); + void _ejectOld(); + +public: + /* all public functions (except constructor) should use the mutex */ + + RecCache(unsigned recsz) : rcmutex( *( new boost::mutex() ) ), recsize(recsz) { + nnodes = 0; + newest = oldest = 0; + } + + /* call this after doing some work, after you are sure you are done with modifications. + we call it from dbunlocking(). + */ + void ejectOld() { + if( nnodes > MAXNODES ) // just enough here to be inlineable for speed reasons. _ejectOld does the real work + _ejectOld(); + } + + /* bg writer thread invokes this */ + void writeLazily(); + + /* Note that this may be called BEFORE the actual writing to the node + takes place. We do flushing later on a dbunlocking() call, which happens + after the writing. + */ + void dirty(DiskLoc d) { + assert( d.a() >= Base ); + boostlock lk(rcmutex); + map<DiskLoc, Node*>::iterator i = m.find(d); + if( i != m.end() ) { + Node *n = i->second; + if( !n->dirty ) { + n->dirty = true; + dirtyl.insert(n->loc); + } + } + } + + char* get(DiskLoc d, unsigned len) { + assert( d.a() >= Base ); + assert( len == recsize ); + + boostlock lk(rcmutex); + map<DiskLoc, Node*>::iterator i = m.find(d); + if( i != m.end() ) { + touch(i->second); + return i->second->data; + } + + Node *n = mkNode(); + n->loc = d; + store(d).get(fileOfs(d), n->data, recsize); // could throw exception + m.insert( pair<DiskLoc, Node*>(d, n) ); + return n->data; + } + + void drop(const char *ns); + + DiskLoc insert(const char *ns, const void *obuf, int len, bool god) { + boostlock lk(rcmutex); + BasicRecStore& rs = store(ns); + fileofs o = rs.insert((const char *) obuf, len); + assert( o % recsize == 0 ); + fileofs recnum = o / recsize; + massert( 10377 , "RecCache file too large?", recnum <= 0x7fffffff ); + Node *n = mkNode(); + memcpy(n->data, obuf, len); + DiskLoc d(rs.fileNumber + Base, (int) recnum); + n->loc = d; + m[d] = n; + return d; + } + + void closeFiles(string dbname, string path); + + // at termination: write dirty pages and close all files + void closing(); +}; + +extern RecCache theRecCache; + +class CachedBasicRecStore : public RecStoreInterface { +public: + virtual char* get(DiskLoc d, unsigned len) { + return theRecCache.get(d, len); + } + + virtual DiskLoc insert(const char *ns, const void *obuf, int len, bool god) { + return theRecCache.insert(ns, obuf, len, god); + } + + virtual void modified(DiskLoc d) { + theRecCache.dirty(d); + } + + /* drop collection */ + virtual void drop(const char *ns) { + theRecCache.drop(ns); + } + + virtual void rename(const char *fromNs, const char *toNs) { + massert( 10378 , "rename not yet implemented for CachedBasicRecStore", false ); + } + + /* close datafiles associated with the db specified. */ + virtual void closeFiles(string dbname, string path) { + theRecCache.closeFiles(dbname, dbpath); + } +}; + +/* see concurrency.h - note on a lock reset from read->write we don't + call dbunlocking_read, we just wait for the final dbunlocking_write + call +*/ + +inline void dbunlocking_read() { + Client *c = currentClient.get(); + if ( c ) + c->top.clientStop(); +} + +inline void dbunlocking_write() { + theRecCache.ejectOld(); + dbunlocking_read(); +} + +} /*namespace*/ diff --git a/db/reci.h b/db/reci.h new file mode 100644 index 0000000..295388c --- /dev/null +++ b/db/reci.h @@ -0,0 +1,45 @@ +// reci.h + +#pragma once + +#include "storage.h" + +namespace mongo { + +/* Subclass this and implement your real storage interface. +*/ +class RecStoreInterface { +public: + virtual ~RecStoreInterface() {} + + /* Get a pointer to the data at diskloc d. Pointer guaranteed to stay in + scope through the current database operation's life. + */ + virtual char* get(DiskLoc d, unsigned len) = 0; + + /* indicate that the diskloc specified has been updated. note that as-is today, the modification may come AFTER this + call -- we handle that currently -- until the dblock finishes. + */ + virtual void modified(DiskLoc d) = 0; + + /* insert specified data as a record */ + virtual DiskLoc insert(const char *ns, const void *obuf, int len, bool god) = 0; + + virtual void deleteRecord(const char *ns, DiskLoc d) { massert( 10379 , "not implemented RecStoreInterface::deleteRecord", false); } + + /* drop the collection */ + virtual void drop(const char *ns) = 0; + + /* rename collection */ + virtual void rename(const char *fromNs, const char *toNs) = 0; + + /* close datafiles associated with the db specified. */ + virtual void closeFiles(string dbname, string path) = 0; + + /* todo add: + closeFiles(dbname) + eraseFiles(dbname) + */ +}; + +} diff --git a/db/recstore.h b/db/recstore.h new file mode 100644 index 0000000..2e6a90a --- /dev/null +++ b/db/recstore.h @@ -0,0 +1,108 @@ +// recstore.h
+
+#pragma once
+
+#include "../util/file.h"
+
+namespace mongo {
+
+using boost::uint32_t;
+using boost::uint64_t;
+
+/* Current version supports only consistent record sizes within a store. */
+
+class BasicRecStore {
+ struct RecStoreHeader {
+ uint32_t version;
+ uint32_t recsize;
+ uint64_t leof; // logical eof, actual file might be prealloc'd further
+ uint64_t firstDeleted; // 0 = no deleted recs
+ uint32_t cleanShutdown; // 0 = clean
+ char reserved[8192-8-8-4-4-4]; // we want our records page-aligned in the file if they are a multiple of a page's size -- so we make this 8KB with that goal
+ RecStoreHeader() {
+ version = 65;
+ recsize = 0;
+ leof = sizeof(RecStoreHeader);
+ firstDeleted = 0;
+ cleanShutdown = 1;
+ memset(reserved, 0, sizeof(reserved));
+ }
+ };
+
+public:
+ BasicRecStore(int _fileNumber) : fileNumber(_fileNumber) { }
+ ~BasicRecStore();
+ void init(const char *fn, unsigned recsize);
+ fileofs insert(const char *buf, unsigned len);
+ void update(fileofs o, const char *buf, unsigned len);
+ void remove(fileofs o, unsigned len);
+ void get(fileofs o, char *buf, unsigned len);
+
+ int fileNumber; // this goes in DiskLoc::a
+
+ string filename;
+
+private:
+
+ void writeHeader();
+ File f;
+ fileofs len;
+ RecStoreHeader h; // h.reserved is wasteful here; fix later.
+ void write(fileofs ofs, const char *data, unsigned len) {
+ f.write(ofs, data, len);
+ massert( 10380 , "basicrecstore write io error", !f.bad());
+ }
+};
+
+/* --- implementation --- */
+
+inline BasicRecStore::~BasicRecStore() {
+ h.cleanShutdown = 0;
+ if( f.is_open() ) {
+ writeHeader();
+ f.fsync();
+ }
+}
+
+inline void BasicRecStore::writeHeader() {
+ write(0, (const char *) &h, 28); // update header in file for new leof
+ uassert( 10115 , "file io error in BasicRecStore [1]", !f.bad());
+}
+
+inline fileofs BasicRecStore::insert(const char *buf, unsigned reclen) {
+ if( h.firstDeleted ) {
+ uasserted(11500, "deleted not yet implemented recstoreinsert");
+ }
+ massert( 10381 , "bad len", reclen == h.recsize);
+ fileofs ofs = h.leof;
+ h.leof += reclen;
+ if( h.leof > len ) {
+ // grow the file. we grow quite a bit to avoid excessive file system fragmentations
+ len += (len / 8) + h.recsize;
+ uassert( 10116 , "recstore file too big for 32 bit", len <= 0x7fffffff || sizeof(std::streamoff) > 4 );
+ write(len, "", 0);
+ }
+ writeHeader();
+ write(ofs, buf, reclen);
+ uassert( 10117 , "file io error in BasicRecStore [2]", !f.bad());
+ return ofs;
+}
+
+/* so far, it's ok to read or update a subset of a record */
+
+inline void BasicRecStore::update(fileofs o, const char *buf, unsigned len) {
+ assert(o <= h.leof && o >= sizeof(RecStoreHeader));
+ write(o, buf, len);
+}
+
+inline void BasicRecStore::get(fileofs o, char *buf, unsigned len) {
+ assert(o <= h.leof && o >= sizeof(RecStoreHeader));
+ f.read(o, buf, len);
+ massert( 10382 , "basicrestore::get I/O error", !f.bad());
+}
+
+inline void BasicRecStore::remove(fileofs o, unsigned len) {
+ uasserted(11501, "not yet implemented recstoreremove");
+}
+
+}
diff --git a/db/repl.cpp b/db/repl.cpp new file mode 100644 index 0000000..04c8d73 --- /dev/null +++ b/db/repl.cpp @@ -0,0 +1,1769 @@ +// repl.cpp + +/* TODO + + PAIRING + _ on a syncexception, don't allow going back to master state? + +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* Collections we use: + + local.sources - indicates what sources we pull from as a "slave", and the last update of each + local.oplog.$main - our op log as "master" + local.dbinfo.<dbname> + local.pair.startup - can contain a special value indicating for a pair that we have the master copy. + used when replacing other half of the pair which has permanently failed. + local.pair.sync - { initialsynccomplete: 1 } +*/ + +#include "stdafx.h" +#include "jsobj.h" +#include "../util/goodies.h" +#include "repl.h" +#include "../util/message.h" +#include "../client/dbclient.h" +#include "pdfile.h" +#include "query.h" +#include "db.h" +#include "commands.h" +#include "security.h" +#include "cmdline.h" + +namespace mongo { + + void ensureHaveIdIndex(const char *ns); + + /* if 1 sync() is running */ + int syncing = 0; + + /* if true replace our peer in a replication pair -- don't worry about if his + local.oplog.$main is empty. + */ + bool replacePeer = false; + + /* "dead" means something really bad happened like replication falling completely out of sync. + when non-null, we are dead and the string is informational + */ + const char *replAllDead = 0; + + extern bool autoresync; + time_t lastForcedResync = 0; + + IdTracker &idTracker = *( new IdTracker() ); + +} // namespace mongo + +#include "replset.h" + +namespace mongo { + + PairSync *pairSync = new PairSync(); + bool getInitialSyncCompleted() { + return pairSync->initialSyncCompleted(); + } + + /* --- ReplPair -------------------------------- */ + + ReplPair *replPair = 0; + + /* output by the web console */ + const char *replInfo = ""; + struct ReplInfo { + ReplInfo(const char *msg) { + replInfo = msg; + } + ~ReplInfo() { + replInfo = "?"; + } + }; + + void ReplPair::setMaster(int n, const char *_comment ) { + if ( n == State_Master && !getInitialSyncCompleted() ) + return; + info = _comment; + if ( n != state && !cmdLine.quiet ) + log() << "pair: setting master=" << n << " was " << state << '\n'; + state = n; + } + + /* peer unreachable, try our arbiter */ + void ReplPair::arbitrate() { + ReplInfo r("arbitrate"); + + if ( arbHost == "-" ) { + // no arbiter. we are up, let's assume partner is down and network is not partitioned. + setMasterLocked(State_Master, "remote unreachable"); + return; + } + + auto_ptr<DBClientConnection> conn( newClientConnection() ); + string errmsg; + if ( !conn->connect(arbHost.c_str(), errmsg) ) { + log() << "repl: cantconn arbiter " << errmsg << endl; + setMasterLocked(State_CantArb, "can't connect to arb"); + return; + } + + negotiate( conn.get(), "arbiter" ); + } + + /* --------------------------------------------- */ + + class CmdReplacePeer : public Command { + public: + virtual bool slaveOk() { + return true; + } + virtual bool adminOnly() { + return true; + } + virtual bool logTheOp() { + return false; + } + CmdReplacePeer() : Command("replacepeer") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + if ( replPair == 0 ) { + errmsg = "not paired"; + return false; + } + if ( !getInitialSyncCompleted() ) { + errmsg = "not caught up cannot replace peer"; + return false; + } + if ( syncing < 0 ) { + errmsg = "replacepeer already invoked"; + return false; + } + Timer t; + while ( 1 ) { + if ( syncing == 0 || t.millis() > 20000 ) + break; + { + dbtemprelease t; + sleepmillis(10); + } + } + if ( syncing ) { + assert( syncing > 0 ); + errmsg = "timeout waiting for sync() to finish"; + return false; + } + { + ReplSource::SourceVector sources; + ReplSource::loadAll(sources); + if ( sources.size() != 1 ) { + errmsg = "local.sources.count() != 1, cannot replace peer"; + return false; + } + } + { + Helpers::emptyCollection("local.sources"); + BSONObj o = fromjson("{\"replacepeer\":1}"); + Helpers::putSingleton("local.pair.startup", o); + } + syncing = -1; + replAllDead = "replacepeer invoked -- adjust local.sources hostname then restart this db process"; + result.append("info", "adjust local.sources hostname; db restart now required"); + return true; + } + } cmdReplacePeer; + + class CmdForceDead : public Command { + public: + virtual bool slaveOk() { + return true; + } + virtual bool adminOnly() { + return true; + } + virtual bool logTheOp() { + return false; + } + CmdForceDead() : Command("forcedead") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + replAllDead = "forced by command"; + return true; + } + } cmdForceDead; + + /* operator requested resynchronization of replication (on the slave). { resync : 1 } */ + class CmdResync : public Command { + public: + virtual bool slaveOk() { + return true; + } + virtual bool adminOnly() { + return true; + } + virtual bool logTheOp() { + return false; + } + CmdResync() : Command("resync") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + if ( cmdObj.getBoolField( "force" ) ) { + if ( !waitForSyncToFinish( errmsg ) ) + return false; + replAllDead = "resync forced"; + } + if ( !replAllDead ) { + errmsg = "not dead, no need to resync"; + return false; + } + if ( !waitForSyncToFinish( errmsg ) ) + return false; + + ReplSource::forceResyncDead( "client" ); + result.append( "info", "triggered resync for all sources" ); + return true; + } + bool waitForSyncToFinish( string &errmsg ) const { + // Wait for slave thread to finish syncing, so sources will be be + // reloaded with new saved state on next pass. + Timer t; + while ( 1 ) { + if ( syncing == 0 || t.millis() > 20000 ) + break; + { + dbtemprelease t; + sleepmillis(10); + } + } + if ( syncing ) { + errmsg = "timeout waiting for sync() to finish"; + return false; + } + return true; + } + } cmdResync; + + class CmdIsMaster : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool slaveOk() { + return true; + } + CmdIsMaster() : Command("ismaster") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { + /* currently request to arbiter is (somewhat arbitrarily) an ismaster request that is not + authenticated. + we allow unauthenticated ismaster but we aren't as verbose informationally if + one is not authenticated for admin db to be safe. + */ + AuthenticationInfo *ai = currentClient.get()->ai; + bool authed = ai->isAuthorized("admin"); + + if ( replAllDead ) { + result.append("ismaster", 0.0); + if( authed ) { + if ( replPair ) + result.append("remote", replPair->remote); + result.append("info", replAllDead); + } + } + else if ( replPair ) { + result.append("ismaster", replPair->state); + if( authed ) { + result.append("remote", replPair->remote); + if ( !replPair->info.empty() ) + result.append("info", replPair->info); + } + } + else { + result.append("ismaster", slave ? 0 : 1); + result.append("msg", "not paired"); + } + + return true; + } + } cmdismaster; + + class CmdIsInitialSyncComplete : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool slaveOk() { + return true; + } + CmdIsInitialSyncComplete() : Command( "isinitialsynccomplete" ) {} + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { + result.appendBool( "initialsynccomplete", getInitialSyncCompleted() ); + return true; + } + } cmdisinitialsynccomplete; + + /* negotiate who is master + + -1=not set (probably means we just booted) + 0=was slave + 1=was master + + remote,local -> new remote,local + !1,1 -> 0,1 + 1,!1 -> 1,0 + -1,-1 -> dominant->1, nondom->0 + 0,0 -> dominant->1, nondom->0 + 1,1 -> dominant->1, nondom->0 + + { negotiatemaster:1, i_was:<state>, your_name:<hostname> } + returns: + { ok:1, you_are:..., i_am:... } + */ + class CmdNegotiateMaster : public Command { + public: + CmdNegotiateMaster() : Command("negotiatemaster") { } + virtual bool slaveOk() { + return true; + } + virtual bool adminOnly() { + return true; + } + + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + if ( replPair == 0 ) { + massert( 10383 , "Another mongod instance believes incorrectly that this node is its peer", !cmdObj.getBoolField( "fromArbiter" ) ); + // assume that we are an arbiter and should forward the request + string host = cmdObj.getStringField("your_name"); + int port = cmdObj.getIntField( "your_port" ); + if ( port == INT_MIN ) { + errmsg = "no port specified"; + problem() << errmsg << endl; + return false; + } + stringstream ss; + ss << host << ":" << port; + string remote = ss.str(); + BSONObj ret; + { + dbtemprelease t; + auto_ptr<DBClientConnection> conn( new DBClientConnection() ); + if ( !conn->connect( remote.c_str(), errmsg ) ) { + result.append( "you_are", ReplPair::State_Master ); + return true; + } + BSONObjBuilder forwardCommand; + forwardCommand.appendElements( cmdObj ); + forwardCommand.appendBool( "fromArbiter", true ); + ret = conn->findOne( "admin.$cmd", forwardCommand.done() ); + } + BSONObjIterator i( ret ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + if ( e.fieldName() != string( "ok" ) ) + result.append( e ); + } + return ( ret.getIntField("ok") == 1 ); + } + + int was = cmdObj.getIntField("i_was"); + string myname = cmdObj.getStringField("your_name"); + if ( myname.empty() || was < -3 ) { + errmsg = "your_name/i_was not specified"; + return false; + } + + int N = ReplPair::State_Negotiating; + int M = ReplPair::State_Master; + int S = ReplPair::State_Slave; + + if ( !replPair->dominant( myname ) ) { + result.append( "you_are", N ); + result.append( "i_am", replPair->state ); + return true; + } + + int me, you; + if ( !getInitialSyncCompleted() || ( replPair->state != M && was == M ) ) { + me=S; + you=M; + } + else { + me=M; + you=S; + } + replPair->setMaster( me, "CmdNegotiateMaster::run()" ); + + result.append("you_are", you); + result.append("i_am", me); + + return true; + } + } cmdnegotiatemaster; + + int ReplPair::negotiate(DBClientConnection *conn, string method) { + BSONObjBuilder b; + b.append("negotiatemaster",1); + b.append("i_was", state); + b.append("your_name", remoteHost); + b.append("your_port", remotePort); + BSONObj cmd = b.done(); + BSONObj res = conn->findOne("admin.$cmd", cmd); + if ( res.getIntField("ok") != 1 ) { + string message = method + " negotiate failed"; + problem() << message << ": " << res.toString() << '\n'; + setMasterLocked(State_Confused, message.c_str()); + return State_Confused; + } + int x = res.getIntField("you_are"); + int remote = res.getIntField("i_am"); + // State_Negotiating means the remote node is not dominant and cannot + // choose who is master. + if ( x != State_Slave && x != State_Master && x != State_Negotiating ) { + problem() << method << " negotiate: bad you_are value " << res.toString() << endl; + } else if ( x != State_Negotiating ) { + string message = method + " negotiation"; + setMasterLocked(x, message.c_str()); + } + return remote; + } + + struct TestOpTime { + TestOpTime() { + OpTime t; + for ( int i = 0; i < 10; i++ ) { + OpTime s = OpTime::now(); + assert( s != t ); + t = s; + } + OpTime q = t; + assert( q == t ); + assert( !(q != t) ); + } + } testoptime; + + /* --------------------------------------------------------------*/ + + ReplSource::ReplSource() { + replacing = false; + nClonedThisPass = 0; + paired = false; + } + + ReplSource::ReplSource(BSONObj o) : nClonedThisPass(0) { + replacing = false; + paired = false; + only = o.getStringField("only"); + hostName = o.getStringField("host"); + _sourceName = o.getStringField("source"); + uassert( 10118 , "'host' field not set in sources collection object", !hostName.empty() ); + uassert( 10119 , "only source='main' allowed for now with replication", sourceName() == "main" ); + BSONElement e = o.getField("syncedTo"); + if ( !e.eoo() ) { + uassert( 10120 , "bad sources 'syncedTo' field value", e.type() == Date || e.type() == Timestamp ); + OpTime tmp( e.date() ); + syncedTo = tmp; + } + + BSONObj dbsObj = o.getObjectField("dbsNextPass"); + if ( !dbsObj.isEmpty() ) { + BSONObjIterator i(dbsObj); + while ( 1 ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + addDbNextPass.insert( e.fieldName() ); + } + } + + dbsObj = o.getObjectField("incompleteCloneDbs"); + if ( !dbsObj.isEmpty() ) { + BSONObjIterator i(dbsObj); + while ( 1 ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + incompleteCloneDbs.insert( e.fieldName() ); + } + } + + _lastSavedLocalTs = OpTime( o.getField( "localLogTs" ).date() ); + } + + /* Turn our C++ Source object into a BSONObj */ + BSONObj ReplSource::jsobj() { + BSONObjBuilder b; + b.append("host", hostName); + b.append("source", sourceName()); + if ( !only.empty() ) + b.append("only", only); + if ( !syncedTo.isNull() ) + b.appendTimestamp("syncedTo", syncedTo.asDate()); + + b.appendTimestamp("localLogTs", _lastSavedLocalTs.asDate()); + + BSONObjBuilder dbsNextPassBuilder; + int n = 0; + for ( set<string>::iterator i = addDbNextPass.begin(); i != addDbNextPass.end(); i++ ) { + n++; + dbsNextPassBuilder.appendBool(i->c_str(), 1); + } + if ( n ) + b.append("dbsNextPass", dbsNextPassBuilder.done()); + + BSONObjBuilder incompleteCloneDbsBuilder; + n = 0; + for ( set<string>::iterator i = incompleteCloneDbs.begin(); i != incompleteCloneDbs.end(); i++ ) { + n++; + incompleteCloneDbsBuilder.appendBool(i->c_str(), 1); + } + if ( n ) + b.append("incompleteCloneDbs", incompleteCloneDbsBuilder.done()); + + return b.obj(); + } + + void ReplSource::save() { + BSONObjBuilder b; + assert( !hostName.empty() ); + b.append("host", hostName); + // todo: finish allowing multiple source configs. + // this line doesn't work right when source is null, if that is allowed as it is now: + //b.append("source", _sourceName); + BSONObj pattern = b.done(); + + BSONObj o = jsobj(); + log( 1 ) << "Saving repl source: " << o << endl; + + OpDebug debug; + setClient("local.sources"); + UpdateResult res = updateObjects("local.sources", o, pattern, true/*upsert for pair feature*/, false,false,debug); + assert( ! res.mod ); + assert( res.num == 1 ); + cc().clearns(); + + if ( replacing ) { + /* if we were in "replace" mode, we now have synced up with the replacement, + so turn that off. + */ + replacing = false; + wassert( replacePeer ); + replacePeer = false; + Helpers::emptyCollection("local.pair.startup"); + } + } + + static void addSourceToList(ReplSource::SourceVector &v, ReplSource& s, const BSONObj &spec, ReplSource::SourceVector &old) { + if ( !s.syncedTo.isNull() ) { // Don't reuse old ReplSource if there was a forced resync. + for ( ReplSource::SourceVector::iterator i = old.begin(); i != old.end(); ) { + if ( s == **i ) { + v.push_back(*i); + old.erase(i); + return; + } + i++; + } + } + + v.push_back( shared_ptr< ReplSource >( new ReplSource( s ) ) ); + } + + /* we reuse our existing objects so that we can keep our existing connection + and cursor in effect. + */ + void ReplSource::loadAll(SourceVector &v) { + SourceVector old = v; + v.clear(); + + bool gotPairWith = false; + + if ( !cmdLine.source.empty() ) { + setClient("local.sources"); + // --source <host> specified. + // check that no items are in sources other than that + // add if missing + auto_ptr<Cursor> c = findTableScan("local.sources", BSONObj()); + int n = 0; + while ( c->ok() ) { + n++; + ReplSource tmp(c->current()); + if ( tmp.hostName != cmdLine.source ) { + log() << "--source " << cmdLine.source << " != " << tmp.hostName << " from local.sources collection" << endl; + log() << "terminating after 30 seconds" << endl; + sleepsecs(30); + dbexit( EXIT_REPLICATION_ERROR ); + } + if ( tmp.only != cmdLine.only ) { + log() << "--only " << cmdLine.only << " != " << tmp.only << " from local.sources collection" << endl; + log() << "terminating after 30 seconds" << endl; + sleepsecs(30); + dbexit( EXIT_REPLICATION_ERROR ); + } + c->advance(); + } + uassert( 10002 , "local.sources collection corrupt?", n<2 ); + if ( n == 0 ) { + // source missing. add. + ReplSource s; + s.hostName = cmdLine.source; + s.only = cmdLine.only; + s.save(); + } + } + else { + try { + massert( 10384 , "--only requires use of --source", cmdLine.only.empty()); + } catch ( ... ) { + dbexit( EXIT_BADOPTIONS ); + } + } + + if ( replPair ) { + const string &remote = replPair->remote; + setClient( "local.sources" ); + // --pairwith host specified. + // check that no items are in sources other than that + // add if missing + auto_ptr<Cursor> c = findTableScan("local.sources", BSONObj()); + int n = 0; + while ( c->ok() ) { + n++; + ReplSource tmp(c->current()); + if ( tmp.hostName != remote ) { + log() << "pairwith " << remote << " != " << tmp.hostName << " from local.sources collection" << endl; + log() << "terminating after 30 seconds" << endl; + sleepsecs(30); + dbexit( EXIT_REPLICATION_ERROR ); + } + c->advance(); + } + uassert( 10122 , "local.sources collection corrupt?", n<2 ); + if ( n == 0 ) { + // source missing. add. + ReplSource s; + s.hostName = remote; + s.save(); + } + } + + setClient("local.sources"); + auto_ptr<Cursor> c = findTableScan("local.sources", BSONObj()); + while ( c->ok() ) { + ReplSource tmp(c->current()); + if ( replPair && tmp.hostName == replPair->remote && tmp.sourceName() == "main" ) { + gotPairWith = true; + tmp.paired = true; + if ( replacePeer ) { + // peer was replaced -- start back at the beginning. + tmp.syncedTo = OpTime(); + tmp.replacing = true; + } + } + addSourceToList(v, tmp, c->current(), old); + c->advance(); + } + cc().clearns(); + + if ( !gotPairWith && replPair ) { + /* add the --pairwith server */ + shared_ptr< ReplSource > s( new ReplSource() ); + s->paired = true; + s->hostName = replPair->remote; + s->replacing = replacePeer; + v.push_back(s); + } + } + + BSONObj opTimeQuery = fromjson("{\"getoptime\":1}"); + + bool ReplSource::throttledForceResyncDead( const char *requester ) { + if ( time( 0 ) - lastForcedResync > 600 ) { + forceResyncDead( requester ); + lastForcedResync = time( 0 ); + return true; + } + return false; + } + + void ReplSource::forceResyncDead( const char *requester ) { + if ( !replAllDead ) + return; + SourceVector sources; + ReplSource::loadAll(sources); + for( SourceVector::iterator i = sources.begin(); i != sources.end(); ++i ) { + (*i)->forceResync( requester ); + } + replAllDead = 0; + } + + void ReplSource::forceResync( const char *requester ) { + BSONObj info; + { + dbtemprelease t; + connect(); + bool ok = conn->runCommand( "admin", BSON( "listDatabases" << 1 ), info ); + massert( 10385 , "Unable to get database list", ok ); + } + BSONObjIterator i( info.getField( "databases" ).embeddedObject() ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + string name = e.embeddedObject().getField( "name" ).valuestr(); + if ( !e.embeddedObject().getBoolField( "empty" ) ) { + if ( name != "local" ) { + if ( only.empty() || only == name ) { + resyncDrop( name.c_str(), requester ); + } + } + } + } + syncedTo = OpTime(); + addDbNextPass.clear(); + save(); + } + + string ReplSource::resyncDrop( const char *db, const char *requester ) { + log() << "resync: dropping database " << db << endl; + string dummyns = string( db ) + "."; + setClient(dummyns.c_str()); + assert( cc().database()->name == db ); + dropDatabase(dummyns.c_str()); + return dummyns; + } + + /* grab initial copy of a database from the master */ + bool ReplSource::resync(string db) { + string dummyNs = resyncDrop( db.c_str(), "internal" ); + setClient( dummyNs.c_str() ); + { + log() << "resync: cloning database " << db << endl; + ReplInfo r("resync: cloning a database"); + string errmsg; + bool ok = cloneFrom(hostName.c_str(), errmsg, cc().database()->name, false, /*slaveok*/ true, /*replauth*/ true, /*snapshot*/false); + if ( !ok ) { + problem() << "resync of " << db << " from " << hostName << " failed " << errmsg << endl; + throw SyncException(); + } + } + + log() << "resync: done " << db << endl; + + return true; + } + + void ReplSource::applyOperation(const BSONObj& op) { + log( 6 ) << "applying op: " << op << endl; + OpDebug debug; + BSONObj o = op.getObjectField("o"); + const char *ns = op.getStringField("ns"); + // operation type -- see logOp() comments for types + const char *opType = op.getStringField("op"); + try { + if ( *opType == 'i' ) { + const char *p = strchr(ns, '.'); + if ( p && strcmp(p, ".system.indexes") == 0 ) { + // updates aren't allowed for indexes -- so we will do a regular insert. if index already + // exists, that is ok. + theDataFileMgr.insert(ns, (void*) o.objdata(), o.objsize()); + } + else { + // do upserts for inserts as we might get replayed more than once + BSONElement _id; + if( !o.getObjectID(_id) ) { + /* No _id. This will be very slow. */ + Timer t; + updateObjects(ns, o, o, true, false, false , debug ); + if( t.millis() >= 2 ) { + RARELY OCCASIONALLY log() << "warning, repl doing slow updates (no _id field) for " << ns << endl; + } + } + else { + BSONObjBuilder b; + b.append(_id); + + /* erh 10/16/2009 - this is probably not relevant any more since its auto-created, but not worth removing */ + RARELY ensureHaveIdIndex(ns); // otherwise updates will be slow + + updateObjects(ns, o, b.done(), true, false, false , debug ); + } + } + } + else if ( *opType == 'u' ) { + RARELY ensureHaveIdIndex(ns); // otherwise updates will be super slow + updateObjects(ns, o, op.getObjectField("o2"), op.getBoolField("b"), false, false , debug ); + } + else if ( *opType == 'd' ) { + if ( opType[1] == 0 ) + deleteObjects(ns, o, op.getBoolField("b")); + else + assert( opType[1] == 'b' ); // "db" advertisement + } + else if ( *opType == 'n' ) { + // no op + } + else { + BufBuilder bb; + BSONObjBuilder ob; + assert( *opType == 'c' ); + _runCommands(ns, o, bb, ob, true, 0); + } + } + catch ( UserException& e ) { + log() << "sync: caught user assertion " << e << " while applying op: " << op << endl;; + } + catch ( DBException& e ) { + log() << "sync: caught db exception " << e << " while applying op: " << op << endl;; + } + } + + /* local.$oplog.main is of the form: + { ts: ..., op: <optype>, ns: ..., o: <obj> , o2: <extraobj>, b: <boolflag> } + ... + see logOp() comments. + */ + void ReplSource::sync_pullOpLog_applyOperation(BSONObj& op, OpTime *localLogTail) { + log( 6 ) << "processing op: " << op << endl; + // skip no-op + if ( op.getStringField( "op" )[ 0 ] == 'n' ) + return; + + char clientName[MaxDatabaseLen]; + const char *ns = op.getStringField("ns"); + nsToDatabase(ns, clientName); + + if ( *ns == '.' ) { + problem() << "skipping bad op in oplog: " << op.toString() << endl; + return; + } + else if ( *ns == 0 ) { + problem() << "halting replication, bad op in oplog:\n " << op.toString() << endl; + replAllDead = "bad object in oplog"; + throw SyncException(); + } + + if ( !only.empty() && only != clientName ) + return; + + dblock lk; + + if ( localLogTail && replPair && replPair->state == ReplPair::State_Master ) { + updateSetsWithLocalOps( *localLogTail, true ); // allow unlocking + updateSetsWithLocalOps( *localLogTail, false ); // don't allow unlocking or conversion to db backed storage + } + + if ( replAllDead ) { + // hmmm why is this check here and not at top of this function? does it get set between top and here? + log() << "replAllDead, throwing SyncException: " << replAllDead << endl; + throw SyncException(); + } + + bool justCreated; + try { + justCreated = setClient(ns); + } catch ( AssertionException& ) { + problem() << "skipping bad(?) op in oplog, setClient() failed, ns: '" << ns << "'\n"; + addDbNextPass.erase(clientName); + return; + } + + bool empty = cc().database()->isEmpty(); + bool incompleteClone = incompleteCloneDbs.count( clientName ) != 0; + + log( 6 ) << "ns: " << ns << ", justCreated: " << justCreated << ", empty: " << empty << ", incompleteClone: " << incompleteClone << endl; + + // always apply admin command command + // this is a bit hacky -- the semantics of replication/commands aren't well specified + if ( strcmp( clientName, "admin" ) == 0 && *op.getStringField( "op" ) == 'c' ) { + applyOperation( op ); + cc().clearns(); + return; + } + + if ( justCreated || empty || incompleteClone ) { + // we must add to incomplete list now that setClient has been called + incompleteCloneDbs.insert( clientName ); + if ( nClonedThisPass ) { + /* we only clone one database per pass, even if a lot need done. This helps us + avoid overflowing the master's transaction log by doing too much work before going + back to read more transactions. (Imagine a scenario of slave startup where we try to + clone 100 databases in one pass.) + */ + addDbNextPass.insert( clientName ); + } else { + if ( incompleteClone ) { + log() << "An earlier initial clone of '" << clientName << "' did not complete, now resyncing." << endl; + } + save(); + setClient( ns ); + nClonedThisPass++; + resync(cc().database()->name); + addDbNextPass.erase(clientName); + incompleteCloneDbs.erase( clientName ); + } + save(); + } else { + bool mod; + if ( replPair && replPair->state == ReplPair::State_Master ) { + BSONObj id = idForOp( op, mod ); + if ( !idTracker.haveId( ns, id ) ) { + applyOperation( op ); + } else if ( idTracker.haveModId( ns, id ) ) { + log( 6 ) << "skipping operation matching mod id object " << op << endl; + BSONObj existing; + if ( Helpers::findOne( ns, id, existing ) ) + logOp( "i", ns, existing ); + } else { + log( 6 ) << "skipping operation matching changed id object " << op << endl; + } + } else { + applyOperation( op ); + } + addDbNextPass.erase( clientName ); + } + cc().clearns(); + } + + BSONObj ReplSource::idForOp( const BSONObj &op, bool &mod ) { + mod = false; + const char *opType = op.getStringField( "op" ); + BSONObj o = op.getObjectField( "o" ); + switch( opType[ 0 ] ) { + case 'i': { + BSONObjBuilder idBuilder; + BSONElement id; + if ( !o.getObjectID( id ) ) + return BSONObj(); + idBuilder.append( id ); + return idBuilder.obj(); + } + case 'u': { + BSONObj o2 = op.getObjectField( "o2" ); + if ( strcmp( o2.firstElement().fieldName(), "_id" ) != 0 ) + return BSONObj(); + if ( o.firstElement().fieldName()[ 0 ] == '$' ) + mod = true; + return o2; + } + case 'd': { + if ( opType[ 1 ] != '\0' ) + return BSONObj(); // skip "db" op type + return o; + } + default: + break; + } + return BSONObj(); + } + + void ReplSource::updateSetsWithOp( const BSONObj &op, bool mayUnlock ) { + if ( mayUnlock ) { + idTracker.mayUpgradeStorage(); + } + bool mod; + BSONObj id = idForOp( op, mod ); + if ( !id.isEmpty() ) { + const char *ns = op.getStringField( "ns" ); + // Since our range of local ops may not be the same as our peer's + // range of unapplied ops, it is always necessary to rewrite objects + // to the oplog after a mod update. + if ( mod ) + idTracker.haveModId( ns, id, true ); + idTracker.haveId( ns, id, true ); + } + } + + void ReplSource::syncToTailOfRemoteLog() { + string _ns = ns(); + BSONObj last = conn->findOne( _ns.c_str(), Query().sort( BSON( "$natural" << -1 ) ) ); + if ( !last.isEmpty() ) { + BSONElement ts = last.findElement( "ts" ); + massert( 10386 , "non Date ts found", ts.type() == Date || ts.type() == Timestamp ); + syncedTo = OpTime( ts.date() ); + } + } + + OpTime ReplSource::nextLastSavedLocalTs() const { + setClient( "local.oplog.$main" ); + auto_ptr< Cursor > c = findTableScan( "local.oplog.$main", BSON( "$natural" << -1 ) ); + if ( c->ok() ) + return OpTime( c->current().getField( "ts" ).date() ); + return OpTime(); + } + + void ReplSource::setLastSavedLocalTs( const OpTime &nextLocalTs ) { + _lastSavedLocalTs = nextLocalTs; + log( 3 ) << "updated _lastSavedLocalTs to: " << _lastSavedLocalTs << endl; + } + + void ReplSource::resetSlave() { + massert( 10387 , "request to kill slave replication falied", + conn->simpleCommand( "admin", 0, "forcedead" ) ); + syncToTailOfRemoteLog(); + { + dblock lk; + setLastSavedLocalTs( nextLastSavedLocalTs() ); + save(); + cursor.reset(); + } + } + + bool ReplSource::updateSetsWithLocalOps( OpTime &localLogTail, bool mayUnlock ) { + setClient( "local.oplog.$main" ); + auto_ptr< Cursor > localLog = findTableScan( "local.oplog.$main", BSON( "$natural" << -1 ) ); + OpTime newTail; + for( ; localLog->ok(); localLog->advance() ) { + BSONObj op = localLog->current(); + OpTime ts( localLog->current().getField( "ts" ).date() ); + if ( newTail.isNull() ) { + newTail = ts; + } + if ( !( localLogTail < ts ) ) + break; + updateSetsWithOp( op, mayUnlock ); + if ( mayUnlock ) { + RARELY { + dbtemprelease t; + } + } + } + if ( !localLogTail.isNull() && !localLog->ok() ) { + // local log filled up + idTracker.reset(); + dbtemprelease t; + resetSlave(); + massert( 10388 , "local master log filled, forcing slave resync", false ); + } + if ( !newTail.isNull() ) + localLogTail = newTail; + return true; + } + + /* slave: pull some data from the master's oplog + note: not yet in db mutex at this point. + */ + bool ReplSource::sync_pullOpLog(int& nApplied) { + string ns = string("local.oplog.$") + sourceName(); + log(2) << "repl: sync_pullOpLog " << ns << " syncedTo:" << syncedTo.toStringLong() << '\n'; + + bool tailing = true; + DBClientCursor *c = cursor.get(); + if ( c && c->isDead() ) { + log() << "repl: old cursor isDead, initiating a new one\n"; + c = 0; + } + + if ( replPair && replPair->state == ReplPair::State_Master ) { + dblock lk; + idTracker.reset(); + } + OpTime localLogTail = _lastSavedLocalTs; + + bool initial = syncedTo.isNull(); + + if ( c == 0 || initial ) { + if ( initial ) { + // Important to grab last oplog timestamp before listing databases. + syncToTailOfRemoteLog(); + BSONObj info; + bool ok = conn->runCommand( "admin", BSON( "listDatabases" << 1 ), info ); + massert( 10389 , "Unable to get database list", ok ); + BSONObjIterator i( info.getField( "databases" ).embeddedObject() ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + string name = e.embeddedObject().getField( "name" ).valuestr(); + if ( !e.embeddedObject().getBoolField( "empty" ) ) { + if ( name != "local" ) { + if ( only.empty() || only == name ) { + log( 2 ) << "adding to 'addDbNextPass': " << name << endl; + addDbNextPass.insert( name ); + } + } + } + } + dblock lk; + save(); + } + + BSONObjBuilder q; + q.appendDate("$gte", syncedTo.asDate()); + BSONObjBuilder query; + query.append("ts", q.done()); + if ( !only.empty() ) { + // note we may here skip a LOT of data table scanning, a lot of work for the master. + query.appendRegex("ns", string("^") + only); + } + BSONObj queryObj = query.done(); + // queryObj = { ts: { $gte: syncedTo } } + + log(2) << "repl: " << ns << ".find(" << queryObj.toString() << ')' << '\n'; + cursor = conn->query( ns.c_str(), queryObj, 0, 0, 0, + QueryOption_CursorTailable | QueryOption_SlaveOk | QueryOption_OplogReplay | + QueryOption_AwaitData + ); + c = cursor.get(); + tailing = false; + } + else { + log(2) << "repl: tailing=true\n"; + } + + if ( c == 0 ) { + problem() << "repl: dbclient::query returns null (conn closed?)" << endl; + resetConnection(); + return false; + } + + // show any deferred database creates from a previous pass + { + set<string>::iterator i = addDbNextPass.begin(); + if ( i != addDbNextPass.end() ) { + BSONObjBuilder b; + b.append("ns", *i + '.'); + b.append("op", "db"); + BSONObj op = b.done(); + sync_pullOpLog_applyOperation(op, 0); + } + } + + if ( !c->more() ) { + if ( tailing ) { + log(2) << "repl: tailing & no new activity\n"; + } else { + log() << "repl: " << ns << " oplog is empty\n"; + } + { + dblock lk; + OpTime nextLastSaved = nextLastSavedLocalTs(); + { + dbtemprelease t; + if ( !c->more() ) { + setLastSavedLocalTs( nextLastSaved ); + } + } + save(); + } + return true; + } + + int n = 0; + BSONObj op = c->next(); + BSONElement ts = op.findElement("ts"); + if ( ts.type() != Date && ts.type() != Timestamp ) { + string err = op.getStringField("$err"); + if ( !err.empty() ) { + problem() << "repl: $err reading remote oplog: " + err << '\n'; + massert( 10390 , "got $err reading remote oplog", false ); + } + else { + problem() << "repl: bad object read from remote oplog: " << op.toString() << '\n'; + massert( 10391 , "repl: bad object read from remote oplog", false); + } + } + + if ( replPair && replPair->state == ReplPair::State_Master ) { + + OpTime nextOpTime( ts.date() ); + if ( !tailing && !initial && nextOpTime != syncedTo ) { + log() << "remote slave log filled, forcing slave resync" << endl; + resetSlave(); + return true; + } + + dblock lk; + updateSetsWithLocalOps( localLogTail, true ); + } + + OpTime nextOpTime( ts.date() ); + log(2) << "repl: first op time received: " << nextOpTime.toString() << '\n'; + if ( tailing || initial ) { + if ( initial ) + log(1) << "repl: initial run\n"; + else + assert( syncedTo < nextOpTime ); + sync_pullOpLog_applyOperation(op, &localLogTail); + n++; + } + else if ( nextOpTime != syncedTo ) { + Nullstream& l = log(); + l << "repl: nextOpTime " << nextOpTime.toStringLong() << ' '; + if ( nextOpTime < syncedTo ) + l << "<??"; + else + l << ">"; + + l << " syncedTo " << syncedTo.toStringLong() << '\n'; + log() << "repl: time diff: " << (nextOpTime.getSecs() - syncedTo.getSecs()) << "sec\n"; + log() << "repl: tailing: " << tailing << '\n'; + log() << "repl: data too stale, halting replication" << endl; + replInfo = replAllDead = "data too stale halted replication"; + assert( syncedTo < nextOpTime ); + throw SyncException(); + } + else { + /* t == syncedTo, so the first op was applied previously. */ + } + + // apply operations + { + time_t saveLast = time(0); + while ( 1 ) { + /* from a.s.: + I think the idea here is that we can establish a sync point between the local op log and the remote log with the following steps: + + 1) identify most recent op in local log -- call it O + 2) ask "does nextOpTime reflect the tail of the remote op log?" (in other words, is more() false?) - If yes, all subsequent ops after nextOpTime in the remote log must have occurred after O. If no, we can't establish a sync point. + + Note that we can't do step (2) followed by step (1) because if we do so ops may be added to both machines between steps (2) and (1) and we can't establish a sync point. (In particular, between (2) and (1) an op may be added to the remote log before a different op is added to the local log. In this case, the newest remote op will have occurred after nextOpTime but before O.) + + Now, for performance reasons we don't want to have to identify the most recent op in the local log every time we call c->more() because in performance sensitive situations more() will be true most of the time. So we do: + + 0) more()? + 1) find most recent op in local log + 2) more()? + */ + if ( !c->more() ) { + dblock lk; + OpTime nextLastSaved = nextLastSavedLocalTs(); // this may make c->more() become true + { + dbtemprelease t; + if ( c->more() ) { + continue; + } else { + setLastSavedLocalTs( nextLastSaved ); + } + } + syncedTo = nextOpTime; + save(); // note how far we are synced up to now + log() << "repl: applied " << n << " operations" << endl; + nApplied = n; + log() << "repl: end sync_pullOpLog syncedTo: " << syncedTo.toStringLong() << endl; + break; + } + + OCCASIONALLY if( n > 100000 || time(0) - saveLast > 60 ) { + // periodically note our progress, in case we are doing a lot of work and crash + dblock lk; + syncedTo = nextOpTime; + // can't update local log ts since there are pending operations from our peer + save(); + log() << "repl: checkpoint applied " << n << " operations" << endl; + log() << "repl: syncedTo: " << syncedTo.toStringLong() << endl; + saveLast = time(0); + n = 0; + } + + BSONObj op = c->next(); + ts = op.findElement("ts"); + assert( ts.type() == Date || ts.type() == Timestamp ); + OpTime last = nextOpTime; + OpTime tmp( ts.date() ); + nextOpTime = tmp; + if ( !( last < nextOpTime ) ) { + problem() << "sync error: last " << last.toString() << " >= nextOpTime " << nextOpTime.toString() << endl; + uassert( 10123 , "bad 'ts' value in sources", false); + } + + sync_pullOpLog_applyOperation(op, &localLogTail); + n++; + } + } + + return true; + } + + BSONObj userReplQuery = fromjson("{\"user\":\"repl\"}"); + + bool replAuthenticate(DBClientConnection *conn) { + AuthenticationInfo *ai = currentClient.get()->ai; + if( !ai->isAuthorized("admin") ) { + log() << "replauthenticate: requires admin permissions, failing\n"; + return false; + } + + BSONObj user; + { + dblock lk; + Client::Context ctxt("local."); + if( !Helpers::findOne("local.system.users", userReplQuery, user) ) { + // try the first user is local + if( !Helpers::getSingleton("local.system.users", user) ) { + if( noauth ) + return true; // presumably we are running a --noauth setup all around. + + log() << "replauthenticate: no user in local.system.users to use for authentication\n"; + return false; + } + } + } + + string u = user.getStringField("user"); + string p = user.getStringField("pwd"); + massert( 10392 , "bad user object? [1]", !u.empty()); + massert( 10393 , "bad user object? [2]", !p.empty()); + string err; + if( !conn->auth("local", u.c_str(), p.c_str(), err, false) ) { + log() << "replauthenticate: can't authenticate to master server, user:" << u << endl; + return false; + } + return true; + } + + bool ReplSource::connect() { + if ( conn.get() == 0 ) { + conn = auto_ptr<DBClientConnection>(new DBClientConnection()); + string errmsg; + ReplInfo r("trying to connect to sync source"); + if ( !conn->connect(hostName.c_str(), errmsg) || !replAuthenticate(conn.get()) ) { + resetConnection(); + log() << "repl: " << errmsg << endl; + return false; + } + } + return true; + } + + /* note: not yet in mutex at this point. + returns true if everything happy. return false if you want to reconnect. + */ + bool ReplSource::sync(int& nApplied) { + ReplInfo r("sync"); + if ( !cmdLine.quiet ) + log() << "repl: " << sourceName() << '@' << hostName << endl; + nClonedThisPass = 0; + + // FIXME Handle cases where this db isn't on default port, or default port is spec'd in hostName. + if ( (string("localhost") == hostName || string("127.0.0.1") == hostName) && cmdLine.port == CmdLine::DefaultDBPort ) { + log() << "repl: can't sync from self (localhost). sources configuration may be wrong." << endl; + sleepsecs(5); + return false; + } + + if ( !connect() ) { + if ( replPair && paired ) { + assert( startsWith(hostName.c_str(), replPair->remoteHost.c_str()) ); + replPair->arbitrate(); + } + { + ReplInfo r("can't connect to sync source"); + } + return false; + } + + if ( paired ) { + int remote = replPair->negotiate(conn.get(), "direct"); + int nMasters = ( remote == ReplPair::State_Master ) + ( replPair->state == ReplPair::State_Master ); + if ( getInitialSyncCompleted() && nMasters != 1 ) { + log() << ( nMasters == 0 ? "no master" : "two masters" ) << ", deferring oplog pull" << endl; + return true; + } + } + + /* + // get current mtime at the server. + BSONObj o = conn->findOne("admin.$cmd", opTimeQuery); + BSONElement e = o.findElement("optime"); + if( e.eoo() ) { + log() << "repl: failed to get cur optime from master" << endl; + log() << " " << o.toString() << endl; + return false; + } + uassert( 10124 , e.type() == Date ); + OpTime serverCurTime; + serverCurTime.asDate() = e.date(); + */ + return sync_pullOpLog(nApplied); + } + + /* -- Logging of operations -------------------------------------*/ + +// cached copies of these...so don't rename them + NamespaceDetails *localOplogMainDetails = 0; + Database *localOplogClient = 0; + + void logOp(const char *opstr, const char *ns, const BSONObj& obj, BSONObj *patt, bool *b) { + if ( master ) { + _logOp(opstr, ns, "local.oplog.$main", obj, patt, b, OpTime::now()); + char cl[ 256 ]; + nsToDatabase( ns, cl ); + } + NamespaceDetailsTransient &t = NamespaceDetailsTransient::get_w( ns ); + if ( t.cllEnabled() ) { + try { + _logOp(opstr, ns, t.cllNS().c_str(), obj, patt, b, OpTime::now()); + } catch ( const DBException & ) { + t.cllInvalidate(); + } + } + } + + /* we write to local.opload.$main: + { ts : ..., op: ..., ns: ..., o: ... } + ts: an OpTime timestamp + op: + "i" insert + "u" update + "d" delete + "c" db cmd + "db" declares presence of a database (ns is set to the db name + '.') + "n" no op + bb: + if not null, specifies a boolean to pass along to the other side as b: param. + used for "justOne" or "upsert" flags on 'd', 'u' + first: true + when set, indicates this is the first thing we have logged for this database. + thus, the slave does not need to copy down all the data when it sees this. + */ + void _logOp(const char *opstr, const char *ns, const char *logNS, const BSONObj& obj, BSONObj *o2, bool *bb, const OpTime &ts ) { + if ( strncmp(ns, "local.", 6) == 0 ) + return; + + DEV assertInWriteLock(); + + Client::Context context; + + /* we jump through a bunch of hoops here to avoid copying the obj buffer twice -- + instead we do a single copy to the destination position in the memory mapped file. + */ + + BSONObjBuilder b; + b.appendTimestamp("ts", ts.asDate()); + b.append("op", opstr); + b.append("ns", ns); + if ( bb ) + b.appendBool("b", *bb); + if ( o2 ) + b.append("o2", *o2); + BSONObj partial = b.done(); + int posz = partial.objsize(); + int len = posz + obj.objsize() + 1 + 2 /*o:*/; + + Record *r; + if ( strncmp( logNS, "local.", 6 ) == 0 ) { // For now, assume this is olog main + if ( localOplogMainDetails == 0 ) { + setClient("local."); + localOplogClient = cc().database(); + localOplogMainDetails = nsdetails(logNS); + } + cc().setns("", localOplogClient); // database = localOplogClient; + r = theDataFileMgr.fast_oplog_insert(localOplogMainDetails, logNS, len); + } else { + setClient( logNS ); + assert( nsdetails( logNS ) ); + r = theDataFileMgr.fast_oplog_insert( nsdetails( logNS ), logNS, len); + } + + char *p = r->data; + memcpy(p, partial.objdata(), posz); + *((unsigned *)p) += obj.objsize() + 1 + 2; + p += posz - 1; + *p++ = (char) Object; + *p++ = 'o'; + *p++ = 0; + memcpy(p, obj.objdata(), obj.objsize()); + p += obj.objsize(); + *p = EOO; + + if ( logLevel >= 6 ) { + BSONObj temp(r); + log( 6 ) << "logging op:" << temp << endl; + } + } + + /* --------------------------------------------------------------*/ + + /* + TODO: + _ source has autoptr to the cursor + _ reuse that cursor when we can + */ + + /* returns: # of seconds to sleep before next pass + 0 = no sleep recommended + 1 = special sentinel indicating adaptive sleep recommended + */ + int _replMain(ReplSource::SourceVector& sources, int& nApplied) { + { + ReplInfo r("replMain load sources"); + dblock lk; + ReplSource::loadAll(sources); + } + + if ( sources.empty() ) { + /* replication is not configured yet (for --slave) in local.sources. Poll for config it + every 20 seconds. + */ + return 20; + } + + int sleepAdvice = 1; + for ( ReplSource::SourceVector::iterator i = sources.begin(); i != sources.end(); i++ ) { + ReplSource *s = i->get(); + bool ok = false; + try { + ok = s->sync(nApplied); + bool moreToSync = s->haveMoreDbsToSync(); + if( !ok ) { + sleepAdvice = 3; + } + else if( moreToSync ) { + sleepAdvice = 0; + } + if ( ok && !moreToSync /*&& !s->syncedTo.isNull()*/ ) { + pairSync->setInitialSyncCompletedLocking(); + } + } + catch ( const SyncException& ) { + log() << "caught SyncException" << endl; + return 10; + } + catch ( AssertionException& e ) { + if ( e.severe() ) { + log() << "replMain AssertionException " << e.what() << endl; + return 60; + } + else { + log() << "repl: AssertionException " << e.what() << '\n'; + } + replInfo = "replMain caught AssertionException"; + } + catch ( const DBException& e ) { + log() << "repl: DBException " << e.what() << endl; + replInfo = "replMain caught DBException"; + } + catch ( const std::exception &e ) { + log() << "repl: std::exception " << e.what() << endl; + replInfo = "replMain caught std::exception"; + } + catch ( ... ) { + log() << "unexpected exception during replication. replication will halt" << endl; + replAllDead = "caught unexpected exception during replication"; + } + if ( !ok ) + s->resetConnection(); + } + return sleepAdvice; + } + + void replMain() { + ReplSource::SourceVector sources; + while ( 1 ) { + int s = 0; + { + dblock lk; + if ( replAllDead ) { + if ( !autoresync || !ReplSource::throttledForceResyncDead( "auto" ) ) + break; + } + assert( syncing == 0 ); + syncing++; + } + try { + int nApplied = 0; + s = _replMain(sources, nApplied); + if( s == 1 ) { + if( nApplied == 0 ) s = 2; + else if( nApplied > 100 ) { + // sleep very little - just enought that we aren't truly hammering master + sleepmillis(75); + s = 0; + } + } + } catch (...) { + out() << "caught exception in _replMain" << endl; + s = 4; + } + { + dblock lk; + assert( syncing == 1 ); + syncing--; + } + if ( s ) { + stringstream ss; + ss << "repl: sleep " << s << "sec before next pass"; + string msg = ss.str(); + log() << msg << endl; + ReplInfo r(msg.c_str()); + sleepsecs(s); + } + } + } + + int debug_stop_repl = 0; + + void replSlaveThread() { + sleepsecs(1); + + { + dblock lk; + + Client::initThread("replslave"); + currentClient.get()->ai->authorize("admin"); + + BSONObj obj; + if ( Helpers::getSingleton("local.pair.startup", obj) ) { + // should be: {replacepeer:1} + replacePeer = true; + pairSync->setInitialSyncCompleted(); // we are the half that has all the data + } + } + + while ( 1 ) { + try { + replMain(); + if ( debug_stop_repl ) + break; + sleepsecs(5); + } + catch ( AssertionException& ) { + ReplInfo r("Assertion in replSlaveThread(): sleeping 5 minutes before retry"); + problem() << "Assertion in replSlaveThread(): sleeping 5 minutes before retry" << endl; + sleepsecs(300); + } + } + } + + void tempThread() { + while ( 1 ) { + out() << dbMutex.info().isLocked() << endl; + sleepmillis(100); + } + } + + void createOplog() { + dblock lk; + + const char * ns = "local.oplog.$main"; + setClient(ns); + + if ( nsdetails( ns ) ) + return; + + /* create an oplog collection, if it doesn't yet exist. */ + BSONObjBuilder b; + double sz; + if ( cmdLine.oplogSize != 0 ) + sz = (double)cmdLine.oplogSize; + else { + sz = 50.0 * 1000 * 1000; + if ( sizeof(int *) >= 8 ) { + sz = 990.0 * 1000 * 1000; + boost::intmax_t free = freeSpace(); //-1 if call not supported. + double fivePct = free * 0.05; + if ( fivePct > sz ) + sz = fivePct; + } + } + + log() << "******\n"; + log() << "creating replication oplog of size: " << (int)( sz / ( 1024 * 1024 ) ) << "MB (use --oplogSize to change)\n"; + log() << "******" << endl; + + b.append("size", sz); + b.appendBool("capped", 1); + b.appendBool("autoIndexId", false); + + string err; + BSONObj o = b.done(); + userCreateNS(ns, o, err, false); + logOp( "n", "dummy", BSONObj() ); + cc().clearns(); + } + + void startReplication() { + /* this was just to see if anything locks for longer than it should -- we need to be careful + not to be locked when trying to connect() or query() the other side. + */ + //boost::thread tempt(tempThread); + + if ( !slave && !master && !replPair ) + return; + + { + dblock lk; + pairSync->init(); + } + + if ( slave || replPair ) { + if ( slave ) { + assert( slave == SimpleSlave ); + log(1) << "slave=true" << endl; + } + else + slave = ReplPairSlave; + boost::thread repl_thread(replSlaveThread); + } + + if ( master || replPair ) { + if ( master ) + log(1) << "master=true" << endl; + master = true; + createOplog(); + } + } + + /* called from main at server startup */ + void pairWith(const char *remoteEnd, const char *arb) { + replPair = new ReplPair(remoteEnd, arb); + } + + class CmdLogCollection : public Command { + public: + virtual bool slaveOk() { + return false; + } + CmdLogCollection() : Command( "logCollection" ) {} + virtual void help( stringstream &help ) const { + help << "examples: { logCollection: <collection ns>, start: 1 }, " + << "{ logCollection: <collection ns>, validateComplete: 1 }"; + } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + string logCollection = cmdObj.getStringField( "logCollection" ); + if ( logCollection.empty() ) { + errmsg = "missing logCollection spec"; + return false; + } + bool start = !cmdObj.getField( "start" ).eoo(); + bool validateComplete = !cmdObj.getField( "validateComplete" ).eoo(); + if ( start ? validateComplete : !validateComplete ) { + errmsg = "Must specify exactly one of start:1 or validateComplete:1"; + return false; + } + int logSizeMb = cmdObj.getIntField( "logSizeMb" ); + NamespaceDetailsTransient &t = NamespaceDetailsTransient::get_w( logCollection.c_str() ); + if ( start ) { + if ( t.cllNS().empty() ) { + if ( logSizeMb == INT_MIN ) { + t.cllStart(); + } else { + t.cllStart( logSizeMb ); + } + } else { + errmsg = "Log already started for ns: " + logCollection; + return false; + } + } else { + if ( t.cllNS().empty() ) { + errmsg = "No log to validateComplete for ns: " + logCollection; + return false; + } else { + if ( !t.cllValidateComplete() ) { + errmsg = "Oplog failure, insufficient space allocated"; + return false; + } + } + } + log() << "started logCollection with cmd obj: " << cmdObj << endl; + return true; + } + } cmdlogcollection; + +} // namespace mongo diff --git a/db/repl.h b/db/repl.h new file mode 100644 index 0000000..a4c1737 --- /dev/null +++ b/db/repl.h @@ -0,0 +1,315 @@ +// repl.h - replication + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* replication data overview + + at the slave: + local.sources { host: ..., source: ..., only: ..., syncedTo: ..., localLogTs: ..., dbsNextPass: { ... }, incompleteCloneDbs: { ... } } + + at the master: + local.oplog.$<source> + local.oplog.$main is the default +*/ + +#pragma once + +#include "pdfile.h" +#include "db.h" +#include "dbhelpers.h" +#include "query.h" + +#include "../client/dbclient.h" + +#include "../util/optime.h" + +namespace mongo { + + class DBClientConnection; + class DBClientCursor; + + /* replication slave? (possibly with slave or repl pair nonmaster) + --slave cmd line setting -> SimpleSlave + */ + typedef enum { NotSlave=0, SimpleSlave, ReplPairSlave } SlaveTypes; + extern SlaveTypes slave; + + /* true means we are master and doing replication. if we are not writing to oplog (no --master or repl pairing), + this won't be true. + */ + extern bool master; + + extern int opIdMem; + + bool cloneFrom(const char *masterHost, string& errmsg, const string& fromdb, bool logForReplication, + bool slaveOk, bool useReplAuth, bool snapshot); + + /* A replication exception */ + class SyncException : public DBException { + public: + virtual const char* what() const throw() { return "sync exception"; } + virtual int getCode(){ return 10001; } + }; + + /* A Source is a source from which we can pull (replicate) data. + stored in collection local.sources. + + Can be a group of things to replicate for several databases. + + { host: ..., source: ..., only: ..., syncedTo: ..., localLogTs: ..., dbsNextPass: { ... }, incompleteCloneDbs: { ... } } + + 'source' defaults to 'main'; support for multiple source names is + not done (always use main for now). + */ + class ReplSource { + bool resync(string db); + + /* pull some operations from the master's oplog, and apply them. */ + bool sync_pullOpLog(int& nApplied); + + void sync_pullOpLog_applyOperation(BSONObj& op, OpTime *localLogTail); + + auto_ptr<DBClientConnection> conn; + auto_ptr<DBClientCursor> cursor; + + /* we only clone one database per pass, even if a lot need done. This helps us + avoid overflowing the master's transaction log by doing too much work before going + back to read more transactions. (Imagine a scenario of slave startup where we try to + clone 100 databases in one pass.) + */ + set<string> addDbNextPass; + + set<string> incompleteCloneDbs; + + ReplSource(); + + // returns the dummy ns used to do the drop + string resyncDrop( const char *db, const char *requester ); + // returns true if connected on return + bool connect(); + // returns possibly unowned id spec for the operation. + static BSONObj idForOp( const BSONObj &op, bool &mod ); + static void updateSetsWithOp( const BSONObj &op, bool mayUpdateStorage ); + // call without the db mutex + void syncToTailOfRemoteLog(); + // call with the db mutex + OpTime nextLastSavedLocalTs() const; + void setLastSavedLocalTs( const OpTime &nextLocalTs ); + // call without the db mutex + void resetSlave(); + // call with the db mutex + // returns false if the slave has been reset + bool updateSetsWithLocalOps( OpTime &localLogTail, bool mayUnlock ); + string ns() const { return string( "local.oplog.$" ) + sourceName(); } + + public: + static void applyOperation(const BSONObj& op); + bool replacing; // in "replace mode" -- see CmdReplacePeer + bool paired; // --pair in use + string hostName; // ip addr or hostname plus optionally, ":<port>" + string _sourceName; // a logical source name. + string sourceName() const { + return _sourceName.empty() ? "main" : _sourceName; + } + string only; // only a certain db. note that in the sources collection, this may not be changed once you start replicating. + + /* the last time point we have already synced up to (in the remote/master's oplog). */ + OpTime syncedTo; + + /* This is for repl pairs. + _lastSavedLocalTs is the most recent point in the local log that we know is consistent
+ with the remote log ( ie say the local op log has entries ABCDE and the remote op log
+ has ABCXY, then _lastSavedLocalTs won't be greater than C until we have reconciled
+ the DE-XY difference.)
+ */
+ OpTime _lastSavedLocalTs; + + int nClonedThisPass; + + typedef vector< shared_ptr< ReplSource > > SourceVector; + static void loadAll(SourceVector&); + explicit ReplSource(BSONObj); + bool sync(int& nApplied); + void save(); // write ourself to local.sources + void resetConnection() { + cursor = auto_ptr<DBClientCursor>(0); + conn = auto_ptr<DBClientConnection>(0); + } + + // make a jsobj from our member fields of the form + // { host: ..., source: ..., syncedTo: ... } + BSONObj jsobj(); + + bool operator==(const ReplSource&r) const { + return hostName == r.hostName && sourceName() == r.sourceName(); + } + operator string() const { return sourceName() + "@" + hostName; } + + bool haveMoreDbsToSync() const { return !addDbNextPass.empty(); } + + static bool throttledForceResyncDead( const char *requester ); + static void forceResyncDead( const char *requester ); + void forceResync( const char *requester ); + }; + + /* Write operation to the log (local.oplog.$main) + "i" insert + "u" update + "d" delete + "c" db cmd + "db" declares presence of a database (ns is set to the db name + '.') + */ + void _logOp(const char *opstr, const char *ns, const char *logNs, const BSONObj& obj, BSONObj *patt, bool *b, const OpTime &ts); + void logOp(const char *opstr, const char *ns, const BSONObj& obj, BSONObj *patt = 0, bool *b = 0); + + // class for managing a set of ids in memory + class MemIds { + public: + MemIds() : size_() {} + friend class IdTracker; + void reset() { imp_.clear(); } + bool get( const char *ns, const BSONObj &id ) { return imp_[ ns ].count( id ); } + void set( const char *ns, const BSONObj &id, bool val ) { + if ( val ) { + if ( imp_[ ns ].insert( id.getOwned() ).second ) { + size_ += id.objsize() + sizeof( BSONObj ); + } + } else { + if ( imp_[ ns ].erase( id ) == 1 ) { + size_ -= id.objsize() + sizeof( BSONObj ); + } + } + } + long long roughSize() const { + return size_; + } + private: + typedef map< string, BSONObjSetDefaultOrder > IdSets; + IdSets imp_; + long long size_; + }; + + // class for managing a set of ids in a db collection + // All functions must be called with db mutex held + class DbIds { + public: + DbIds( const string & name ) : impl_( name, BSON( "ns" << 1 << "id" << 1 ) ) {} + void reset() { + impl_.reset(); + } + bool get( const char *ns, const BSONObj &id ) { + return impl_.get( key( ns, id ) ); + } + void set( const char *ns, const BSONObj &id, bool val ) { + impl_.set( key( ns, id ), val ); + } + private: + static BSONObj key( const char *ns, const BSONObj &id ) { + BSONObjBuilder b; + b << "ns" << ns; + // rename _id to id since there may be duplicates + b.appendAs( id.firstElement(), "id" ); + return b.obj(); + } + DbSet impl_; + }; + + // class for tracking ids and mod ids, in memory or on disk + // All functions must be called with db mutex held + // Kind of sloppy class structure, for now just want to keep the in mem + // version speedy. + // see http://www.mongodb.org/display/DOCS/Pairing+Internals + class IdTracker { + public: + IdTracker() : + dbIds_( "local.temp.replIds" ), + dbModIds_( "local.temp.replModIds" ), + inMem_( true ), + maxMem_( opIdMem ) { + } + void reset( int maxMem = opIdMem ) { + memIds_.reset(); + memModIds_.reset(); + dbIds_.reset(); + dbModIds_.reset(); + maxMem_ = maxMem; + inMem_ = true; + } + bool haveId( const char *ns, const BSONObj &id ) { + if ( inMem_ ) + return get( memIds_, ns, id ); + else + return get( dbIds_, ns, id ); + } + bool haveModId( const char *ns, const BSONObj &id ) { + if ( inMem_ ) + return get( memModIds_, ns, id ); + else + return get( dbModIds_, ns, id ); + } + void haveId( const char *ns, const BSONObj &id, bool val ) { + if ( inMem_ ) + set( memIds_, ns, id, val ); + else + set( dbIds_, ns, id, val ); + } + void haveModId( const char *ns, const BSONObj &id, bool val ) { + if ( inMem_ ) + set( memModIds_, ns, id, val ); + else + set( dbModIds_, ns, id, val ); + } + // will release the db mutex + void mayUpgradeStorage() { + if ( !inMem_ || memIds_.roughSize() + memModIds_.roughSize() <= maxMem_ ) + return; + log() << "saving master modified id information to collection" << endl; + upgrade( memIds_, dbIds_ ); + upgrade( memModIds_, dbModIds_ ); + memIds_.reset(); + memModIds_.reset(); + inMem_ = false; + } + bool inMem() const { return inMem_; } + private: + template< class T > + bool get( T &ids, const char *ns, const BSONObj &id ) { + return ids.get( ns, id ); + } + template< class T > + void set( T &ids, const char *ns, const BSONObj &id, bool val ) { + ids.set( ns, id, val ); + } + void upgrade( MemIds &a, DbIds &b ) { + for( MemIds::IdSets::const_iterator i = a.imp_.begin(); i != a.imp_.end(); ++i ) { + for( BSONObjSetDefaultOrder::const_iterator j = i->second.begin(); j != i->second.end(); ++j ) { + set( b, i->first.c_str(), *j, true ); + RARELY { + dbtemprelease t; + } + } + } + } + MemIds memIds_; + MemIds memModIds_; + DbIds dbIds_; + DbIds dbModIds_; + bool inMem_; + int maxMem_; + }; + +} // namespace mongo diff --git a/db/replset.h b/db/replset.h new file mode 100644 index 0000000..98d80d6 --- /dev/null +++ b/db/replset.h @@ -0,0 +1,207 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "db.h" +#include "dbhelpers.h" +#include "json.h" +#include "../client/dbclient.h" +#include "repl.h" +#include "cmdline.h" + +namespace mongo { + + extern const char *replAllDead; + + /* ReplPair is a pair of db servers replicating to one another and cooperating. + + Only one member of the pair is active at a time; so this is a smart master/slave + configuration basically. + + You may read from the slave at anytime though (if you don't mind the slight lag). + + todo: Could be extended to be more than a pair, thus the name 'Set' -- for example, + a set of 3... + */ + + class ReplPair { + public: + enum ReplState { + State_CantArb = -3, + State_Confused = -2, + State_Negotiating = -1, + State_Slave = 0, + State_Master = 1 + }; + + int state; + string info; // commentary about our current state + string arbHost; // "-" for no arbiter. "host[:port]" + int remotePort; + string remoteHost; + string remote; // host:port if port specified. +// int date; // -1 not yet set; 0=slave; 1=master + + string getInfo() { + stringstream ss; + ss << " state: "; + if ( state == 1 ) ss << "1 State_Master "; + else if ( state == 0 ) ss << "0 State_Slave"; + else + ss << "<b>" << state << "</b>"; + ss << '\n'; + ss << " info: " << info << '\n'; + ss << " arbhost: " << arbHost << '\n'; + ss << " remote: " << remoteHost << ':' << remotePort << '\n'; +// ss << " date: " << date << '\n'; + return ss.str(); + } + + ReplPair(const char *remoteEnd, const char *arbiter); + virtual ~ReplPair() {} + + bool dominant(const string& myname) { + if ( myname == remoteHost ) + return cmdLine.port > remotePort; + return myname > remoteHost; + } + + void setMasterLocked( int n, const char *_comment = "" ) { + dblock p; + setMaster( n, _comment ); + } + + void setMaster(int n, const char *_comment = ""); + + /* negotiate with our peer who is master; returns state of peer */ + int negotiate(DBClientConnection *conn, string method); + + /* peer unreachable, try our arbitrator */ + void arbitrate(); + + virtual + DBClientConnection *newClientConnection() const { + return new DBClientConnection(); + } + }; + + extern ReplPair *replPair; + + /* note we always return true for the "local" namespace. + + we should not allow most operations when not the master + also we report not master if we are "dead". + + See also CmdIsMaster. + + If 'client' is not specified, the current client is used. + */ + inline bool isMaster( const char *client = 0 ) { + if( !slave ) + return true; + + if ( !client ) { + Database *database = cc().database(); + assert( database ); + client = database->name.c_str(); + } + + if ( replAllDead ) + return strcmp( client, "local" ) == 0; + + if ( replPair ) { + if( replPair->state == ReplPair::State_Master ) + return true; + } + else { + if( master ) { + // if running with --master --slave, allow. note that master is also true + // for repl pairs so the check for replPair above is important. + return true; + } + } + + if ( cc().isGod() ) + return true; + + return strcmp( client, "local" ) == 0; + } + inline bool isMasterNs( const char *ns ) { + char cl[ 256 ]; + nsToDatabase( ns, cl ); + return isMaster( cl ); + } + + inline ReplPair::ReplPair(const char *remoteEnd, const char *arb) { + state = -1; + remote = remoteEnd; + remotePort = CmdLine::DefaultDBPort; + remoteHost = remoteEnd; + const char *p = strchr(remoteEnd, ':'); + if ( p ) { + remoteHost = string(remoteEnd, p-remoteEnd); + remotePort = atoi(p+1); + uassert( 10125 , "bad port #", remotePort > 0 && remotePort < 0x10000 ); + if ( remotePort == CmdLine::DefaultDBPort ) + remote = remoteHost; // don't include ":27017" as it is default; in case ran in diff ways over time to normalizke the hostname format in sources collection + } + + uassert( 10126 , "arbiter parm is missing, use '-' for none", arb); + arbHost = arb; + uassert( 10127 , "arbiter parm is empty", !arbHost.empty()); + } + + /* This is set to true if we have EVER been up to date -- this way a new pair member + which is a replacement won't go online as master until we have initially fully synced. + */ + class PairSync { + int initialsynccomplete; + public: + PairSync() { + initialsynccomplete = -1; + } + + /* call before using the class. from dbmutex */ + void init() { + BSONObj o; + initialsynccomplete = 0; + if ( Helpers::getSingleton("local.pair.sync", o) ) + initialsynccomplete = 1; + } + + bool initialSyncCompleted() { + return initialsynccomplete != 0; + } + + void setInitialSyncCompleted() { + BSONObj o = fromjson("{\"initialsynccomplete\":1}"); + Helpers::putSingleton("local.pair.sync", o); + initialsynccomplete = 1; + } + + void setInitialSyncCompletedLocking() { + if ( initialsynccomplete == 1 ) + return; + dblock lk; + BSONObj o = fromjson("{\"initialsynccomplete\":1}"); + Helpers::putSingleton("local.pair.sync", o); + initialsynccomplete = 1; + } + }; + + +} // namespace mongo diff --git a/db/resource.h b/db/resource.h new file mode 100644 index 0000000..59b9f5c --- /dev/null +++ b/db/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by db.rc + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace mongo { + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif + +} // namespace mongo diff --git a/db/scanandorder.h b/db/scanandorder.h new file mode 100644 index 0000000..3f41433 --- /dev/null +++ b/db/scanandorder.h @@ -0,0 +1,148 @@ +/* scanandorder.h + Order results (that aren't already indexes and in order.) +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /* todo: + _ handle compound keys with differing directions. we don't handle this yet: neither here nor in indexes i think!!! + _ limit amount of data + */ + + /* see also IndexDetails::getKeysFromObject, which needs some merging with this. */ + + class KeyType : boost::noncopyable { + public: + BSONObj pattern; // e.g., { ts : -1 } + public: + KeyType(BSONObj _keyPattern) { + pattern = _keyPattern; + assert( !pattern.isEmpty() ); + } + + // returns the key value for o + BSONObj getKeyFromObject(BSONObj o) { + return o.extractFields(pattern); + } + }; + + /* todo: + _ respect limit + _ check for excess mem usage + _ response size limit from runquery; push it up a bit. + */ + + inline void fillQueryResultFromObj(BufBuilder& bb, FieldMatcher *filter, BSONObj& js) { + if ( filter ) { + BSONObjBuilder b( bb ); + BSONObjIterator i( js ); + bool gotId = false; + while ( i.more() ){ + BSONElement e = i.next(); + const char * fname = e.fieldName(); + + if ( strcmp( fname , "_id" ) == 0 ){ + b.append( e ); + gotId = true; + } else { + filter->append( b , e ); + } + } + b.done(); + } else { + bb.append((void*) js.objdata(), js.objsize()); + } + } + + typedef multimap<BSONObj,BSONObj,BSONObjCmp> BestMap; + class ScanAndOrder { + BestMap best; // key -> full object + int startFrom; + int limit; // max to send back. + KeyType order; + unsigned approxSize; + + void _add(BSONObj& k, BSONObj o) { + best.insert(make_pair(k,o)); + } + + void _addIfBetter(BSONObj& k, BSONObj o, BestMap::iterator i) { + const BSONObj& worstBestKey = i->first; + int c = worstBestKey.woCompare(k, order.pattern); + if ( c > 0 ) { + // k is better, 'upgrade' + best.erase(i); + _add(k, o); + } + } + + public: + ScanAndOrder(int _startFrom, int _limit, BSONObj _order) : + best( BSONObjCmp( _order ) ), + startFrom(_startFrom), order(_order) { + limit = _limit > 0 ? _limit + startFrom : 0x7fffffff; + approxSize = 0; + } + + int size() const { + return best.size(); + } + + void add(BSONObj o) { + BSONObj k = order.getKeyFromObject(o); + if ( (int) best.size() < limit ) { + approxSize += k.objsize(); + uassert( 10128 , "too much key data for sort() with no index. add an index or specify a smaller limit", approxSize < 1 * 1024 * 1024 ); + _add(k, o); + return; + } + BestMap::iterator i; + assert( best.end() != best.begin() ); + i = best.end(); + i--; + _addIfBetter(k, o, i); + } + + void _fill(BufBuilder& b, FieldMatcher *filter, int& nout, BestMap::iterator begin, BestMap::iterator end) { + int n = 0; + int nFilled = 0; + for ( BestMap::iterator i = begin; i != end; i++ ) { + n++; + if ( n <= startFrom ) + continue; + BSONObj& o = i->second; + fillQueryResultFromObj(b, filter, o); + nFilled++; + if ( nFilled >= limit ) + break; + uassert( 10129 , "too much data for sort() with no index", b.len() < 4000000 ); // appserver limit + } + nout = nFilled; + } + + /* scanning complete. stick the query result in b for n objects. */ + void fill(BufBuilder& b, FieldMatcher *filter, int& nout) { + _fill(b, filter, nout, best.begin(), best.end()); + } + + }; + +} // namespace mongo diff --git a/db/security.cpp b/db/security.cpp new file mode 100644 index 0000000..747b04a --- /dev/null +++ b/db/security.cpp @@ -0,0 +1,32 @@ +// security.cpp + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "security.h" +#include "instance.h" +#include "client.h" +#include "curop.h" + +namespace mongo { + + bool noauth = true; + + int AuthenticationInfo::warned = 0; + +} // namespace mongo + diff --git a/db/security.h b/db/security.h new file mode 100644 index 0000000..f61d5e1 --- /dev/null +++ b/db/security.h @@ -0,0 +1,77 @@ +// security.h + +/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <boost/thread/tss.hpp> +#undef assert +#define assert xassert + +#include "db.h" +#include "dbhelpers.h" +#include "nonce.h" + +namespace mongo { + + // --noauth cmd line option + extern bool noauth; + + /* for a particular db */ + struct Auth { + Auth() { level = 0; } + int level; + }; + + class AuthenticationInfo : boost::noncopyable { + map<string, Auth> m; // dbname -> auth + static int warned; + public: + bool isLocalHost; + AuthenticationInfo() { isLocalHost = false; } + virtual ~AuthenticationInfo() { + } + void logout(const char *dbname) { + assertInWriteLock(); + m.erase(dbname); + } + void authorize(const char *dbname) { + assertInWriteLock(); + m[dbname].level = 2; + } + virtual bool isAuthorized(const char *dbname) { + if( m[dbname].level == 2 ) return true; + if( noauth ) return true; + if( m["admin"].level == 2 ) return true; + if( m["local"].level == 2 ) return true; + if( isLocalHost ) { + readlock l(""); + Client::Context c("admin.system.users"); + BSONObj result; + if( Helpers::getSingleton("admin.system.users", result) ) + return false; + if( warned == 0 ) { + warned++; + log() << "warning: no users configured in admin.system.users, allowing localhost access" << endl; + } + return true; + } + return false; + } + }; + +} // namespace mongo diff --git a/db/security_commands.cpp b/db/security_commands.cpp new file mode 100644 index 0000000..9d63744 --- /dev/null +++ b/db/security_commands.cpp @@ -0,0 +1,160 @@ +// security_commands.cpp +// security.cpp links with both dbgrid and db. this file db only -- at least for now. + +// security.cpp + +#include "stdafx.h" +#include "security.h" +#include "../util/md5.hpp" +#include "json.h" +#include "pdfile.h" +#include "db.h" +#include "dbhelpers.h" +#include "commands.h" +#include "jsobj.h" +#include "client.h" + +namespace mongo { + +/* authentication + + system.users contains + { user : <username>, pwd : <pwd_digest>, ... } + + getnonce sends nonce to client + + client then sends { authenticate:1, nonce:<nonce_str>, user:<username>, key:<key> } + + where <key> is md5(<nonce_str><username><pwd_digest_str>) as a string +*/ + + boost::thread_specific_ptr<nonce> lastNonce; + + class CmdGetNonce : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + CmdGetNonce() : Command("getnonce") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + nonce *n = new nonce(security.getNonce()); + stringstream ss; + ss << hex << *n; + result.append("nonce", ss.str() ); + lastNonce.reset(n); + return true; + } + } cmdGetNonce; + + class CmdLogout : public Command { + public: + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + CmdLogout() : Command("logout") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + // database->name is the one we are logging out... + Client& client = cc(); + AuthenticationInfo *ai = client.ai; + ai->logout(client.database()->name.c_str()); + return true; + } + } cmdLogout; + + class CmdAuthenticate : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool logTheOp() { + return false; + } + virtual bool slaveOk() { + return true; + } + CmdAuthenticate() : Command("authenticate") {} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) { + log(1) << " authenticate: " << cmdObj << endl; + + string user = cmdObj.getStringField("user"); + string key = cmdObj.getStringField("key"); + string received_nonce = cmdObj.getStringField("nonce"); + + if( user.empty() || key.empty() || received_nonce.empty() ) { + log() << "field missing/wrong type in received authenticate command " + << cc().database()->name + << '\n'; + errmsg = "auth fails"; + sleepmillis(10); + return false; + } + + stringstream digestBuilder; + + { + bool reject = false; + nonce *ln = lastNonce.release(); + if ( ln == 0 ) { + reject = true; + } else { + digestBuilder << hex << *ln; + reject = digestBuilder.str() != received_nonce; + } + + if ( reject ) { + log() << "auth: bad nonce received or getnonce not called. could be a driver bug or a security attack. db:" << cc().database()->name << '\n'; + errmsg = "auth fails"; + sleepmillis(30); + return false; + } + } + + static BSONObj userPattern = fromjson("{\"user\":1}"); + string systemUsers = cc().database()->name + ".system.users"; + OCCASIONALLY Helpers::ensureIndex(systemUsers.c_str(), userPattern, false, "user_1"); + + BSONObj userObj; + { + BSONObjBuilder b; + b << "user" << user; + BSONObj query = b.done(); + if( !Helpers::findOne(systemUsers.c_str(), query, userObj) ) { + log() << "auth: couldn't find user " << user << ", " << systemUsers << '\n'; + errmsg = "auth fails"; + return false; + } + } + + md5digest d; + { + + string pwd = userObj.getStringField("pwd"); + digestBuilder << user << pwd; + string done = digestBuilder.str(); + + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t *) done.c_str(), done.size()); + md5_finish(&st, d); + } + + string computed = digestToString( d ); + + if ( key != computed ){ + log() << "auth: key mismatch " << user << ", ns:" << ns << '\n'; + errmsg = "auth fails"; + return false; + } + + AuthenticationInfo *ai = currentClient.get()->ai; + ai->authorize(cc().database()->name.c_str()); + return true; + } + } cmdAuthenticate; + +} // namespace mongo diff --git a/db/storage.cpp b/db/storage.cpp new file mode 100644 index 0000000..4da2d82 --- /dev/null +++ b/db/storage.cpp @@ -0,0 +1,61 @@ +// storage.cpp + +#include "stdafx.h" +#include "pdfile.h" +#include "reccache.h" +#include "rec.h" +#include "db.h" + +namespace mongo { + +void writerThread(); + +#if defined(_RECSTORE) + static int inited; +#endif + +// pick your store for indexes by setting this typedef +// this doesn't need to be an ifdef, we can make it dynamic +#if defined(_RECSTORE) +RecStoreInterface *btreeStore = new CachedBasicRecStore(); +#else +RecStoreInterface *btreeStore = new MongoMemMapped_RecStore(); +#endif + +void BasicRecStore::init(const char *fn, unsigned recsize) +{ + massert( 10394 , "compile packing problem recstore?", sizeof(RecStoreHeader) == 8192); + filename = fn; + f.open(fn); + uassert( 10130 , string("couldn't open file:")+fn, f.is_open() ); + len = f.len(); + if( len == 0 ) { + log() << "creating recstore file " << fn << '\n'; + h.recsize = recsize; + len = sizeof(RecStoreHeader); + f.write(0, (const char *) &h, sizeof(RecStoreHeader)); + } + else { + f.read(0, (char *) &h, sizeof(RecStoreHeader)); + massert( 10395 , string("recstore was not closed cleanly: ")+fn, h.cleanShutdown==0); + massert( 10396 , string("recstore recsize mismatch, file:")+fn, h.recsize == recsize); + massert( 10397 , string("bad recstore [1], file:")+fn, (h.leof-sizeof(RecStoreHeader)) % recsize == 0); + if( h.leof > len ) { + stringstream ss; + ss << "bad recstore, file:" << fn << " leof:" << h.leof << " len:" << len; + massert( 10398 , ss.str(), false); + } + if( h.cleanShutdown ) + log() << "warning: non-clean shutdown for file " << fn << '\n'; + h.cleanShutdown = 2; + writeHeader(); + f.fsync(); + } +#if defined(_RECSTORE) + if( inited++ == 0 ) { + boost::thread t(writerThread); + } +#endif +} + +} diff --git a/db/storage.h b/db/storage.h new file mode 100644 index 0000000..cc29e60 --- /dev/null +++ b/db/storage.h @@ -0,0 +1,155 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* storage.h + + Storage subsystem management. + Lays out our datafiles on disk, manages disk space. +*/ + +#pragma once + +namespace mongo { + +#pragma pack(1) + + class Record; + class DeletedRecord; + class Extent; + class BtreeBucket; + class BSONObj; + class MongoDataFile; + + class DiskLoc { + int fileNo; /* this will be volume, file #, etc. */ + int ofs; + public: + // Note: MaxFiles imposes a limit of about 32TB of data per process + enum SentinelValues { MaxFiles=16000, NullOfs = -1 }; + + int a() const { + return fileNo; + } + + DiskLoc(int a, int b) : fileNo(a), ofs(b) { + //assert(ofs!=0); + } + DiskLoc() { Null(); } + DiskLoc(const DiskLoc& l) { + fileNo=l.fileNo; + ofs=l.ofs; + } + + bool questionable() { + return ofs < -1 || + fileNo < -1 || + fileNo > 524288; + } + + bool isNull() const { + return fileNo == -1; + // return ofs == NullOfs; + } + void Null() { + fileNo = -1; + ofs = 0; + } + void assertOk() { + assert(!isNull()); + } + void setInvalid() { + fileNo = -2; + ofs = 0; + } + bool isValid() const { + return fileNo != -2; + } + + string toString() const { + if ( isNull() ) + return "null"; + stringstream ss; + ss << hex << fileNo << ':' << ofs; + return ss.str(); + } + operator string() const { return toString(); } + + int& GETOFS() { + return ofs; + } + int getOfs() const { + return ofs; + } + void set(int a, int b) { + fileNo=a; + ofs=b; + } + void setOfs(int _fileNo, int _ofs) { + fileNo = _fileNo; + ofs = _ofs; + } + + void inc(int amt) { + assert( !isNull() ); + ofs += amt; + } + + bool sameFile(DiskLoc b) { + return fileNo == b.fileNo; + } + + bool operator==(const DiskLoc& b) const { + return fileNo==b.fileNo && ofs == b.ofs; + } + bool operator!=(const DiskLoc& b) const { + return !(*this==b); + } + const DiskLoc& operator=(const DiskLoc& b) { + fileNo=b.fileNo; + ofs = b.ofs; + //assert(ofs!=0); + return *this; + } + int compare(const DiskLoc& b) const { + int x = fileNo - b.fileNo; + if ( x ) + return x; + return ofs - b.ofs; + } + bool operator<(const DiskLoc& b) const { + return compare(b) < 0; + } + + /* get the "thing" associated with this disk location. + it is assumed the object is what it is -- you must asure that: + think of this as an unchecked type cast. + */ + BSONObj obj() const; + Record* rec() const; + DeletedRecord* drec() const; + Extent* ext() const; + BtreeBucket* btree() const; + BtreeBucket* btreemod() const; // marks modified / dirty + + MongoDataFile& pdf() const; + }; + +#pragma pack() + + const DiskLoc minDiskLoc(0, 1); + const DiskLoc maxDiskLoc(0x7fffffff, 0x7fffffff); + +} // namespace mongo diff --git a/db/tests.cpp b/db/tests.cpp new file mode 100644 index 0000000..81cc363 --- /dev/null +++ b/db/tests.cpp @@ -0,0 +1,68 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* tests.cpp + + unit test & such +*/ + +#include "stdafx.h" +#include "../util/mmap.h" + +namespace mongo { + + int test2_old9() { + out() << "test2" << endl; + printStackTrace(); + if ( 1 ) + return 1; + + MemoryMappedFile f; + + long len = 64*1024*1024; + char *p = (char *) f.map("/tmp/test.dat", len); + char *start = p; + char *end = p + 64*1024*1024-2; + end[1] = 'z'; + int i; + while ( p < end ) { + *p++ = ' '; + if ( ++i%64 == 0 ) { + *p++ = '\n'; + *p++ = 'x'; + } + } + *p = 'a'; + + f.flush(true); + out() << "done" << endl; + + char *x = start + 32 * 1024 * 1024; + char *y = start + 48 * 1024 * 1024; + char *z = start + 62 * 1024 * 1024; + + strcpy(z, "zfoo"); + out() << "y" << endl; + strcpy(y, "yfoo"); + strcpy(x, "xfoo"); + strcpy(start, "xfoo"); + + dbexit( EXIT_TEST ); + + return 1; + } + +} // namespace mongo diff --git a/db/update.cpp b/db/update.cpp new file mode 100644 index 0000000..0639a99 --- /dev/null +++ b/db/update.cpp @@ -0,0 +1,736 @@ +// update.cpp + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "query.h" +#include "pdfile.h" +#include "jsobjmanipulator.h" +#include "queryoptimizer.h" +#include "repl.h" +#include "update.h" + +namespace mongo { + + const char* Mod::modNames[] = { "$inc", "$set", "$push", "$pushAll", "$pull", "$pullAll" , "$pop", "$unset" , + "$bitand" , "$bitor" , "$bit" }; + unsigned Mod::modNamesNum = sizeof(Mod::modNames)/sizeof(char*); + + bool Mod::_pullElementMatch( BSONElement& toMatch ) const { + + if ( elt.type() != Object ){ + // if elt isn't an object, then comparison will work + return toMatch.woCompare( elt , false ) == 0; + } + + if ( toMatch.type() != Object ){ + // looking for an object, so this can't match + return false; + } + + // now we have an object on both sides + return matcher->matches( toMatch.embeddedObject() ); + } + + void Mod::apply( BSONObjBuilder& b , BSONElement in ){ + switch ( op ){ + + case INC: { + // TODO: this is horrible + inc( in ); + b.appendAs( elt , shortFieldName ); + break; + } + + case SET: { + _checkForAppending( elt ); + b.appendAs( elt , shortFieldName ); + break; + } + + case UNSET: { + //Explicit NOOP + break; + } + + case PUSH: { + uassert( 10131 , "$push can only be applied to an array" , in.type() == Array ); + BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); + BSONObjIterator i( in.embeddedObject() ); + int n=0; + while ( i.more() ){ + bb.append( i.next() ); + n++; + } + + pushStartSize = n; + + bb.appendAs( elt , bb.numStr( n ) ); + bb.done(); + break; + } + + case PUSH_ALL: { + uassert( 10132 , "$pushAll can only be applied to an array" , in.type() == Array ); + uassert( 10133 , "$pushAll has to be passed an array" , elt.type() ); + + BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); + + BSONObjIterator i( in.embeddedObject() ); + int n=0; + while ( i.more() ){ + bb.append( i.next() ); + n++; + } + + pushStartSize = n; + + i = BSONObjIterator( elt.embeddedObject() ); + while ( i.more() ){ + bb.appendAs( i.next() , bb.numStr( n++ ) ); + } + + bb.done(); + break; + } + + case PULL: + case PULL_ALL: { + uassert( 10134 , "$pull/$pullAll can only be applied to an array" , in.type() == Array ); + BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); + + int n = 0; + + BSONObjIterator i( in.embeddedObject() ); + while ( i.more() ){ + BSONElement e = i.next(); + bool allowed = true; + + if ( op == PULL ){ + allowed = ! _pullElementMatch( e ); + } + else { + BSONObjIterator j( elt.embeddedObject() ); + while( j.more() ) { + BSONElement arrJ = j.next(); + if ( e.woCompare( arrJ, false ) == 0 ){ + allowed = false; + break; + } + } + } + + if ( allowed ) + bb.appendAs( e , bb.numStr( n++ ) ); + } + + bb.done(); + break; + } + + case POP: { + uassert( 10135 , "$pop can only be applied to an array" , in.type() == Array ); + BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); + + int n = 0; + + BSONObjIterator i( in.embeddedObject() ); + if ( elt.isNumber() && elt.number() < 0 ){ + // pop from front + if ( i.more() ){ + i.next(); + n++; + } + + while( i.more() ) { + bb.appendAs( i.next() , bb.numStr( n - 1 ).c_str() ); + n++; + } + } + else { + // pop from back + while( i.more() ) { + n++; + BSONElement arrI = i.next(); + if ( i.more() ){ + bb.append( arrI ); + } + } + } + + pushStartSize = n; + assert( pushStartSize == in.embeddedObject().nFields() ); + bb.done(); + break; + } + + case BIT: { + uassert( 10136 , "$bit needs an array" , elt.type() == Object ); + uassert( 10137 , "$bit can only be applied to numbers" , in.isNumber() ); + uassert( 10138 , "$bit can't use a double" , in.type() != NumberDouble ); + + int x = in.numberInt(); + long long y = in.numberLong(); + + BSONObjIterator it( elt.embeddedObject() ); + while ( it.more() ){ + BSONElement e = it.next(); + uassert( 10139 , "$bit field must be number" , e.isNumber() ); + if ( strcmp( e.fieldName() , "and" ) == 0 ){ + switch( in.type() ){ + case NumberInt: x = x&e.numberInt(); break; + case NumberLong: y = y&e.numberLong(); break; + default: assert( 0 ); + } + } + else if ( strcmp( e.fieldName() , "or" ) == 0 ){ + switch( in.type() ){ + case NumberInt: x = x|e.numberInt(); break; + case NumberLong: y = y|e.numberLong(); break; + default: assert( 0 ); + } + } + + else { + throw UserException( 9016, (string)"unknown bit mod:" + e.fieldName() ); + } + } + + switch( in.type() ){ + case NumberInt: b.append( shortFieldName , x ); break; + case NumberLong: b.append( shortFieldName , y ); break; + default: assert( 0 ); + } + + break; + } + + default: + stringstream ss; + ss << "Mod::apply can't handle type: " << op; + throw UserException( 9017, ss.str() ); + } + } + + bool ModSet::canApplyInPlaceAndVerify(const BSONObj &obj) const { + bool inPlacePossible = true; + + // Perform this check first, so that we don't leave a partially modified object on uassert. + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); ++i ) { + const Mod& m = i->second; + BSONElement e = obj.getFieldDotted(m.fieldName); + + if ( e.eoo() ) { + inPlacePossible = (m.op == Mod::UNSET); + } + else { + switch( m.op ) { + case Mod::INC: + uassert( 10140 , "Cannot apply $inc modifier to non-number", e.isNumber() || e.eoo() ); + if ( !e.isNumber() ) + inPlacePossible = false; + break; + case Mod::SET: + inPlacePossible = + m.elt.type() == e.type() && + m.elt.valuesize() == e.valuesize(); + break; + case Mod::PUSH: + case Mod::PUSH_ALL: + uassert( 10141 , "Cannot apply $push/$pushAll modifier to non-array", e.type() == Array || e.eoo() ); + inPlacePossible = false; + break; + case Mod::PULL: + case Mod::PULL_ALL: { + uassert( 10142 , "Cannot apply $pull/$pullAll modifier to non-array", e.type() == Array || e.eoo() ); + BSONObjIterator i( e.embeddedObject() ); + while( inPlacePossible && i.more() ) { + BSONElement arrI = i.next(); + if ( m.op == Mod::PULL ) { + if ( m._pullElementMatch( arrI ) ) + inPlacePossible = false; + } + else if ( m.op == Mod::PULL_ALL ) { + BSONObjIterator j( m.elt.embeddedObject() ); + while( inPlacePossible && j.moreWithEOO() ) { + BSONElement arrJ = j.next(); + if ( arrJ.eoo() ) + break; + if ( arrI.woCompare( arrJ, false ) == 0 ) { + inPlacePossible = false; + } + } + } + } + break; + } + case Mod::POP: { + uassert( 10143 , "Cannot apply $pop modifier to non-array", e.type() == Array || e.eoo() ); + if ( ! e.embeddedObject().isEmpty() ) + inPlacePossible = false; + break; + } + default: + // mods we don't know about shouldn't be done in place + inPlacePossible = false; + } + } + } + return inPlacePossible; + } + + void ModSet::applyModsInPlace(const BSONObj &obj) const { + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); ++i ) { + const Mod& m = i->second; + BSONElement e = obj.getFieldDotted(m.fieldName); + + switch ( m.op ){ + case Mod::UNSET: + case Mod::PULL: + case Mod::PULL_ALL: + break; + + // [dm] the BSONElementManipulator statements below are for replication (correct?) + case Mod::INC: + m.inc(e); + m.setElementToOurNumericValue(e); + break; + case Mod::SET: + if ( e.isNumber() && m.elt.isNumber() ) { + // todo: handle NumberLong: + m.setElementToOurNumericValue(e); + } + else { + BSONElementManipulator( e ).replaceTypeAndValue( m.elt ); + } + break; + default: + uassert( 10144 , "can't apply mod in place - shouldn't have gotten here" , 0 ); + } + } + } + + void ModSet::extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base ) { + if ( top.type() != Object ) { + fields[ base + top.fieldName() ] = top; + return; + } + BSONObjIterator i( top.embeddedObject() ); + bool empty = true; + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + extractFields( fields, e, base + top.fieldName() + "." ); + empty = false; + } + if ( empty ) + fields[ base + top.fieldName() ] = top; + } + + void ModSet::_appendNewFromMods( const string& root , Mod& m , BSONObjBuilder& b , set<string>& onedownseen ){ + const char * temp = m.fieldName; + temp += root.size(); + const char * dot = strchr( temp , '.' ); + if ( dot ){ + string nr( m.fieldName , 0 , 1 + ( dot - m.fieldName ) ); + string nf( temp , 0 , dot - temp ); + if ( onedownseen.count( nf ) ) + return; + onedownseen.insert( nf ); + BSONObjBuilder bb ( b.subobjStart( nf.c_str() ) ); + createNewFromMods( nr , bb , BSONObj() ); + bb.done(); + } + else { + appendNewFromMod( m , b ); + } + + } + + void ModSet::createNewFromMods( const string& root , BSONObjBuilder& b , const BSONObj &obj ){ + BSONObjIteratorSorted es( obj ); + BSONElement e = es.next(); + + ModHolder::iterator m = _mods.lower_bound( root ); + ModHolder::iterator mend = _mods.lower_bound( root + "{" ); + + set<string> onedownseen; + + while ( e.type() && m != mend ){ + string field = root + e.fieldName(); + FieldCompareResult cmp = compareDottedFieldNames( m->second.fieldName , field ); + + switch ( cmp ){ + + case LEFT_SUBFIELD: { // Mod is embeddeed under this element + uassert( 10145 , "LEFT_SUBFIELD only supports Object" , e.type() == Object || e.type() == Array ); + if ( onedownseen.count( e.fieldName() ) == 0 ){ + onedownseen.insert( e.fieldName() ); + BSONObjBuilder bb ( e.type() == Object ? b.subobjStart( e.fieldName() ) : b.subarrayStart( e.fieldName() ) ); + stringstream nr; nr << root << e.fieldName() << "."; + createNewFromMods( nr.str() , bb , e.embeddedObject() ); + bb.done(); + // inc both as we handled both + e = es.next(); + m++; + } + continue; + } + case LEFT_BEFORE: // Mod on a field that doesn't exist + _appendNewFromMods( root , m->second , b , onedownseen ); + m++; + continue; + case SAME: + m->second.apply( b , e ); + e = es.next(); + m++; + continue; + case RIGHT_BEFORE: // field that doesn't have a MOD + b.append( e ); + e = es.next(); + continue; + case RIGHT_SUBFIELD: + massert( 10399 , "ModSet::createNewFromMods - RIGHT_SUBFIELD should be impossible" , 0 ); + break; + default: + massert( 10400 , "unhandled case" , 0 ); + } + } + + // finished looping the mods, just adding the rest of the elements + while ( e.type() ){ + b.append( e ); + e = es.next(); + } + + // do mods that don't have fields already + for ( ; m != mend; m++ ){ + _appendNewFromMods( root , m->second , b , onedownseen ); + } + } + + BSONObj ModSet::createNewFromMods( const BSONObj &obj ) { + BSONObjBuilder b( (int)(obj.objsize() * 1.1) ); + createNewFromMods( "" , b , obj ); + return b.obj(); + } + + BSONObj ModSet::createNewFromQuery( const BSONObj& query ){ + BSONObj newObj; + + { + BSONObjBuilder bb; + EmbeddedBuilder eb( &bb ); + BSONObjIteratorSorted i( query ); + while ( i.more() ){ + BSONElement e = i.next(); + + if ( e.type() == Object && e.embeddedObject().firstElement().fieldName()[0] == '$' ){ + // this means this is a $gt type filter, so don't make part of the new object + continue; + } + + eb.appendAs( e , e.fieldName() ); + } + eb.done(); + newObj = bb.obj(); + } + + if ( canApplyInPlaceAndVerify( newObj ) ) + applyModsInPlace( newObj ); + else + newObj = createNewFromMods( newObj ); + + return newObj; + } + + /* get special operations like $inc + { $inc: { a:1, b:1 } } + { $set: { a:77 } } + { $push: { a:55 } } + { $pushAll: { a:[77,88] } } + { $pull: { a:66 } } + { $pullAll : { a:[99,1010] } } + NOTE: MODIFIES source from object! + */ + void ModSet::getMods(const BSONObj &from) { + BSONObjIterator it(from); + while ( it.more() ) { + BSONElement e = it.next(); + const char *fn = e.fieldName(); + uassert( 10147 , "Invalid modifier specified" + string( fn ), e.type() == Object ); + BSONObj j = e.embeddedObject(); + BSONObjIterator jt(j); + Mod::Op op = opFromStr( fn ); + if ( op == Mod::INC ) + strcpy((char *) fn, "$set"); // rewrite for op log + while ( jt.more() ) { + BSONElement f = jt.next(); // x:44 + + const char * fieldName = f.fieldName(); + + uassert( 10148 , "Mod on _id not allowed", strcmp( fieldName, "_id" ) != 0 ); + uassert( 10149 , "Invalid mod field name, may not end in a period", fieldName[ strlen( fieldName ) - 1 ] != '.' ); + uassert( 10150 , "Field name duplication not allowed with modifiers", ! haveModForField( fieldName ) ); + uassert( 10151 , "have conflict mod" , ! haveConflictingMod( fieldName ) ); + uassert( 10152 , "Modifier $inc allowed for numbers only", f.isNumber() || op != Mod::INC ); + uassert( 10153 , "Modifier $pushAll/pullAll allowed for arrays only", f.type() == Array || ( op != Mod::PUSH_ALL && op != Mod::PULL_ALL ) ); + + Mod m; + m.init( op , f ); + m.setFieldName( f.fieldName() ); + + // horrible - to be cleaned up + if ( f.type() == NumberDouble ) { + m.ndouble = (double *) f.value(); + m.nint = 0; + } else if ( f.type() == NumberInt ) { + m.ndouble = 0; + m.nint = (int *) f.value(); + } + else if( f.type() == NumberLong ) { + m.ndouble = 0; + m.nint = 0; + m.nlong = (long long *) f.value(); + } + + _mods[m.fieldName] = m; + } + } + } + + void checkNoMods( BSONObj o ) { + BSONObjIterator i( o ); + while( i.moreWithEOO() ) { + BSONElement e = i.next(); + if ( e.eoo() ) + break; + uassert( 10154 , "Modifiers and non-modifiers cannot be mixed", e.fieldName()[ 0 ] != '$' ); + } + } + + class UpdateOp : public QueryOp { + public: + UpdateOp() : nscanned_() {} + virtual void init() { + BSONObj pattern = qp().query(); + c_.reset( qp().newCursor().release() ); + if ( !c_->ok() ) + setComplete(); + else + matcher_.reset( new CoveredIndexMatcher( pattern, qp().indexKey() ) ); + } + virtual void next() { + if ( !c_->ok() ) { + setComplete(); + return; + } + nscanned_++; + if ( matcher_->matches(c_->currKey(), c_->currLoc()) ) { + setComplete(); + return; + } + c_->advance(); + } + bool curMatches(){ + return matcher_->matches(c_->currKey(), c_->currLoc() ); + } + virtual bool mayRecordPlan() const { return false; } + virtual QueryOp *clone() const { + return new UpdateOp(); + } + shared_ptr< Cursor > c() { return c_; } + long long nscanned() const { return nscanned_; } + private: + shared_ptr< Cursor > c_; + long long nscanned_; + auto_ptr< CoveredIndexMatcher > matcher_; + }; + + + UpdateResult updateObjects(const char *ns, BSONObj updateobjOrig, BSONObj patternOrig, bool upsert, bool multi, bool logop , OpDebug& debug ) { + int profile = cc().database()->profile; + StringBuilder& ss = debug.str; + + uassert( 10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0 ); + if ( strstr(ns, ".system.") ) { + /* dm: it's very important that system.indexes is never updated as IndexDetails has pointers into it */ + uassert( 10156 , "cannot update system collection", legalClientSystemNS( ns , true ) ); + } + + set<DiskLoc> seenObjects; + + QueryPlanSet qps( ns, patternOrig, BSONObj() ); + UpdateOp original; + shared_ptr< UpdateOp > u = qps.runOp( original ); + massert( 10401 , u->exceptionMessage(), u->complete() ); + shared_ptr< Cursor > c = u->c(); + int numModded = 0; + while ( c->ok() ) { + if ( numModded > 0 && ! u->curMatches() ){ + c->advance(); + continue; + } + Record *r = c->_current(); + DiskLoc loc = c->currLoc(); + + if ( c->getsetdup( loc ) ){ + c->advance(); + continue; + } + + BSONObj js(r); + + BSONObj pattern = patternOrig; + BSONObj updateobj = updateobjOrig; + + if ( logop ) { + BSONObjBuilder idPattern; + BSONElement id; + // NOTE: If the matching object lacks an id, we'll log + // with the original pattern. This isn't replay-safe. + // It might make sense to suppress the log instead + // if there's no id. + if ( js.getObjectID( id ) ) { + idPattern.append( id ); + pattern = idPattern.obj(); + } + else { + uassert( 10157 , "multi-update requires all modified objects to have an _id" , ! multi ); + } + } + + if ( profile ) + ss << " nscanned:" << u->nscanned(); + + /* look for $inc etc. note as listed here, all fields to inc must be this type, you can't set some + regular ones at the moment. */ + + const char *firstField = updateobj.firstElement().fieldName(); + + if ( firstField[0] == '$' ) { + + if ( multi ){ + c->advance(); // go to next record in case this one moves + if ( seenObjects.count( loc ) ) + continue; + updateobj = updateobj.copy(); + } + + ModSet mods; + mods.getMods(updateobj); + NamespaceDetailsTransient& ndt = NamespaceDetailsTransient::get_w(ns); + set<string>& idxKeys = ndt.indexKeys(); + int isIndexed = mods.isIndexed( idxKeys ); + + if ( isIndexed && multi ){ + c->noteLocation(); + } + + if ( isIndexed <= 0 && mods.canApplyInPlaceAndVerify( loc.obj() ) ) { + mods.applyModsInPlace( loc.obj() ); + //seenObjects.insert( loc ); + if ( profile ) + ss << " fastmod "; + + if ( isIndexed ){ + seenObjects.insert( loc ); + } + } + else { + BSONObj newObj = mods.createNewFromMods( loc.obj() ); + uassert( 12522 , "$ operator made objcet too large" , newObj.isValid() ); + DiskLoc newLoc = theDataFileMgr.update(ns, r, loc , newObj.objdata(), newObj.objsize(), debug); + if ( newLoc != loc || isIndexed ){ + // object moved, need to make sure we don' get again + seenObjects.insert( newLoc ); + } + + } + + if ( logop ) { + + assert( mods.size() ); + + if ( mods.haveArrayDepMod() ) { + BSONObjBuilder patternBuilder; + patternBuilder.appendElements( pattern ); + mods.appendSizeSpecForArrayDepMods( patternBuilder ); + pattern = patternBuilder.obj(); + } + + if ( mods.needOpLogRewrite() ) + updateobj = mods.getOpLogRewrite(); + + logOp("u", ns, updateobj, &pattern ); + } + numModded++; + if ( ! multi ) + break; + if ( multi && isIndexed ) + c->checkLocation(); + continue; + } + + uassert( 10158 , "multi update only works with $ operators" , ! multi ); + + BSONElementManipulator::lookForTimestamps( updateobj ); + checkNoMods( updateobj ); + theDataFileMgr.update(ns, r, loc , updateobj.objdata(), updateobj.objsize(), debug); + if ( logop ) + logOp("u", ns, updateobj, &pattern ); + return UpdateResult( 1 , 0 , 1 ); + } + + if ( numModded ) + return UpdateResult( 1 , 1 , numModded ); + + + if ( profile ) + ss << " nscanned:" << u->nscanned(); + + if ( upsert ) { + if ( updateobjOrig.firstElement().fieldName()[0] == '$' ) { + /* upsert of an $inc. build a default */ + ModSet mods; + mods.getMods(updateobjOrig); + + BSONObj newObj = mods.createNewFromQuery( patternOrig ); + + if ( profile ) + ss << " fastmodinsert "; + theDataFileMgr.insert(ns, newObj); + if ( profile ) + ss << " fastmodinsert "; + if ( logop ) + logOp( "i", ns, newObj ); + return UpdateResult( 0 , 1 , 1 ); + } + uassert( 10159 , "multi update only works with $ operators" , ! multi ); + checkNoMods( updateobjOrig ); + if ( profile ) + ss << " upsert "; + theDataFileMgr.insert(ns, updateobjOrig); + if ( logop ) + logOp( "i", ns, updateobjOrig ); + return UpdateResult( 0 , 0 , 1 ); + } + return UpdateResult( 0 , 0 , 0 ); + } + +} diff --git a/db/update.h b/db/update.h new file mode 100644 index 0000000..26a8a8d --- /dev/null +++ b/db/update.h @@ -0,0 +1,382 @@ +// update.h + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "../stdafx.h" +#include "jsobj.h" +#include "../util/embedded_builder.h" +#include "matcher.h" + +namespace mongo { + + /* Used for modifiers such as $inc, $set, $push, ... */ + struct Mod { + // See opFromStr below + // 0 1 2 3 4 5 6 7 8 9 10 + enum Op { INC, SET, PUSH, PUSH_ALL, PULL, PULL_ALL , POP, UNSET, BITAND, BITOR , BIT } op; + + static const char* modNames[]; + static unsigned modNamesNum; + + const char *fieldName; + const char *shortFieldName; + + // kind of lame; fix one day? + double *ndouble; + int *nint; + long long *nlong; + + BSONElement elt; // x:5 note: this is the actual element from the updateobj + int pushStartSize; + boost::shared_ptr<Matcher> matcher; + + void init( Op o , BSONElement& e ){ + op = o; + elt = e; + if ( op == PULL && e.type() == Object ) + matcher.reset( new Matcher( e.embeddedObject() ) ); + } + + void setFieldName( const char * s ){ + fieldName = s; + shortFieldName = strrchr( fieldName , '.' ); + if ( shortFieldName ) + shortFieldName++; + else + shortFieldName = fieldName; + } + + /* [dm] why is this const? (or rather, why was setn const?) i see why but think maybe clearer if were not. */ + void inc(BSONElement& n) const { + uassert( 10160 , "$inc value is not a number", n.isNumber() ); + if( ndouble ) + *ndouble += n.numberDouble(); + else if( nint ) + *nint += n.numberInt(); + else + *nlong += n.numberLong(); + } + + void setElementToOurNumericValue(BSONElement& e) const { + BSONElementManipulator manip(e); + if( e.type() == NumberLong ) + manip.setLong(_getlong()); + else + manip.setNumber(_getn()); + } + + double _getn() const { + if( ndouble ) return *ndouble; + if( nint ) return *nint; + return (double) *nlong; + } + long long _getlong() const { + if( nlong ) return *nlong; + if( ndouble ) return (long long) *ndouble; + return *nint; + } + bool operator<( const Mod &other ) const { + return strcmp( fieldName, other.fieldName ) < 0; + } + + bool arrayDep() const { + switch (op){ + case PUSH: + case PUSH_ALL: + case POP: + return true; + default: + return false; + } + } + + bool isIndexed( const set<string>& idxKeys ) const { + // check if there is an index key that is a parent of mod + for( const char *dot = strchr( fieldName, '.' ); dot; dot = strchr( dot + 1, '.' ) ) + if ( idxKeys.count( string( fieldName, dot - fieldName ) ) ) + return true; + string fullName = fieldName; + // check if there is an index key equal to mod + if ( idxKeys.count(fullName) ) + return true; + // check if there is an index key that is a child of mod + set< string >::const_iterator j = idxKeys.upper_bound( fullName ); + if ( j != idxKeys.end() && j->find( fullName ) == 0 && (*j)[fullName.size()] == '.' ) + return true; + return false; + } + + void apply( BSONObjBuilder& b , BSONElement in ); + + /** + * @return true iff toMatch should be removed from the array + */ + bool _pullElementMatch( BSONElement& toMatch ) const; + + bool needOpLogRewrite() const { + switch( op ){ + case BIT: + case BITAND: + case BITOR: + // TODO: should we convert this to $set? + return false; + default: + return false; + } + } + + void appendForOpLog( BSONObjBuilder& b ) const { + const char * name = modNames[op]; + + BSONObjBuilder bb( b.subobjStart( name ) ); + bb.append( elt ); + bb.done(); + } + + void _checkForAppending( BSONElement& e ){ + if ( e.type() == Object ){ + // this is a tiny bit slow, but rare and important + // only when setting something TO an object, not setting something in an object + // and it checks for { $set : { x : { 'a.b' : 1 } } } + // which is feel has been common + uassert( 12527 , "not okForStorage" , e.embeddedObject().okForStorage() ); + } + } + + }; + + class ModSet { + typedef map<string,Mod> ModHolder; + ModHolder _mods; + + static void extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base ); + + FieldCompareResult compare( const ModHolder::iterator &m, map< string, BSONElement >::iterator &p, const map< string, BSONElement >::iterator &pEnd ) const { + bool mDone = ( m == _mods.end() ); + bool pDone = ( p == pEnd ); + assert( ! mDone ); + assert( ! pDone ); + if ( mDone && pDone ) + return SAME; + // If one iterator is done we want to read from the other one, so say the other one is lower. + if ( mDone ) + return RIGHT_BEFORE; + if ( pDone ) + return LEFT_BEFORE; + + return compareDottedFieldNames( m->first, p->first.c_str() ); + } + + void _appendNewFromMods( const string& root , Mod& m , BSONObjBuilder& b , set<string>& onedownseen ); + + void appendNewFromMod( Mod& m , BSONObjBuilder& b ){ + switch ( m.op ){ + + case Mod::PUSH: { + BSONObjBuilder arr( b.subarrayStart( m.shortFieldName ) ); + arr.appendAs( m.elt, "0" ); + arr.done(); + m.pushStartSize = -1; + break; + } + + case Mod::PUSH_ALL: { + b.appendAs( m.elt, m.shortFieldName ); + m.pushStartSize = -1; + break; + } + + case Mod::UNSET: + case Mod::PULL: + case Mod::PULL_ALL: + // no-op b/c unset/pull of nothing does nothing + break; + + case Mod::INC: + case Mod::SET: { + m._checkForAppending( m.elt ); + b.appendAs( m.elt, m.shortFieldName ); + break; + } + default: + stringstream ss; + ss << "unknown mod in appendNewFromMod: " << m.op; + throw UserException( 9015, ss.str() ); + } + + } + + bool mayAddEmbedded( map< string, BSONElement > &existing, string right ) { + for( string left = EmbeddedBuilder::splitDot( right ); + left.length() > 0 && left[ left.length() - 1 ] != '.'; + left += "." + EmbeddedBuilder::splitDot( right ) ) { + if ( existing.count( left ) > 0 && existing[ left ].type() != Object ) + return false; + if ( haveModForField( left.c_str() ) ) + return false; + } + return true; + } + static Mod::Op opFromStr( const char *fn ) { + assert( fn[0] == '$' ); + switch( fn[1] ){ + case 'i': { + if ( fn[2] == 'n' && fn[3] == 'c' && fn[4] == 0 ) + return Mod::INC; + break; + } + case 's': { + if ( fn[2] == 'e' && fn[3] == 't' && fn[4] == 0 ) + return Mod::SET; + break; + } + case 'p': { + if ( fn[2] == 'u' ){ + if ( fn[3] == 's' && fn[4] == 'h' ){ + if ( fn[5] == 0 ) + return Mod::PUSH; + if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 ) + return Mod::PUSH_ALL; + } + else if ( fn[3] == 'l' && fn[4] == 'l' ){ + if ( fn[5] == 0 ) + return Mod::PULL; + if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 ) + return Mod::PULL_ALL; + } + } + else if ( fn[2] == 'o' && fn[3] == 'p' && fn[4] == 0 ) + return Mod::POP; + break; + } + case 'u': { + if ( fn[2] == 'n' && fn[3] == 's' && fn[4] == 'e' && fn[5] == 't' && fn[6] == 0 ) + return Mod::UNSET; + break; + } + case 'b': { + if ( fn[2] == 'i' && fn[3] == 't' ){ + if ( fn[4] == 0 ) + return Mod::BIT; + if ( fn[4] == 'a' && fn[5] == 'n' && fn[6] == 'd' && fn[7] == 0 ) + return Mod::BITAND; + if ( fn[4] == 'o' && fn[5] == 'r' && fn[6] == 0 ) + return Mod::BITOR; + } + break; + } + default: break; + } + uassert( 10161 , "Invalid modifier specified " + string( fn ), false ); + return Mod::INC; + } + + public: + + void getMods( const BSONObj &from ); + /** + will return if can be done in place, or uassert if there is an error + @return whether or not the mods can be done in place + */ + bool canApplyInPlaceAndVerify( const BSONObj &obj ) const; + void applyModsInPlace( const BSONObj &obj ) const; + + // new recursive version, will replace at some point + void createNewFromMods( const string& root , BSONObjBuilder& b , const BSONObj &obj ); + + BSONObj createNewFromMods( const BSONObj &obj ); + + BSONObj createNewFromQuery( const BSONObj& query ); + + /** + * + */ + int isIndexed( const set<string>& idxKeys ) const { + int numIndexes = 0; + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ){ + if ( i->second.isIndexed( idxKeys ) ) + numIndexes++; + } + return numIndexes; + } + + unsigned size() const { return _mods.size(); } + + bool haveModForField( const char *fieldName ) const { + return _mods.find( fieldName ) != _mods.end(); + } + + bool haveConflictingMod( const string& fieldName ){ + size_t idx = fieldName.find( '.' ); + if ( idx == string::npos ) + idx = fieldName.size(); + + ModHolder::const_iterator start = _mods.lower_bound(fieldName.substr(0,idx)); + for ( ; start != _mods.end(); start++ ){ + FieldCompareResult r = compareDottedFieldNames( fieldName , start->first ); + switch ( r ){ + case LEFT_SUBFIELD: return true; + case LEFT_BEFORE: return false; + case SAME: return true; + case RIGHT_BEFORE: return false; + case RIGHT_SUBFIELD: return true; + } + } + return false; + + + } + + // re-writing for oplog + + bool needOpLogRewrite() const { + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) + if ( i->second.needOpLogRewrite() ) + return true; + return false; + } + + BSONObj getOpLogRewrite() const { + BSONObjBuilder b; + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) + i->second.appendForOpLog( b ); + return b.obj(); + } + + bool haveArrayDepMod() const { + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) + if ( i->second.arrayDep() ) + return true; + return false; + } + + void appendSizeSpecForArrayDepMods( BSONObjBuilder &b ) const { + for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) { + const Mod& m = i->second; + if ( m.arrayDep() ){ + if ( m.pushStartSize == -1 ) + b.appendNull( m.fieldName ); + else + b << m.fieldName << BSON( "$size" << m.pushStartSize ); + } + } + } + }; + + +} + diff --git a/dbtests/basictests.cpp b/dbtests/basictests.cpp new file mode 100644 index 0000000..20dc6d7 --- /dev/null +++ b/dbtests/basictests.cpp @@ -0,0 +1,251 @@ +// basictests.cpp : basic unit tests +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#include "dbtests.h" +#include "../util/base64.h" + +namespace BasicTests { + + class Rarely { + public: + void run() { + int first = 0; + int second = 0; + int third = 0; + for( int i = 0; i < 128; ++i ) { + incRarely( first ); + incRarely2( second ); + ONCE ++third; + } + ASSERT_EQUALS( 1, first ); + ASSERT_EQUALS( 1, second ); + ASSERT_EQUALS( 1, third ); + } + private: + void incRarely( int &c ) { + RARELY ++c; + } + void incRarely2( int &c ) { + RARELY ++c; + } + }; + + class Base64Tests { + public: + + void roundTrip( string s ){ + ASSERT_EQUALS( s , base64::decode( base64::encode( s ) ) ); + } + + void roundTrip( const unsigned char * _data , int len ){ + const char *data = (const char *) _data; + string s = base64::encode( data , len ); + string out = base64::decode( s ); + ASSERT_EQUALS( out.size() , static_cast<size_t>(len) ); + bool broke = false; + for ( int i=0; i<len; i++ ){ + if ( data[i] != out[i] ) + broke = true; + } + if ( ! broke ) + return; + + cout << s << endl; + for ( int i=0; i<len; i++ ) + cout << hex << ( data[i] & 0xFF ) << dec << " "; + cout << endl; + for ( int i=0; i<len; i++ ) + cout << hex << ( out[i] & 0xFF ) << dec << " "; + cout << endl; + + ASSERT(0); + } + + void run(){ + + ASSERT_EQUALS( "ZWxp" , base64::encode( "eli" , 3 ) ); + ASSERT_EQUALS( "ZWxpb3Rz" , base64::encode( "eliots" , 6 ) ); + ASSERT_EQUALS( "ZWxpb3Rz" , base64::encode( "eliots" ) ); + + ASSERT_EQUALS( "ZQ==" , base64::encode( "e" , 1 ) ); + ASSERT_EQUALS( "ZWw=" , base64::encode( "el" , 2 ) ); + + roundTrip( "e" ); + roundTrip( "el" ); + roundTrip( "eli" ); + roundTrip( "elio" ); + roundTrip( "eliot" ); + roundTrip( "eliots" ); + roundTrip( "eliotsz" ); + + unsigned char z[] = { 0x1 , 0x2 , 0x3 , 0x4 }; + roundTrip( z , 4 ); + + unsigned char y[] = { + 0x01, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8B, 0x30, + 0xD3, 0x8F, 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96, + 0x9B, 0x71, 0xD7, 0x9F, 0x82, 0x18, 0xA3, 0x92, 0x59, 0xA7, + 0xA2, 0x9A, 0xAB, 0xB2, 0xDB, 0xAF, 0xC3, 0x1C, 0xB3, 0xD3, + 0x5D, 0xB7, 0xE3, 0x9E, 0xBB, 0xF3, 0xDF, 0xBF + }; + roundTrip( y , 4 ); + roundTrip( y , 40 ); + } + }; + + namespace stringbuildertests { +#define SBTGB(x) ss << (x); sb << (x); + + class Base { + virtual void pop() = 0; + + public: + Base(){} + virtual ~Base(){} + + void run(){ + pop(); + ASSERT_EQUALS( ss.str() , sb.str() ); + } + + stringstream ss; + StringBuilder sb; + }; + + class simple1 : public Base { + void pop(){ + SBTGB(1); + SBTGB("yo"); + SBTGB(2); + } + }; + + class simple2 : public Base { + void pop(){ + SBTGB(1); + SBTGB("yo"); + SBTGB(2); + SBTGB( 12123123123LL ); + SBTGB( "xxx" ); + SBTGB( 5.4 ); + SBTGB( 5.4312 ); + SBTGB( "yyy" ); + SBTGB( (short)5 ); + SBTGB( (short)(1231231231231LL) ); + } + }; + + class reset1 { + public: + void run(){ + StringBuilder sb; + sb << "1" << "abc" << "5.17"; + ASSERT_EQUALS( "1abc5.17" , sb.str() ); + ASSERT_EQUALS( "1abc5.17" , sb.str() ); + sb.reset(); + ASSERT_EQUALS( "" , sb.str() ); + sb << "999"; + ASSERT_EQUALS( "999" , sb.str() ); + } + }; + + class reset2 { + public: + void run(){ + StringBuilder sb; + sb << "1" << "abc" << "5.17"; + ASSERT_EQUALS( "1abc5.17" , sb.str() ); + ASSERT_EQUALS( "1abc5.17" , sb.str() ); + sb.reset(1); + ASSERT_EQUALS( "" , sb.str() ); + sb << "999"; + ASSERT_EQUALS( "999" , sb.str() ); + } + }; + + } + + class sleeptest { + public: + void run(){ + Timer t; + sleepsecs( 1 ); + ASSERT_EQUALS( 1 , t.seconds() ); + + t.reset(); + sleepmicros( 1527123 ); + ASSERT( t.micros() > 1000000 ); + ASSERT( t.micros() < 2000000 ); + + t.reset(); + sleepmillis( 1727 ); + ASSERT( t.millis() >= 1000 ); + ASSERT( t.millis() <= 2000 ); + + } + + }; + + class AssertTests { + public: + + int x; + + AssertTests(){ + x = 0; + } + + string foo(){ + x++; + return ""; + } + void run(){ + uassert( -1 , foo() , 1 ); + ASSERT_EQUALS( 0 , x ); + try { + uassert( -1 , foo() , 0 ); + } + catch ( ... ){} + ASSERT_EQUALS( 1 , x ); + } + }; + + class All : public Suite { + public: + All() : Suite( "basic" ){ + } + + void setupTests(){ + add< Rarely >(); + add< Base64Tests >(); + + add< stringbuildertests::simple1 >(); + add< stringbuildertests::simple2 >(); + add< stringbuildertests::reset1 >(); + add< stringbuildertests::reset2 >(); + + add< sleeptest >(); + add< AssertTests >(); + } + } myall; + +} // namespace BasicTests + diff --git a/dbtests/btreetests.cpp b/dbtests/btreetests.cpp new file mode 100644 index 0000000..5a0b15d --- /dev/null +++ b/dbtests/btreetests.cpp @@ -0,0 +1,238 @@ +// btreetests.cpp : Btree unit tests +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#include "../db/db.h" +#include "../db/btree.h" + +#include "dbtests.h" + +namespace BtreeTests { + + class Base { + public: + Base() { + { + bool f = false; + assert( f = true ); + massert( 10402 , "assert is misdefined", f); + } + + setClient( ns() ); + BSONObjBuilder builder; + builder.append( "ns", ns() ); + builder.append( "name", "testIndex" ); + BSONObj bobj = builder.done(); + idx_.info = + theDataFileMgr.insert( ns(), bobj.objdata(), bobj.objsize() ); + idx_.head = BtreeBucket::addBucket( idx_ ); + } + ~Base() { + // FIXME cleanup all btree buckets. + theDataFileMgr.deleteRecord( ns(), idx_.info.rec(), idx_.info ); + ASSERT( theDataFileMgr.findAll( ns() )->eof() ); + } + protected: + BtreeBucket* bt() const { + return idx_.head.btree(); + } + DiskLoc dl() const { + return idx_.head; + } + IndexDetails& id() { + return idx_; + } + static const char* ns() { + return "unittests.btreetests"; + } + // dummy, valid record loc + static DiskLoc recordLoc() { + return DiskLoc( 0, 2 ); + } + void checkValid( int nKeys ) const { + ASSERT( bt() ); + ASSERT( bt()->isHead() ); + bt()->assertValid( order(), true ); + ASSERT_EQUALS( nKeys, bt()->fullValidate( dl(), order() ) ); + } + void insert( BSONObj &key ) { + bt()->bt_insert( dl(), recordLoc(), key, order(), true, id(), true ); + } + void unindex( BSONObj &key ) { + bt()->unindex( dl(), id(), key, recordLoc() ); + } + static BSONObj simpleKey( char c, int n = 1 ) { + BSONObjBuilder builder; + string val( n, c ); + builder.append( "a", val ); + return builder.obj(); + } + void locate( BSONObj &key, int expectedPos, + bool expectedFound, const DiskLoc &expectedLocation, + int direction = 1 ) { + int pos; + bool found; + DiskLoc location = + bt()->locate( id(), dl(), key, order(), pos, found, recordLoc(), direction ); + ASSERT_EQUALS( expectedFound, found ); + ASSERT( location == expectedLocation ); + ASSERT_EQUALS( expectedPos, pos ); + } + BSONObj order() const { + return idx_.keyPattern(); + } + private: + dblock lk_; + IndexDetails idx_; + }; + + class Create : public Base { + public: + void run() { + checkValid( 0 ); + } + }; + + class SimpleInsertDelete : public Base { + public: + void run() { + BSONObj key = simpleKey( 'z' ); + insert( key ); + + checkValid( 1 ); + locate( key, 0, true, dl() ); + + unindex( key ); + + checkValid( 0 ); + locate( key, 0, false, DiskLoc() ); + } + }; + + class SplitUnevenBucketBase : public Base { + public: + virtual ~SplitUnevenBucketBase() {} + void run() { + for ( int i = 0; i < 10; ++i ) { + BSONObj shortKey = simpleKey( shortToken( i ), 1 ); + insert( shortKey ); + BSONObj longKey = simpleKey( longToken( i ), 800 ); + insert( longKey ); + } + checkValid( 20 ); + } + protected: + virtual char shortToken( int i ) const = 0; + virtual char longToken( int i ) const = 0; + static char leftToken( int i ) { + return 'a' + i; + } + static char rightToken( int i ) { + return 'z' - i; + } + }; + + class SplitRightHeavyBucket : public SplitUnevenBucketBase { + private: + virtual char shortToken( int i ) const { + return leftToken( i ); + } + virtual char longToken( int i ) const { + return rightToken( i ); + } + }; + + class SplitLeftHeavyBucket : public SplitUnevenBucketBase { + private: + virtual char shortToken( int i ) const { + return rightToken( i ); + } + virtual char longToken( int i ) const { + return leftToken( i ); + } + }; + + class MissingLocate : public Base { + public: + void run() { + for ( int i = 0; i < 3; ++i ) { + BSONObj k = simpleKey( 'b' + 2 * i ); + insert( k ); + } + + locate( 1, 'a', 'b', dl() ); + locate( 1, 'c', 'd', dl() ); + locate( 1, 'e', 'f', dl() ); + locate( 1, 'g', 'g' + 1, DiskLoc() ); // of course, 'h' isn't in the index. + + // old behavior + // locate( -1, 'a', 'b', dl() ); + // locate( -1, 'c', 'd', dl() ); + // locate( -1, 'e', 'f', dl() ); + // locate( -1, 'g', 'f', dl() ); + + locate( -1, 'a', 'a' - 1, DiskLoc() ); // of course, 'a' - 1 isn't in the index + locate( -1, 'c', 'b', dl() ); + locate( -1, 'e', 'd', dl() ); + locate( -1, 'g', 'f', dl() ); + } + private: + void locate( int direction, char token, char expectedMatch, + DiskLoc expectedLocation ) { + BSONObj k = simpleKey( token ); + int expectedPos = ( expectedMatch - 'b' ) / 2; + Base::locate( k, expectedPos, false, expectedLocation, direction ); + } + }; + + class MissingLocateMultiBucket : public Base { + public: + void run() { + for ( int i = 0; i < 10; ++i ) { + BSONObj k = key( 'b' + 2 * i ); + insert( k ); + } + BSONObj straddle = key( 'i' ); + locate( straddle, 0, false, dl(), 1 ); + straddle = key( 'k' ); + locate( straddle, 0, false, dl(), -1 ); + } + private: + BSONObj key( char c ) { + return simpleKey( c, 800 ); + } + }; + + class All : public Suite { + public: + All() : Suite( "btree" ){ + } + + void setupTests(){ + add< Create >(); + add< SimpleInsertDelete >(); + add< SplitRightHeavyBucket >(); + add< SplitLeftHeavyBucket >(); + add< MissingLocate >(); + add< MissingLocateMultiBucket >(); + } + } myall; +} + diff --git a/dbtests/clienttests.cpp b/dbtests/clienttests.cpp new file mode 100644 index 0000000..1dadd1a --- /dev/null +++ b/dbtests/clienttests.cpp @@ -0,0 +1,98 @@ +// client.cpp + +#include "stdafx.h" +#include "../client/dbclient.h" +#include "dbtests.h" +#include "../db/concurrency.h" + +namespace ClientTests { + + class Base { + public: + + Base( string coll ){ + _ns = (string)"test." + coll; + } + + virtual ~Base(){ + db.dropCollection( _ns ); + } + + const char * ns(){ return _ns.c_str(); } + + string _ns; + DBDirectClient db; + }; + + + class DropIndex : public Base { + public: + DropIndex() : Base( "dropindex" ){} + void run(){ + db.insert( ns() , BSON( "x" << 2 ) ); + ASSERT_EQUALS( 1 , db.getIndexes( ns() )->itcount() ); + + db.ensureIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + + db.dropIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 1 , db.getIndexes( ns() )->itcount() ); + + db.ensureIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + + db.dropIndexes( ns() ); + ASSERT_EQUALS( 1 , db.getIndexes( ns() )->itcount() ); + } + }; + + class ReIndex : public Base { + public: + ReIndex() : Base( "reindex" ){} + void run(){ + + db.insert( ns() , BSON( "x" << 2 ) ); + ASSERT_EQUALS( 1 , db.getIndexes( ns() )->itcount() ); + + db.ensureIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + + db.reIndex( ns() ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + } + + }; + + class ReIndex2 : public Base { + public: + ReIndex2() : Base( "reindex2" ){} + void run(){ + + db.insert( ns() , BSON( "x" << 2 ) ); + ASSERT_EQUALS( 1 , db.getIndexes( ns() )->itcount() ); + + db.ensureIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + + BSONObj out; + ASSERT( db.runCommand( "test" , BSON( "reIndex" << "reindex2" ) , out ) ); + ASSERT_EQUALS( 2 , out["nIndexes"].number() ); + ASSERT_EQUALS( 2 , db.getIndexes( ns() )->itcount() ); + } + + }; + + + class All : public Suite { + public: + All() : Suite( "client" ){ + } + + void setupTests(){ + add<DropIndex>(); + add<ReIndex>(); + add<ReIndex2>(); + } + + } all; +} diff --git a/dbtests/cursortests.cpp b/dbtests/cursortests.cpp new file mode 100644 index 0000000..28a4ba4 --- /dev/null +++ b/dbtests/cursortests.cpp @@ -0,0 +1,127 @@ +// cusrortests.cpp // cursor related unit tests +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/db.h" +#include "../db/clientcursor.h" +#include "../db/instance.h" +#include "../db/btree.h" +#include "dbtests.h" + +namespace CursorTests { + + namespace BtreeCursorTests { + + class MultiRange { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRange"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 1 ), BSON( "" << 2 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 4 ), BSON( "" << 6 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->idx(1), b, 1 ); + ASSERT_EQUALS( "BtreeCursor a_1 multi", c.toString() ); + double expected[] = { 1, 2, 4, 5, 6 }; + for( int i = 0; i < 5; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + class MultiRangeGap { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeGap"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + for( int i = 100; i < 110; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << -50 ), BSON( "" << 2 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 40 ), BSON( "" << 60 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 109 ), BSON( "" << 200 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->idx(1), b, 1 ); + ASSERT_EQUALS( "BtreeCursor a_1 multi", c.toString() ); + double expected[] = { 0, 1, 2, 109 }; + for( int i = 0; i < 4; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + class MultiRangeReverse { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeReverse"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 6 ), BSON( "" << 4 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 2 ), BSON( "" << 1 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->idx(1), b, -1 ); + ASSERT_EQUALS( "BtreeCursor a_1 reverse multi", c.toString() ); + double expected[] = { 6, 5, 4, 2, 1 }; + for( int i = 0; i < 5; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + } // namespace MultiBtreeCursorTests + + class All : public Suite { + public: + All() : Suite( "cursor" ){} + + void setupTests(){ + add< BtreeCursorTests::MultiRange >(); + add< BtreeCursorTests::MultiRangeGap >(); + add< BtreeCursorTests::MultiRangeReverse >(); + } + } myall; +} // namespace CursorTests diff --git a/dbtests/dbtests.cpp b/dbtests/dbtests.cpp new file mode 100644 index 0000000..3821163 --- /dev/null +++ b/dbtests/dbtests.cpp @@ -0,0 +1,27 @@ +// dbtests.cpp : Runs db unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#include "dbtests.h" + +int main( int argc, char** argv ) { + return Suite::run(argc, argv, "/tmp/unittest"); +} + diff --git a/dbtests/dbtests.h b/dbtests/dbtests.h new file mode 100644 index 0000000..3184a05 --- /dev/null +++ b/dbtests/dbtests.h @@ -0,0 +1,24 @@ +// dbtests.h : Test suite generator headers. +// + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "framework.h" + +using namespace mongo; +using namespace mongo::regression; + diff --git a/dbtests/framework.cpp b/dbtests/framework.cpp new file mode 100644 index 0000000..6ed5e72 --- /dev/null +++ b/dbtests/framework.cpp @@ -0,0 +1,361 @@ +// framework.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include <boost/program_options.hpp> + +#undef assert +#define assert xassert + +#include "framework.h" +#include "../util/file_allocator.h" + +#ifndef _WIN32 +#include <cxxabi.h> +#include <sys/file.h> +#endif + +namespace po = boost::program_options; + +namespace mongo { + + namespace regression { + + map<string,Suite*> * mongo::regression::Suite::_suites = 0; + + class Result { + public: + Result( string name ) : _name( name ) , _rc(0) , _tests(0) , _fails(0) , _asserts(0) { + } + + string toString(){ + stringstream ss; + + char result[128]; + sprintf(result, "%-20s | tests: %4d | fails: %4d | assert calls: %6d\n", _name.c_str(), _tests, _fails, _asserts); + ss << result; + + for ( list<string>::iterator i=_messages.begin(); i!=_messages.end(); i++ ){ + ss << "\t" << *i << "\n"; + } + + return ss.str(); + } + + int rc(){ + return _rc; + } + + string _name; + + int _rc; + int _tests; + int _fails; + int _asserts; + list<string> _messages; + + static Result * cur; + }; + + Result * Result::cur = 0; + + Result * Suite::run(){ + log(1) << "\t about to setupTests" << endl; + setupTests(); + log(1) << "\t done setupTests" << endl; + + Result * r = new Result( _name ); + Result::cur = r; + + /* see note in SavedContext */ + //writelock lk(""); + + for ( list<TestCase*>::iterator i=_tests.begin(); i!=_tests.end(); i++ ){ + TestCase * tc = *i; + + r->_tests++; + + bool passes = false; + + log(1) << "\t going to run test: " << tc->getName() << endl; + + stringstream err; + err << tc->getName() << "\t"; + + try { + tc->run(); + passes = true; + } + catch ( MyAssertionException * ae ){ + err << ae->ss.str(); + delete( ae ); + } + catch ( std::exception& e ){ + err << " exception: " << e.what(); + } + catch ( int x ){ + err << " caught int : " << x << endl; + } + catch ( ... ){ + cerr << "unknown exception in test: " << tc->getName() << endl; + } + + if ( ! passes ){ + string s = err.str(); + log() << "FAIL: " << s << endl; + r->_fails++; + r->_messages.push_back( s ); + } + } + + if ( r->_fails ) + r->_rc = 17; + + log(1) << "\t DONE running tests" << endl; + + return r; + } + + void show_help_text(const char* name, po::options_description options) { + cout << "usage: " << name << " [options] [suite]..." << endl + << options << "suite: run the specified test suite(s) only" << endl; + } + + int Suite::run( int argc , char** argv , string default_dbpath ) { + unsigned long long seed = time( 0 ); + string dbpathSpec; + + po::options_description shell_options("options"); + po::options_description hidden_options("Hidden options"); + po::options_description cmdline_options("Command line options"); + po::positional_options_description positional_options; + + shell_options.add_options() + ("help,h", "show this usage information") + ("dbpath", po::value<string>(&dbpathSpec)->default_value(default_dbpath), + "db data path for this test run. NOTE: the contents of this " + "directory will be overwritten if it already exists") + ("debug", "run tests with verbose output") + ("list,l", "list available test suites") + ("verbose,v", "verbose") + ("seed", po::value<unsigned long long>(&seed), "random number seed") + ; + + hidden_options.add_options() + ("suites", po::value< vector<string> >(), "test suites to run") + ; + + positional_options.add("suites", -1); + + cmdline_options.add(shell_options).add(hidden_options); + + po::variables_map params; + int command_line_style = (((po::command_line_style::unix_style ^ + po::command_line_style::allow_guessing) | + po::command_line_style::allow_long_disguise) ^ + po::command_line_style::allow_sticky); + + try { + po::store(po::command_line_parser(argc, argv).options(cmdline_options). + positional(positional_options). + style(command_line_style).run(), params); + po::notify(params); + } catch (po::error &e) { + cout << "ERROR: " << e.what() << endl << endl; + show_help_text(argv[0], shell_options); + return EXIT_BADOPTIONS; + } + + if (params.count("help")) { + show_help_text(argv[0], shell_options); + return EXIT_CLEAN; + } + + if (params.count("debug") || params.count("verbose") ) { + logLevel = 1; + } + + if (params.count("list")) { + for ( map<string,Suite*>::iterator i = _suites->begin() ; i != _suites->end(); i++ ) + cout << i->first << endl; + return 0; + } + + boost::filesystem::path p(dbpathSpec); + + /* remove the contents of the test directory if it exists. */ + if (boost::filesystem::exists(p)) { + if (!boost::filesystem::is_directory(p)) { + cout << "ERROR: path \"" << p.string() << "\" is not a directory" << endl << endl; + show_help_text(argv[0], shell_options); + return EXIT_BADOPTIONS; + } + boost::filesystem::directory_iterator end_iter; + for (boost::filesystem::directory_iterator dir_iter(p); + dir_iter != end_iter; ++dir_iter) { + boost::filesystem::remove_all(*dir_iter); + } + } else { + boost::filesystem::create_directory(p); + } + + string dbpathString = p.native_directory_string(); + dbpath = dbpathString.c_str(); + + cmdLine.prealloc = false; + cmdLine.smallfiles = true; + cmdLine.oplogSize = 10 * 1024 * 1024; + Client::initThread("testsuite"); + acquirePathLock(); + + srand( (unsigned) seed ); + printGitVersion(); + printSysInfo(); + out() << "random seed: " << seed << endl; + + theFileAllocator().start(); + + vector<string> suites; + if (params.count("suites")) { + suites = params["suites"].as< vector<string> >(); + } + int ret = run(suites); + +#if !defined(_WIN32) && !defined(__sunos__) + flock( lockFile, LOCK_UN ); +#endif + + cc().shutdown(); + dbexit( (ExitCode)ret ); // so everything shuts down cleanly + return ret; + } + + int Suite::run( vector<string> suites ){ + for ( unsigned int i = 0; i < suites.size(); i++ ) { + if ( _suites->find( suites[i] ) == _suites->end() ) { + cout << "invalid test [" << suites[i] << "], use --list to see valid names" << endl; + return -1; + } + } + + list<string> torun(suites.begin(), suites.end()); + + if ( torun.size() == 0 ) + for ( map<string,Suite*>::iterator i=_suites->begin() ; i!=_suites->end(); i++ ) + torun.push_back( i->first ); + + list<Result*> results; + + for ( list<string>::iterator i=torun.begin(); i!=torun.end(); i++ ){ + string name = *i; + Suite * s = (*_suites)[name]; + assert( s ); + + log() << "going to run suite: " << name << endl; + results.push_back( s->run() ); + } + + Logstream::get().flush(); + + cout << "**************************************************" << endl; + cout << "**************************************************" << endl; + cout << "**************************************************" << endl; + + int rc = 0; + + int tests = 0; + int fails = 0; + int asserts = 0; + + for ( list<Result*>::iterator i=results.begin(); i!=results.end(); i++ ){ + Result * r = *i; + cout << r->toString(); + if ( abs( r->rc() ) > abs( rc ) ) + rc = r->rc(); + + tests += r->_tests; + fails += r->_fails; + asserts += r->_asserts; + } + + Result totals ("TOTALS"); + totals._tests = tests; + totals._fails = fails; + totals._asserts = asserts; + + cout << totals.toString(); // includes endl + + return rc; + } + + void Suite::registerSuite( string name , Suite * s ){ + if ( ! _suites ) + _suites = new map<string,Suite*>(); + Suite*& m = (*_suites)[name]; + uassert( 10162 , "already have suite with that name" , ! m ); + m = s; + } + + void assert_pass(){ + Result::cur->_asserts++; + } + + void assert_fail( const char * exp , const char * file , unsigned line ){ + Result::cur->_asserts++; + + MyAssertionException * e = new MyAssertionException(); + e->ss << "ASSERT FAILED! " << file << ":" << line << endl; + throw e; + } + + void fail( const char * exp , const char * file , unsigned line ){ + assert(0); + } + + string demangleName( const type_info& typeinfo ){ +#ifdef _WIN32 + return typeinfo.name(); +#else + int status; + + char * niceName = abi::__cxa_demangle(typeinfo.name(), 0, 0, &status); + if ( ! niceName ) + return typeinfo.name(); + + string s = niceName; + free(niceName); + return s; +#endif + } + + MyAssertionException * MyAsserts::getBase(){ + MyAssertionException * e = new MyAssertionException(); + e->ss << _file << ":" << _line << " " << _aexp << " != " << _bexp << " "; + return e; + } + + void MyAsserts::printLocation(){ + log() << _file << ":" << _line << " " << _aexp << " != " << _bexp << " "; + } + + void MyAsserts::_gotAssert(){ + Result::cur->_asserts++; + } + + } +} diff --git a/dbtests/framework.h b/dbtests/framework.h new file mode 100644 index 0000000..710b880 --- /dev/null +++ b/dbtests/framework.h @@ -0,0 +1,184 @@ +// framework.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + + simple portable regression system + */ + +#include "../stdafx.h" + +#define ASSERT_EXCEPTION(a,b) \ + try { \ + a; \ + mongo::regression::assert_fail( #a , __FILE__ , __LINE__ ); \ + } catch ( b& ){ \ + mongo::regression::assert_pass(); \ + } + + + +#define ASSERT_EQUALS(a,b) (mongo::regression::MyAsserts( #a , #b , __FILE__ , __LINE__ ) ).ae( (a) , (b) ) +#define ASSERT(x) (void)( (!(!(x))) ? mongo::regression::assert_pass() : mongo::regression::assert_fail( #x , __FILE__ , __LINE__ ) ) +#define FAIL(x) mongo::regression::fail( #x , __FILE__ , __LINE__ ) + +#include "../db/instance.h" + +namespace mongo { + + namespace regression { + + class Result; + + string demangleName( const type_info& typeinfo ); + + class TestCase { + public: + virtual ~TestCase(){} + virtual void run() = 0; + virtual string getName() = 0; + }; + + template< class T > + class TestHolderBase : public TestCase { + public: + TestHolderBase(){} + virtual ~TestHolderBase(){} + virtual void run(){ + auto_ptr<T> t; + t.reset( create() ); + t->run(); + } + virtual T * create() = 0; + virtual string getName(){ + return demangleName( typeid(T) ); + } + }; + + template< class T > + class TestHolder0 : public TestHolderBase<T> { + public: + virtual T * create(){ + return new T(); + } + }; + + template< class T , typename A > + class TestHolder1 : public TestHolderBase<T> { + public: + TestHolder1( const A& a ) : _a(a){} + virtual T * create(){ + return new T( _a ); + } + const A& _a; + }; + + class Suite { + public: + Suite( string name ) : _name( name ){ + registerSuite( name , this ); + _ran = 0; + } + + virtual ~Suite() { + if ( _ran ){ + DBDirectClient c; + c.dropDatabase( "unittests" ); + } + } + + template<class T> + void add(){ + _tests.push_back( new TestHolder0<T>() ); + } + + template<class T , typename A > + void add( const A& a ){ + _tests.push_back( new TestHolder1<T,A>(a) ); + } + + Result * run(); + + static int run( vector<string> suites ); + static int run( int argc , char ** argv , string default_dbpath ); + + + protected: + virtual void setupTests() = 0; + + private: + string _name; + list<TestCase*> _tests; + bool _ran; + + static map<string,Suite*> * _suites; + + void registerSuite( string name , Suite * s ); + }; + + void assert_pass(); + void assert_fail( const char * exp , const char * file , unsigned line ); + void fail( const char * exp , const char * file , unsigned line ); + + class MyAssertionException : boost::noncopyable { + public: + MyAssertionException(){ + ss << "assertion: "; + } + stringstream ss; + }; + + + + class MyAsserts { + public: + MyAsserts( const char * aexp , const char * bexp , const char * file , unsigned line ) + : _aexp( aexp ) , _bexp( bexp ) , _file( file ) , _line( line ){ + + } + + template<typename A,typename B> + void ae( A a , B b ){ + _gotAssert(); + if ( a == b ) + return; + + printLocation(); + + MyAssertionException * e = getBase(); + e->ss << a << " != " << b << endl; + log() << e->ss.str() << endl; + throw e; + } + + void printLocation(); + + private: + + void _gotAssert(); + + MyAssertionException * getBase(); + + string _aexp; + string _bexp; + string _file; + unsigned _line; + }; + + } +} diff --git a/dbtests/jsobjtests.cpp b/dbtests/jsobjtests.cpp new file mode 100644 index 0000000..0402426 --- /dev/null +++ b/dbtests/jsobjtests.cpp @@ -0,0 +1,1340 @@ +// jsobjtests.cpp - Tests for jsobj.{h,cpp} code +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/jsobj.h" +#include "../db/jsobjmanipulator.h" +#include "../db/json.h" +#include "../db/repl.h" +#include "../db/extsort.h" + +#include "dbtests.h" + +namespace JsobjTests { + class BufBuilderBasic { + public: + void run() { + BufBuilder b( 0 ); + b.append( "foo" ); + ASSERT_EQUALS( 4, b.len() ); + ASSERT( strcmp( "foo", b.buf() ) == 0 ); + } + }; + + class BSONElementBasic { + public: + void run() { + ASSERT_EQUALS( 1, BSONElement().size() ); + } + }; + + namespace BSONObjTests { + class Create { + public: + void run() { + BSONObj b; + ASSERT_EQUALS( 0, b.nFields() ); + } + }; + + class Base { + protected: + static BSONObj basic( const char *name, int val ) { + BSONObjBuilder b; + b.append( name, val ); + return b.obj(); + } + static BSONObj basic( const char *name, vector< int > val ) { + BSONObjBuilder b; + b.append( name, val ); + return b.obj(); + } + template< class T > + static BSONObj basic( const char *name, T val ) { + BSONObjBuilder b; + b.append( name, val ); + return b.obj(); + } + }; + + class WoCompareBasic : public Base { + public: + void run() { + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 1 ) ) == 0 ); + ASSERT( basic( "a", 2 ).woCompare( basic( "a", 1 ) ) > 0 ); + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 2 ) ) < 0 ); + // field name comparison + ASSERT( basic( "a", 1 ).woCompare( basic( "b", 1 ) ) < 0 ); + } + }; + + class NumericCompareBasic : public Base { + public: + void run() { + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 1.0 ) ) == 0 ); + } + }; + + class WoCompareEmbeddedObject : public Base { + public: + void run() { + ASSERT( basic( "a", basic( "b", 1 ) ).woCompare + ( basic( "a", basic( "b", 1.0 ) ) ) == 0 ); + ASSERT( basic( "a", basic( "b", 1 ) ).woCompare + ( basic( "a", basic( "b", 2 ) ) ) < 0 ); + } + }; + + class WoCompareEmbeddedArray : public Base { + public: + void run() { + vector< int > i; + i.push_back( 1 ); + i.push_back( 2 ); + vector< double > d; + d.push_back( 1 ); + d.push_back( 2 ); + ASSERT( basic( "a", i ).woCompare( basic( "a", d ) ) == 0 ); + + vector< int > j; + j.push_back( 1 ); + j.push_back( 3 ); + ASSERT( basic( "a", i ).woCompare( basic( "a", j ) ) < 0 ); + } + }; + + class WoCompareOrdered : public Base { + public: + void run() { + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 1 ), basic( "a", 1 ) ) == 0 ); + ASSERT( basic( "a", 2 ).woCompare( basic( "a", 1 ), basic( "a", 1 ) ) > 0 ); + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 2 ), basic( "a", 1 ) ) < 0 ); + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 1 ), basic( "a", -1 ) ) == 0 ); + ASSERT( basic( "a", 2 ).woCompare( basic( "a", 1 ), basic( "a", -1 ) ) < 0 ); + ASSERT( basic( "a", 1 ).woCompare( basic( "a", 2 ), basic( "a", -1 ) ) > 0 ); + } + }; + + class WoCompareDifferentLength : public Base { + public: + void run() { + ASSERT( BSON( "a" << 1 ).woCompare( BSON( "a" << 1 << "b" << 1 ) ) < 0 ); + ASSERT( BSON( "a" << 1 << "b" << 1 ).woCompare( BSON( "a" << 1 ) ) > 0 ); + } + }; + + class WoSortOrder : public Base { + public: + void run() { + ASSERT( BSON( "a" << 1 ).woSortOrder( BSON( "a" << 2 ), BSON( "b" << 1 << "a" << 1 ) ) < 0 ); + ASSERT( fromjson( "{a:null}" ).woSortOrder( BSON( "b" << 1 ), BSON( "a" << 1 ) ) == 0 ); + } + }; + + class MultiKeySortOrder : public Base { + public: + void run(){ + ASSERT( BSON( "x" << "a" ).woCompare( BSON( "x" << "b" ) ) < 0 ); + ASSERT( BSON( "x" << "b" ).woCompare( BSON( "x" << "a" ) ) > 0 ); + + ASSERT( BSON( "x" << "a" << "y" << "a" ).woCompare( BSON( "x" << "a" << "y" << "b" ) ) < 0 ); + ASSERT( BSON( "x" << "a" << "y" << "a" ).woCompare( BSON( "x" << "b" << "y" << "a" ) ) < 0 ); + ASSERT( BSON( "x" << "a" << "y" << "a" ).woCompare( BSON( "x" << "b" ) ) < 0 ); + + ASSERT( BSON( "x" << "c" ).woCompare( BSON( "x" << "b" << "y" << "h" ) ) > 0 ); + ASSERT( BSON( "x" << "b" << "y" << "b" ).woCompare( BSON( "x" << "c" ) ) < 0 ); + + BSONObj key = BSON( "x" << 1 << "y" << 1 ); + + ASSERT( BSON( "x" << "c" ).woSortOrder( BSON( "x" << "b" << "y" << "h" ) , key ) > 0 ); + ASSERT( BSON( "x" << "b" << "y" << "b" ).woCompare( BSON( "x" << "c" ) , key ) < 0 ); + + key = BSON( "" << 1 << "" << 1 ); + + ASSERT( BSON( "" << "c" ).woSortOrder( BSON( "" << "b" << "" << "h" ) , key ) > 0 ); + ASSERT( BSON( "" << "b" << "" << "b" ).woCompare( BSON( "" << "c" ) , key ) < 0 ); + + { + BSONObjBuilder b; + b.append( "" , "c" ); + b.appendNull( "" ); + BSONObj o = b.obj(); + ASSERT( o.woSortOrder( BSON( "" << "b" << "" << "h" ) , key ) > 0 ); + ASSERT( BSON( "" << "b" << "" << "h" ).woSortOrder( o , key ) < 0 ); + + } + + + ASSERT( BSON( "" << "a" ).woCompare( BSON( "" << "a" << "" << "c" ) ) < 0 ); + { + BSONObjBuilder b; + b.append( "" , "a" ); + b.appendNull( "" ); + ASSERT( b.obj().woCompare( BSON( "" << "a" << "" << "c" ) ) < 0 ); // SERVER-282 + } + + } + }; + + class TimestampTest : public Base { + public: + void run() { + BSONObjBuilder b; + b.appendTimestamp( "a" ); + BSONObj o = b.done(); + o.toString(); + ASSERT( o.valid() ); + ASSERT_EQUALS( Timestamp, o.getField( "a" ).type() ); + BSONObjIterator i( o ); + ASSERT( i.moreWithEOO() ); + ASSERT( i.more() ); + + BSONElement e = i.next(); + ASSERT_EQUALS( Timestamp, e.type() ); + ASSERT( i.moreWithEOO() ); + ASSERT( ! i.more() ); + + e = i.next(); + ASSERT( e.eoo() ); + + OpTime before = OpTime::now(); + BSONElementManipulator( o.firstElement() ).initTimestamp(); + OpTime after = OpTime::now(); + + OpTime test = OpTime( o.firstElement().date() ); + ASSERT( before < test && test < after ); + + BSONElementManipulator( o.firstElement() ).initTimestamp(); + test = OpTime( o.firstElement().date() ); + ASSERT( before < test && test < after ); + } + }; + + class Nan : public Base { + public: + void run() { + double inf = numeric_limits< double >::infinity(); + double nan = numeric_limits< double >::quiet_NaN(); + double nan2 = numeric_limits< double >::signaling_NaN(); + + ASSERT( BSON( "a" << inf ).woCompare( BSON( "a" << inf ) ) == 0 ); + ASSERT( BSON( "a" << inf ).woCompare( BSON( "a" << 1 ) ) < 0 ); + ASSERT( BSON( "a" << 1 ).woCompare( BSON( "a" << inf ) ) > 0 ); + + ASSERT( BSON( "a" << nan ).woCompare( BSON( "a" << nan ) ) == 0 ); + ASSERT( BSON( "a" << nan ).woCompare( BSON( "a" << 1 ) ) < 0 ); + ASSERT( BSON( "a" << 1 ).woCompare( BSON( "a" << nan ) ) > 0 ); + + ASSERT( BSON( "a" << nan2 ).woCompare( BSON( "a" << nan2 ) ) == 0 ); + ASSERT( BSON( "a" << nan2 ).woCompare( BSON( "a" << 1 ) ) < 0 ); + ASSERT( BSON( "a" << 1 ).woCompare( BSON( "a" << nan2 ) ) > 0 ); + + ASSERT( BSON( "a" << inf ).woCompare( BSON( "a" << nan ) ) == 0 ); + ASSERT( BSON( "a" << inf ).woCompare( BSON( "a" << nan2 ) ) == 0 ); + ASSERT( BSON( "a" << nan ).woCompare( BSON( "a" << nan2 ) ) == 0 ); + } + }; + + namespace Validation { + + class Base { + public: + virtual ~Base() {} + void run() { + ASSERT( valid().valid() ); + ASSERT( !invalid().valid() ); + } + protected: + virtual BSONObj valid() const { return BSONObj(); } + virtual BSONObj invalid() const { return BSONObj(); } + static char get( const BSONObj &o, int i ) { + return o.objdata()[ i ]; + } + static void set( BSONObj &o, int i, char c ) { + const_cast< char * >( o.objdata() )[ i ] = c; + } + }; + + class BadType : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":1}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 4, 50 ); + return ret; + } + }; + + class EooBeforeEnd : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":1}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + // (first byte of size)++ + set( ret, 0, get( ret, 0 ) + 1 ); + // re-read size for BSONObj::details + return ret.copy(); + } + }; + + class Undefined : public Base { + public: + void run() { + BSONObjBuilder b; + b.appendNull( "a" ); + BSONObj o = b.done(); + set( o, 4, mongo::Undefined ); + ASSERT( o.valid() ); + } + }; + + class TotalSizeTooSmall : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":1}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + // (first byte of size)-- + set( ret, 0, get( ret, 0 ) - 1 ); + // re-read size for BSONObj::details + return ret.copy(); + } + }; + + class EooMissing : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":1}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, ret.objsize() - 1, (char) 0xff ); + // (first byte of size)-- + set( ret, 0, get( ret, 0 ) - 1 ); + // re-read size for BSONObj::details + return ret.copy(); + } + }; + + class WrongStringSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":\"b\"}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 0, get( ret, 0 ) + 1 ); + set( ret, 7, get( ret, 7 ) + 1 ); + return ret.copy(); + } + }; + + class ZeroStringSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":\"b\"}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 7, 0 ); + return ret; + } + }; + + class NegativeStringSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":\"b\"}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 10, -100 ); + return ret; + } + }; + + class WrongSubobjectSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":{\"b\":1}}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 0, get( ret, 0 ) + 1 ); + set( ret, 7, get( ret, 7 ) + 1 ); + return ret.copy(); + } + }; + + class WrongDbrefNsSize : public Base { + BSONObj valid() const { + return fromjson( "{ \"a\": Dbref( \"b\", \"ffffffffffffffffffffffff\" ) }" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 0, get( ret, 0 ) + 1 ); + set( ret, 7, get( ret, 7 ) + 1 ); + return ret.copy(); + }; + }; + + class WrongSymbolSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":\"b\"}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 4, Symbol ); + set( ret, 0, get( ret, 0 ) + 1 ); + set( ret, 7, get( ret, 7 ) + 1 ); + return ret.copy(); + } + }; + + class WrongCodeSize : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":\"b\"}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + set( ret, 4, Code ); + set( ret, 0, get( ret, 0 ) + 1 ); + set( ret, 7, get( ret, 7 ) + 1 ); + return ret.copy(); + } + }; + + class NoFieldNameEnd : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":1}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + memset( const_cast< char * >( ret.objdata() ) + 5, 0xff, ret.objsize() - 5 ); + return ret; + } + }; + + class BadRegex : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":/c/i}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + memset( const_cast< char * >( ret.objdata() ) + 7, 0xff, ret.objsize() - 7 ); + return ret; + } + }; + + class BadRegexOptions : public Base { + BSONObj valid() const { + return fromjson( "{\"a\":/c/i}" ); + } + BSONObj invalid() const { + BSONObj ret = valid(); + memset( const_cast< char * >( ret.objdata() ) + 9, 0xff, ret.objsize() - 9 ); + return ret; + } + }; + + class CodeWScopeBase : public Base { + BSONObj valid() const { + BSONObjBuilder b; + BSONObjBuilder scope; + scope.append( "a", "b" ); + b.appendCodeWScope( "c", "d", scope.done() ); + return b.obj(); + } + BSONObj invalid() const { + BSONObj ret = valid(); + modify( ret ); + return ret; + } + protected: + virtual void modify( BSONObj &o ) const = 0; + }; + + class CodeWScopeSmallSize : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 7, 7 ); + } + }; + + class CodeWScopeZeroStrSize : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 11, 0 ); + } + }; + + class CodeWScopeSmallStrSize : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 11, 1 ); + } + }; + + class CodeWScopeNoSizeForObj : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 7, 13 ); + } + }; + + class CodeWScopeSmallObjSize : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 17, 1 ); + } + }; + + class CodeWScopeBadObject : public CodeWScopeBase { + void modify( BSONObj &o ) const { + set( o, 21, JSTypeMax + 1 ); + } + }; + + class NoSize { + public: + NoSize( BSONType type ) : type_( type ) {} + void run() { + const char data[] = { 0x07, 0x00, 0x00, 0x00, char( type_ ), 'a', 0x00 }; + BSONObj o( data ); + ASSERT( !o.valid() ); + } + private: + BSONType type_; + }; + + // Randomized BSON parsing test. See if we seg fault. + class Fuzz { + public: + Fuzz( double frequency ) : frequency_( frequency ) {} + void run() { + BSONObj a = fromjson( "{\"a\": 1, \"b\": \"c\"}" ); + fuzz( a ); + a.valid(); + + BSONObj b = fromjson( "{\"one\":2, \"two\":5, \"three\": {}," + "\"four\": { \"five\": { \"six\" : 11 } }," + "\"seven\": [ \"a\", \"bb\", \"ccc\", 5 ]," + "\"eight\": Dbref( \"rrr\", \"01234567890123456789aaaa\" )," + "\"_id\": ObjectId( \"deadbeefdeadbeefdeadbeef\" )," + "\"nine\": { \"$binary\": \"abc=\", \"$type\": \"02\" }," + "\"ten\": Date( 44 ), \"eleven\": /foooooo/i }" ); + fuzz( b ); + b.valid(); + } + private: + void fuzz( BSONObj &o ) const { + for( int i = 4; i < o.objsize(); ++i ) + for( unsigned char j = 1; j; j <<= 1 ) + if ( rand() < int( RAND_MAX * frequency_ ) ) { + char *c = const_cast< char * >( o.objdata() ) + i; + if ( *c & j ) + *c &= ~j; + else + *c |= j; + } + } + double frequency_; + }; + + } // namespace Validation + + } // namespace BSONObjTests + + namespace OIDTests { + + class init1 { + public: + void run(){ + OID a; + OID b; + + a.init(); + b.init(); + + ASSERT( a != b ); + } + }; + + class initParse1 { + public: + void run(){ + + OID a; + OID b; + + a.init(); + b.init( a.str() ); + + ASSERT( a == b ); + } + }; + + class append { + public: + void run(){ + BSONObjBuilder b; + b.appendOID( "a" , 0 ); + b.appendOID( "b" , 0 , false ); + b.appendOID( "c" , 0 , true ); + BSONObj o = b.obj(); + + ASSERT( o["a"].__oid().str() == "000000000000000000000000" ); + ASSERT( o["b"].__oid().str() == "000000000000000000000000" ); + ASSERT( o["c"].__oid().str() != "000000000000000000000000" ); + + } + }; + + class increasing { + public: + BSONObj g(){ + BSONObjBuilder b; + b.appendOID( "_id" , 0 , true ); + return b.obj(); + } + void run(){ + BSONObj a = g(); + BSONObj b = g(); + + ASSERT( a.woCompare( b ) < 0 ); + + // yes, there is a 1/1000 chance this won't increase time(0) + // and therefore inaccurately say the function is behaving + // buf if its broken, it will fail 999/1000, so i think that's good enough + sleepsecs( 1 ); + BSONObj c = g(); + ASSERT( a.woCompare( c ) < 0 ); + } + }; + } // namespace OIDTests + + namespace ValueStreamTests { + + class LabelBase { + public: + virtual ~LabelBase() {} + void run() { + ASSERT( !expected().woCompare( actual() ) ); + } + protected: + virtual BSONObj expected() = 0; + virtual BSONObj actual() = 0; + }; + + class LabelBasic : public LabelBase { + BSONObj expected() { + return BSON( "a" << ( BSON( "$gt" << 1 ) ) ); + } + BSONObj actual() { + return BSON( "a" << GT << 1 ); + } + }; + + class LabelShares : public LabelBase { + BSONObj expected() { + return BSON( "z" << "q" << "a" << ( BSON( "$gt" << 1 ) ) << "x" << "p" ); + } + BSONObj actual() { + return BSON( "z" << "q" << "a" << GT << 1 << "x" << "p" ); + } + }; + + class LabelDouble : public LabelBase { + BSONObj expected() { + return BSON( "a" << ( BSON( "$gt" << 1 << "$lte" << "x" ) ) ); + } + BSONObj actual() { + return BSON( "a" << GT << 1 << LTE << "x" ); + } + }; + + class LabelDoubleShares : public LabelBase { + BSONObj expected() { + return BSON( "z" << "q" << "a" << ( BSON( "$gt" << 1 << "$lte" << "x" ) ) << "x" << "p" ); + } + BSONObj actual() { + return BSON( "z" << "q" << "a" << GT << 1 << LTE << "x" << "x" << "p" ); + } + }; + + class LabelSize : public LabelBase { + BSONObj expected() { + return BSON( "a" << BSON( "$size" << 4 ) ); + } + BSONObj actual() { + return BSON( "a" << mongo::SIZE << 4 ); + } + }; + + class LabelMulti : public LabelBase { + BSONObj expected() { + return BSON( "z" << "q" + << "a" << BSON( "$gt" << 1 << "$lte" << "x" ) + << "b" << BSON( "$ne" << 1 << "$ne" << "f" << "$ne" << 22.3 ) + << "x" << "p" ); + } + BSONObj actual() { + return BSON( "z" << "q" + << "a" << GT << 1 << LTE << "x" + << "b" << NE << 1 << NE << "f" << NE << 22.3 + << "x" << "p" ); + } + }; + + class Unallowed { + public: + void run() { + ASSERT_EXCEPTION( BSON( GT << 4 ), MsgAssertionException ); + ASSERT_EXCEPTION( BSON( "a" << 1 << GT << 4 ), MsgAssertionException ); + } + }; + + class ElementAppend { + public: + void run(){ + BSONObj a = BSON( "a" << 17 ); + BSONObj b = BSON( "b" << a["a"] ); + ASSERT_EQUALS( NumberInt , a["a"].type() ); + ASSERT_EQUALS( NumberInt , b["b"].type() ); + ASSERT_EQUALS( 17 , b["b"].number() ); + } + }; + + } // namespace ValueStreamTests + + class SubObjectBuilder { + public: + void run() { + BSONObjBuilder b1; + b1.append( "a", "bcd" ); + BSONObjBuilder b2( b1.subobjStart( "foo" ) ); + b2.append( "ggg", 44.0 ); + b2.done(); + b1.append( "f", 10.0 ); + BSONObj ret = b1.done(); + ASSERT( ret.valid() ); + ASSERT( ret.woCompare( fromjson( "{a:'bcd',foo:{ggg:44},f:10}" ) ) == 0 ); + } + }; + + class DateBuilder { + public: + void run() { + BSONObj o = BSON("" << Date_t(1234567890)); + ASSERT( o.firstElement().type() == Date ); + ASSERT( o.firstElement().date() == Date_t(1234567890) ); + } + }; + + class DateNowBuilder { + public: + void run() { + Date_t before = jsTime(); + BSONObj o = BSON("now" << DATENOW); + Date_t after = jsTime(); + + ASSERT( o.valid() ); + + BSONElement e = o["now"]; + ASSERT( e.type() == Date ); + ASSERT( e.date() >= before ); + ASSERT( e.date() <= after ); + } + }; + + class TimeTBuilder { + public: + void run() { + Date_t before = jsTime(); + time_t now = time(NULL); + Date_t after = jsTime(); + + BSONObjBuilder b; + b.appendTimeT("now", now); + BSONObj o = b.obj(); + + ASSERT( o.valid() ); + + BSONElement e = o["now"]; + ASSERT( e.type() == Date ); + ASSERT( e.date()/1000 >= before/1000 ); + ASSERT( e.date()/1000 <= after/1000 ); + } + }; + + class MinMaxElementTest { + public: + + BSONObj min( int t ){ + BSONObjBuilder b; + b.appendMinForType( "a" , t ); + return b.obj(); + } + + BSONObj max( int t ){ + BSONObjBuilder b; + b.appendMaxForType( "a" , t ); + return b.obj(); + } + + void run(){ + for ( int t=1; t<JSTypeMax; t++ ){ + stringstream ss; + ss << "type: " << t; + string s = ss.str(); + massert( 10403 , s , min( t ).woCompare( max( t ) ) < 0 ); + massert( 10404 , s , max( t ).woCompare( min( t ) ) > 0 ); + massert( 10405 , s , min( t ).woCompare( min( t ) ) == 0 ); + massert( 10406 , s , max( t ).woCompare( max( t ) ) == 0 ); + massert( 10407 , s , abs( min( t ).firstElement().canonicalType() - max( t ).firstElement().canonicalType() ) <= 10 ); + } + } + + + + }; + + class ExtractFieldsTest { + public: + void run(){ + BSONObj x = BSON( "a" << 10 << "b" << 11 ); + assert( BSON( "a" << 10 ).woCompare( x.extractFields( BSON( "a" << 1 ) ) ) == 0 ); + assert( BSON( "b" << 11 ).woCompare( x.extractFields( BSON( "b" << 1 ) ) ) == 0 ); + assert( x.woCompare( x.extractFields( BSON( "a" << 1 << "b" << 1 ) ) ) == 0 ); + + assert( (string)"a" == x.extractFields( BSON( "a" << 1 << "c" << 1 ) ).firstElement().fieldName() ); + } + }; + + class ComparatorTest { + public: + BSONObj one( string s ){ + return BSON( "x" << s ); + } + BSONObj two( string x , string y ){ + BSONObjBuilder b; + b.append( "x" , x ); + if ( y.size() ) + b.append( "y" , y ); + else + b.appendNull( "y" ); + return b.obj(); + } + + void test( BSONObj order , BSONObj l , BSONObj r , bool wanted ){ + BSONObjCmp c( order ); + bool got = c(l,r); + if ( got == wanted ) + return; + cout << " order: " << order << " l: " << l << "r: " << r << " wanted: " << wanted << " got: " << got << endl; + } + + void lt( BSONObj order , BSONObj l , BSONObj r ){ + test( order , l , r , 1 ); + } + + void run(){ + BSONObj s = BSON( "x" << 1 ); + BSONObj c = BSON( "x" << 1 << "y" << 1 ); + test( s , one( "A" ) , one( "B" ) , 1 ); + test( s , one( "B" ) , one( "A" ) , 0 ); + + test( c , two( "A" , "A" ) , two( "A" , "B" ) , 1 ); + test( c , two( "A" , "A" ) , two( "B" , "A" ) , 1 ); + test( c , two( "B" , "A" ) , two( "A" , "B" ) , 0 ); + + lt( c , one("A") , two( "A" , "A" ) ); + lt( c , one("A") , one( "B" ) ); + lt( c , two("A","") , two( "B" , "A" ) ); + + lt( c , two("B","A") , two( "C" , "A" ) ); + lt( c , two("B","A") , one( "C" ) ); + lt( c , two("B","A") , two( "C" , "" ) ); + + } + }; + + namespace external_sort { + class Basic1 { + public: + void run(){ + BSONObjExternalSorter sorter; + sorter.add( BSON( "x" << 10 ) , 5 , 1); + sorter.add( BSON( "x" << 2 ) , 3 , 1 ); + sorter.add( BSON( "x" << 5 ) , 6 , 1 ); + sorter.add( BSON( "x" << 5 ) , 7 , 1 ); + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + int num=0; + while ( i->more() ){ + pair<BSONObj,DiskLoc> p = i->next(); + if ( num == 0 ) + assert( p.first["x"].number() == 2 ); + else if ( num <= 2 ){ + assert( p.first["x"].number() == 5 ); + } + else if ( num == 3 ) + assert( p.first["x"].number() == 10 ); + else + ASSERT( 0 ); + num++; + } + + + ASSERT_EQUALS( 0 , sorter.numFiles() ); + } + }; + + class Basic2 { + public: + void run(){ + BSONObjExternalSorter sorter( BSONObj() , 10 ); + sorter.add( BSON( "x" << 10 ) , 5 , 11 ); + sorter.add( BSON( "x" << 2 ) , 3 , 1 ); + sorter.add( BSON( "x" << 5 ) , 6 , 1 ); + sorter.add( BSON( "x" << 5 ) , 7 , 1 ); + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + int num=0; + while ( i->more() ){ + pair<BSONObj,DiskLoc> p = i->next(); + if ( num == 0 ){ + assert( p.first["x"].number() == 2 ); + ASSERT_EQUALS( p.second.toString() , "3:1" ); + } + else if ( num <= 2 ) + assert( p.first["x"].number() == 5 ); + else if ( num == 3 ){ + assert( p.first["x"].number() == 10 ); + ASSERT_EQUALS( p.second.toString() , "5:b" ); + } + else + ASSERT( 0 ); + num++; + } + + } + }; + + class Basic3 { + public: + void run(){ + BSONObjExternalSorter sorter( BSONObj() , 10 ); + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + assert( ! i->more() ); + + } + }; + + + class ByDiskLock { + public: + void run(){ + BSONObjExternalSorter sorter; + sorter.add( BSON( "x" << 10 ) , 5 , 4); + sorter.add( BSON( "x" << 2 ) , 3 , 0 ); + sorter.add( BSON( "x" << 5 ) , 6 , 2 ); + sorter.add( BSON( "x" << 5 ) , 7 , 3 ); + sorter.add( BSON( "x" << 5 ) , 2 , 1 ); + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + int num=0; + while ( i->more() ){ + pair<BSONObj,DiskLoc> p = i->next(); + if ( num == 0 ) + assert( p.first["x"].number() == 2 ); + else if ( num <= 3 ){ + assert( p.first["x"].number() == 5 ); + } + else if ( num == 4 ) + assert( p.first["x"].number() == 10 ); + else + ASSERT( 0 ); + ASSERT_EQUALS( num , p.second.getOfs() ); + num++; + } + + + } + }; + + + class Big1 { + public: + void run(){ + BSONObjExternalSorter sorter( BSONObj() , 2000 ); + for ( int i=0; i<10000; i++ ){ + sorter.add( BSON( "x" << rand() % 10000 ) , 5 , i ); + } + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + int num=0; + double prev = 0; + while ( i->more() ){ + pair<BSONObj,DiskLoc> p = i->next(); + num++; + double cur = p.first["x"].number(); + assert( cur >= prev ); + prev = cur; + } + assert( num == 10000 ); + } + }; + + class Big2 { + public: + void run(){ + const int total = 100000; + BSONObjExternalSorter sorter( BSONObj() , total * 2 ); + for ( int i=0; i<total; i++ ){ + sorter.add( BSON( "a" << "b" ) , 5 , i ); + } + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + int num=0; + double prev = 0; + while ( i->more() ){ + pair<BSONObj,DiskLoc> p = i->next(); + num++; + double cur = p.first["x"].number(); + assert( cur >= prev ); + prev = cur; + } + assert( num == total ); + ASSERT( sorter.numFiles() > 2 ); + } + }; + + class D1 { + public: + void run(){ + + BSONObjBuilder b; + b.appendNull(""); + BSONObj x = b.obj(); + + BSONObjExternalSorter sorter; + sorter.add(x, DiskLoc(3,7)); + sorter.add(x, DiskLoc(4,7)); + sorter.add(x, DiskLoc(2,7)); + sorter.add(x, DiskLoc(1,7)); + sorter.add(x, DiskLoc(3,77)); + + sorter.sort(); + + auto_ptr<BSONObjExternalSorter::Iterator> i = sorter.iterator(); + while( i->more() ) { + BSONObjExternalSorter::Data d = i->next(); + cout << d.second.toString() << endl; + cout << d.first.objsize() << endl; + cout<<"SORTER next:" << d.first.toString() << endl; + } + } + }; + } + + class CompatBSON { + public: + +#define JSONBSONTEST(j,s,m) ASSERT_EQUALS( fromjson( j ).objsize() , s ); ASSERT_EQUALS( fromjson( j ).md5() , m ); +#define RAWBSONTEST(j,s,m) ASSERT_EQUALS( j.objsize() , s ); ASSERT_EQUALS( j.md5() , m ); + + void run(){ + + JSONBSONTEST( "{ 'x' : true }" , 9 , "6fe24623e4efc5cf07f027f9c66b5456" ); + JSONBSONTEST( "{ 'x' : null }" , 8 , "12d43430ff6729af501faf0638e68888" ); + JSONBSONTEST( "{ 'x' : 5.2 }" , 16 , "aaeeac4a58e9c30eec6b0b0319d0dff2" ); + JSONBSONTEST( "{ 'x' : 'eliot' }" , 18 , "331a3b8b7cbbe0706c80acdb45d4ebbe" ); + JSONBSONTEST( "{ 'x' : 5.2 , 'y' : 'truth' , 'z' : 1.1 }" , 40 , "7c77b3a6e63e2f988ede92624409da58" ); + JSONBSONTEST( "{ 'a' : { 'b' : 1.1 } }" , 24 , "31887a4b9d55cd9f17752d6a8a45d51f" ); + JSONBSONTEST( "{ 'x' : 5.2 , 'y' : { 'a' : 'eliot' , b : true } , 'z' : null }" , 44 , "b3de8a0739ab329e7aea138d87235205" ); + JSONBSONTEST( "{ 'x' : 5.2 , 'y' : [ 'a' , 'eliot' , 'b' , true ] , 'z' : null }" , 62 , "cb7bad5697714ba0cbf51d113b6a0ee8" ); + + RAWBSONTEST( BSON( "x" << 4 ) , 12 , "d1ed8dbf79b78fa215e2ded74548d89d" ); + + } + }; + + class CompareDottedFieldNamesTest { + public: + void t( FieldCompareResult res , const string& l , const string& r ){ + ASSERT_EQUALS( res , compareDottedFieldNames( l , r ) ); + ASSERT_EQUALS( -1 * res , compareDottedFieldNames( r , l ) ); + } + + void run(){ + t( SAME , "x" , "x" ); + t( SAME , "x.a" , "x.a" ); + t( LEFT_BEFORE , "a" , "b" ); + t( RIGHT_BEFORE , "b" , "a" ); + + t( LEFT_SUBFIELD , "a.x" , "a" ); + } + }; + + struct NestedDottedConversions{ + void t(const BSONObj& nest, const BSONObj& dot){ + ASSERT_EQUALS( nested2dotted(nest), dot); + ASSERT_EQUALS( nest, dotted2nested(dot)); + } + + void run(){ + t( BSON("a" << BSON("b" << 1)), BSON("a.b" << 1) ); + t( BSON("a" << BSON("b" << 1 << "c" << 1)), BSON("a.b" << 1 << "a.c" << 1) ); + t( BSON("a" << BSON("b" << 1 << "c" << 1) << "d" << 1), BSON("a.b" << 1 << "a.c" << 1 << "d" << 1) ); + t( BSON("a" << BSON("b" << 1 << "c" << 1 << "e" << BSON("f" << 1)) << "d" << 1), BSON("a.b" << 1 << "a.c" << 1 << "a.e.f" << 1 << "d" << 1) ); + } + }; + + struct BSONArrayBuilderTest{ + void run(){ + int i = 0; + BSONObjBuilder objb; + BSONArrayBuilder arrb; + + objb << objb.numStr(i++) << 100; + arrb << 100; + + objb << objb.numStr(i++) << 1.0; + arrb << 1.0; + + objb << objb.numStr(i++) << "Hello"; + arrb << "Hello"; + + objb << objb.numStr(i++) << string("World"); + arrb << string("World"); + + objb << objb.numStr(i++) << BSON( "a" << 1 << "b" << "foo" ); + arrb << BSON( "a" << 1 << "b" << "foo" ); + + objb << objb.numStr(i++) << BSON( "a" << 1)["a"]; + arrb << BSON( "a" << 1)["a"]; + + OID oid; + oid.init(); + objb << objb.numStr(i++) << oid; + arrb << oid; + + BSONObj obj = objb.obj(); + BSONArray arr = arrb.arr(); + + ASSERT_EQUALS(obj, arr); + + BSONObj o = BSON( "obj" << obj << "arr" << arr << "arr2" << BSONArray(obj) ); + ASSERT_EQUALS(o["obj"].type(), Object); + ASSERT_EQUALS(o["arr"].type(), Array); + ASSERT_EQUALS(o["arr2"].type(), Array); + } + }; + + struct ArrayMacroTest{ + void run(){ + BSONArray arr = BSON_ARRAY( "hello" << 1 << BSON( "foo" << BSON_ARRAY( "bar" << "baz" << "qux" ) ) ); + BSONObj obj = BSON( "0" << "hello" + << "1" << 1 + << "2" << BSON( "foo" << BSON_ARRAY( "bar" << "baz" << "qux" ) ) ); + + ASSERT_EQUALS(arr, obj); + ASSERT_EQUALS(arr["2"].type(), Object); + ASSERT_EQUALS(arr["2"].embeddedObject()["foo"].type(), Array); + } + }; + + class NumberParsing { + public: + void run(){ + BSONObjBuilder a; + BSONObjBuilder b; + + a.append( "a" , (int)1 ); + ASSERT( b.appendAsNumber( "a" , "1" ) ); + + a.append( "b" , 1.1 ); + ASSERT( b.appendAsNumber( "b" , "1.1" ) ); + + a.append( "c" , (int)-1 ); + ASSERT( b.appendAsNumber( "c" , "-1" ) ); + + a.append( "d" , -1.1 ); + ASSERT( b.appendAsNumber( "d" , "-1.1" ) ); + + a.append( "e" , (long long)32131231231232313LL ); + ASSERT( b.appendAsNumber( "e" , "32131231231232313" ) ); + + ASSERT( ! b.appendAsNumber( "f" , "zz" ) ); + ASSERT( ! b.appendAsNumber( "f" , "5zz" ) ); + ASSERT( ! b.appendAsNumber( "f" , "zz5" ) ); + + ASSERT_EQUALS( a.obj() , b.obj() ); + } + }; + + class bson2settest { + public: + void run(){ + BSONObj o = BSON( "z" << 1 << "a" << 2 << "m" << 3 << "c" << 4 ); + BSONObjIteratorSorted i( o ); + stringstream ss; + while ( i.more() ) + ss << i.next().fieldName(); + ASSERT_EQUALS( "acmz" , ss.str() ); + + { + Timer t; + for ( int i=0; i<10000; i++ ){ + BSONObjIteratorSorted j( o ); + int l = 0; + while ( j.more() ) + l += strlen( j.next().fieldName() ); + } + unsigned long long tm = t.micros(); + cout << "time: " << tm << endl; + } + } + + }; + + class checkForStorageTests { + public: + + void good( string s ){ + BSONObj o = fromjson( s ); + if ( o.okForStorage() ) + return; + throw UserException( 12528 , (string)"should be ok for storage:" + s ); + } + + void bad( string s ){ + BSONObj o = fromjson( s ); + if ( ! o.okForStorage() ) + return; + throw UserException( 12529 , (string)"should NOT be ok for storage:" + s ); + } + + void run(){ + good( "{x:1}" ); + bad( "{'x.y':1}" ); + + good( "{x:{a:2}}" ); + bad( "{x:{'$a':2}}" ); + } + }; + + class All : public Suite { + public: + All() : Suite( "jsobj" ){ + } + + void setupTests(){ + add< BufBuilderBasic >(); + add< BSONElementBasic >(); + add< BSONObjTests::Create >(); + add< BSONObjTests::WoCompareBasic >(); + add< BSONObjTests::NumericCompareBasic >(); + add< BSONObjTests::WoCompareEmbeddedObject >(); + add< BSONObjTests::WoCompareEmbeddedArray >(); + add< BSONObjTests::WoCompareOrdered >(); + add< BSONObjTests::WoCompareDifferentLength >(); + add< BSONObjTests::WoSortOrder >(); + add< BSONObjTests::MultiKeySortOrder > (); + add< BSONObjTests::TimestampTest >(); + add< BSONObjTests::Nan >(); + add< BSONObjTests::Validation::BadType >(); + add< BSONObjTests::Validation::EooBeforeEnd >(); + add< BSONObjTests::Validation::Undefined >(); + add< BSONObjTests::Validation::TotalSizeTooSmall >(); + add< BSONObjTests::Validation::EooMissing >(); + add< BSONObjTests::Validation::WrongStringSize >(); + add< BSONObjTests::Validation::ZeroStringSize >(); + add< BSONObjTests::Validation::NegativeStringSize >(); + add< BSONObjTests::Validation::WrongSubobjectSize >(); + add< BSONObjTests::Validation::WrongDbrefNsSize >(); + add< BSONObjTests::Validation::WrongSymbolSize >(); + add< BSONObjTests::Validation::WrongCodeSize >(); + add< BSONObjTests::Validation::NoFieldNameEnd >(); + add< BSONObjTests::Validation::BadRegex >(); + add< BSONObjTests::Validation::BadRegexOptions >(); + add< BSONObjTests::Validation::CodeWScopeSmallSize >(); + add< BSONObjTests::Validation::CodeWScopeZeroStrSize >(); + add< BSONObjTests::Validation::CodeWScopeSmallStrSize >(); + add< BSONObjTests::Validation::CodeWScopeNoSizeForObj >(); + add< BSONObjTests::Validation::CodeWScopeSmallObjSize >(); + add< BSONObjTests::Validation::CodeWScopeBadObject >(); + add< BSONObjTests::Validation::NoSize >( Symbol ); + add< BSONObjTests::Validation::NoSize >( Code ); + add< BSONObjTests::Validation::NoSize >( String ); + add< BSONObjTests::Validation::NoSize >( CodeWScope ); + add< BSONObjTests::Validation::NoSize >( DBRef ); + add< BSONObjTests::Validation::NoSize >( Object ); + add< BSONObjTests::Validation::NoSize >( Array ); + add< BSONObjTests::Validation::NoSize >( BinData ); + add< BSONObjTests::Validation::Fuzz >( .5 ); + add< BSONObjTests::Validation::Fuzz >( .1 ); + add< BSONObjTests::Validation::Fuzz >( .05 ); + add< BSONObjTests::Validation::Fuzz >( .01 ); + add< BSONObjTests::Validation::Fuzz >( .001 ); + add< OIDTests::init1 >(); + add< OIDTests::initParse1 >(); + add< OIDTests::append >(); + add< OIDTests::increasing >(); + add< ValueStreamTests::LabelBasic >(); + add< ValueStreamTests::LabelShares >(); + add< ValueStreamTests::LabelDouble >(); + add< ValueStreamTests::LabelDoubleShares >(); + add< ValueStreamTests::LabelSize >(); + add< ValueStreamTests::LabelMulti >(); + add< ValueStreamTests::Unallowed >(); + add< ValueStreamTests::ElementAppend >(); + add< SubObjectBuilder >(); + add< DateBuilder >(); + add< DateNowBuilder >(); + add< TimeTBuilder >(); + add< MinMaxElementTest >(); + add< ComparatorTest >(); + add< ExtractFieldsTest >(); + add< external_sort::Basic1 >(); + add< external_sort::Basic2 >(); + add< external_sort::Basic3 >(); + add< external_sort::ByDiskLock >(); + add< external_sort::Big1 >(); + add< external_sort::Big2 >(); + add< external_sort::D1 >(); + add< CompatBSON >(); + add< CompareDottedFieldNamesTest >(); + add< NestedDottedConversions >(); + add< BSONArrayBuilderTest >(); + add< ArrayMacroTest >(); + add< NumberParsing >(); + add< bson2settest >(); + add< checkForStorageTests >(); + + } + } myall; + +} // namespace JsobjTests + diff --git a/dbtests/jsontests.cpp b/dbtests/jsontests.cpp new file mode 100644 index 0000000..68c6b5c --- /dev/null +++ b/dbtests/jsontests.cpp @@ -0,0 +1,1117 @@ +// jsontests.cpp - Tests for json.{h,cpp} code and BSONObj::jsonString() +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/jsobj.h" +#include "../db/json.h" + +#include "dbtests.h" + +#include <limits> + +namespace JsonTests { + namespace JsonStringTests { + + class Empty { + public: + void run() { + ASSERT_EQUALS( "{}", BSONObj().jsonString( Strict ) ); + } + }; + + class SingleStringMember { + public: + void run() { + ASSERT_EQUALS( "{ \"a\" : \"b\" }", BSON( "a" << "b" ).jsonString( Strict ) ); + } + }; + + class EscapedCharacters { + public: + void run() { + BSONObjBuilder b; + b.append( "a", "\" \\ / \b \f \n \r \t" ); + ASSERT_EQUALS( "{ \"a\" : \"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\" }", b.done().jsonString( Strict ) ); + } + }; + + // per http://www.ietf.org/rfc/rfc4627.txt, control characters are + // (U+0000 through U+001F). U+007F is not mentioned as a control character. + class AdditionalControlCharacters { + public: + void run() { + BSONObjBuilder b; + b.append( "a", "\x1 \x1f" ); + ASSERT_EQUALS( "{ \"a\" : \"\\u0001 \\u001f\" }", b.done().jsonString( Strict ) ); + } + }; + + class ExtendedAscii { + public: + void run() { + BSONObjBuilder b; + b.append( "a", "\x80" ); + ASSERT_EQUALS( "{ \"a\" : \"\x80\" }", b.done().jsonString( Strict ) ); + } + }; + + class EscapeFieldName { + public: + void run() { + BSONObjBuilder b; + b.append( "\t", "b" ); + ASSERT_EQUALS( "{ \"\\t\" : \"b\" }", b.done().jsonString( Strict ) ); + } + }; + + class SingleIntMember { + public: + void run() { + BSONObjBuilder b; + b.append( "a", 1 ); + ASSERT_EQUALS( "{ \"a\" : 1 }", b.done().jsonString( Strict ) ); + } + }; + + class SingleNumberMember { + public: + void run() { + BSONObjBuilder b; + b.append( "a", 1.5 ); + ASSERT_EQUALS( "{ \"a\" : 1.5 }", b.done().jsonString( Strict ) ); + } + }; + + class InvalidNumbers { + public: + void run() { + BSONObjBuilder b; + b.append( "a", numeric_limits< double >::infinity() ); + ASSERT_EXCEPTION( b.done().jsonString( Strict ), AssertionException ); + + BSONObjBuilder c; + c.append( "a", numeric_limits< double >::quiet_NaN() ); + ASSERT_EXCEPTION( c.done().jsonString( Strict ), AssertionException ); + + BSONObjBuilder d; + d.append( "a", numeric_limits< double >::signaling_NaN() ); + ASSERT_EXCEPTION( d.done().jsonString( Strict ), AssertionException ); + } + }; + + class NumberPrecision { + public: + void run() { + BSONObjBuilder b; + b.append( "a", 123456789 ); + ASSERT_EQUALS( "{ \"a\" : 123456789 }", b.done().jsonString( Strict ) ); + } + }; + + class NegativeNumber { + public: + void run() { + BSONObjBuilder b; + b.append( "a", -1 ); + ASSERT_EQUALS( "{ \"a\" : -1 }", b.done().jsonString( Strict ) ); + } + }; + + class SingleBoolMember { + public: + void run() { + BSONObjBuilder b; + b.appendBool( "a", true ); + ASSERT_EQUALS( "{ \"a\" : true }", b.done().jsonString( Strict ) ); + + BSONObjBuilder c; + c.appendBool( "a", false ); + ASSERT_EQUALS( "{ \"a\" : false }", c.done().jsonString( Strict ) ); + } + }; + + class SingleNullMember { + public: + void run() { + BSONObjBuilder b; + b.appendNull( "a" ); + ASSERT_EQUALS( "{ \"a\" : null }", b.done().jsonString( Strict ) ); + } + }; + + class SingleObjectMember { + public: + void run() { + BSONObjBuilder b, c; + b.append( "a", c.done() ); + ASSERT_EQUALS( "{ \"a\" : {} }", b.done().jsonString( Strict ) ); + } + }; + + class TwoMembers { + public: + void run() { + BSONObjBuilder b; + b.append( "a", 1 ); + b.append( "b", 2 ); + ASSERT_EQUALS( "{ \"a\" : 1, \"b\" : 2 }", b.done().jsonString( Strict ) ); + } + }; + + class EmptyArray { + public: + void run() { + vector< int > arr; + BSONObjBuilder b; + b.append( "a", arr ); + ASSERT_EQUALS( "{ \"a\" : [] }", b.done().jsonString( Strict ) ); + } + }; + + class Array { + public: + void run() { + vector< int > arr; + arr.push_back( 1 ); + arr.push_back( 2 ); + BSONObjBuilder b; + b.append( "a", arr ); + ASSERT_EQUALS( "{ \"a\" : [ 1, 2 ] }", b.done().jsonString( Strict ) ); + } + }; + + class DBRef { + public: + void run() { + OID oid; + memset( &oid, 0xff, 12 ); + BSONObjBuilder b; + b.appendDBRef( "a", "namespace", oid ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$ref\" : \"namespace\", \"$id\" : \"ffffffffffffffffffffffff\" } }", + built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : { \"$ref\" : \"namespace\", \"$id\" : \"ffffffffffffffffffffffff\" } }", + built.jsonString( JS ) ); + ASSERT_EQUALS( "{ \"a\" : Dbref( \"namespace\", \"ffffffffffffffffffffffff\" ) }", + built.jsonString( TenGen ) ); + } + }; + + class DBRefZero { + public: + void run() { + OID oid; + memset( &oid, 0, 12 ); + BSONObjBuilder b; + b.appendDBRef( "a", "namespace", oid ); + ASSERT_EQUALS( "{ \"a\" : { \"$ref\" : \"namespace\", \"$id\" : \"000000000000000000000000\" } }", + b.done().jsonString( Strict ) ); + } + }; + + class ObjectId { + public: + void run() { + OID oid; + memset( &oid, 0xff, 12 ); + BSONObjBuilder b; + b.appendOID( "a", &oid ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$oid\" : \"ffffffffffffffffffffffff\" } }", + built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : ObjectId( \"ffffffffffffffffffffffff\" ) }", + built.jsonString( TenGen ) ); + } + }; + + class BinData { + public: + void run() { + char z[ 3 ]; + z[ 0 ] = 'a'; + z[ 1 ] = 'b'; + z[ 2 ] = 'c'; + BSONObjBuilder b; + b.appendBinData( "a", 3, ByteArray, z ); + ASSERT_EQUALS( "{ \"a\" : { \"$binary\" : \"YWJj\", \"$type\" : \"02\" } }", + b.done().jsonString( Strict ) ); + + BSONObjBuilder c; + c.appendBinData( "a", 2, ByteArray, z ); + ASSERT_EQUALS( "{ \"a\" : { \"$binary\" : \"YWI=\", \"$type\" : \"02\" } }", + c.done().jsonString( Strict ) ); + + BSONObjBuilder d; + d.appendBinData( "a", 1, ByteArray, z ); + ASSERT_EQUALS( "{ \"a\" : { \"$binary\" : \"YQ==\", \"$type\" : \"02\" } }", + d.done().jsonString( Strict ) ); + } + }; + + class Symbol { + public: + void run() { + BSONObjBuilder b; + b.appendSymbol( "a", "b" ); + ASSERT_EQUALS( "{ \"a\" : \"b\" }", b.done().jsonString( Strict ) ); + } + }; + + class Date { + public: + void run() { + BSONObjBuilder b; + b.appendDate( "a", 0 ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$date\" : 0 } }", built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : Date( 0 ) }", built.jsonString( TenGen ) ); + ASSERT_EQUALS( "{ \"a\" : Date( 0 ) }", built.jsonString( JS ) ); + } + }; + + class Regex { + public: + void run() { + BSONObjBuilder b; + b.appendRegex( "a", "abc", "i" ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$regex\" : \"abc\", \"$options\" : \"i\" } }", + built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : /abc/i }", built.jsonString( TenGen ) ); + ASSERT_EQUALS( "{ \"a\" : /abc/i }", built.jsonString( JS ) ); + } + }; + + class RegexEscape { + public: + void run() { + BSONObjBuilder b; + b.appendRegex( "a", "/\"", "i" ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$regex\" : \"\\/\\\"\", \"$options\" : \"i\" } }", + built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : /\\/\\\"/i }", built.jsonString( TenGen ) ); + ASSERT_EQUALS( "{ \"a\" : /\\/\\\"/i }", built.jsonString( JS ) ); + } + }; + + class RegexManyOptions { + public: + void run() { + BSONObjBuilder b; + b.appendRegex( "a", "z", "abcgimx" ); + BSONObj built = b.done(); + ASSERT_EQUALS( "{ \"a\" : { \"$regex\" : \"z\", \"$options\" : \"abcgimx\" } }", + built.jsonString( Strict ) ); + ASSERT_EQUALS( "{ \"a\" : /z/gim }", built.jsonString( TenGen ) ); + ASSERT_EQUALS( "{ \"a\" : /z/gim }", built.jsonString( JS ) ); + } + }; + + class CodeTests { + public: + void run(){ + BSONObjBuilder b; + b.appendCode( "x" , "function(){ return 1; }" ); + BSONObj o = b.obj(); + ASSERT_EQUALS( "{ \"x\" : function(){ return 1; } }" , o.jsonString() ); + } + }; + + class TimestampTests { + public: + void run(){ + BSONObjBuilder b; + b.appendTimestamp( "x" , 4000 , 10 ); + BSONObj o = b.obj(); + ASSERT_EQUALS( "{ \"x\" : { \"t\" : 4000 , \"i\" : 10 } }" , o.jsonString() ); + } + }; + + + } // namespace JsonStringTests + + namespace FromJsonTests { + + class Base { + public: + virtual ~Base() {} + void run() { + ASSERT( fromjson( json() ).valid() ); + assertEquals( bson(), fromjson( json() ) ); + assertEquals( bson(), fromjson( bson().jsonString( Strict ) ) ); + assertEquals( bson(), fromjson( bson().jsonString( TenGen ) ) ); + assertEquals( bson(), fromjson( bson().jsonString( JS ) ) ); + } + protected: + virtual BSONObj bson() const = 0; + virtual string json() const = 0; + private: + static void assertEquals( const BSONObj &expected, const BSONObj &actual ) { + if ( expected.woCompare( actual ) ) { + out() << "want:" << expected.jsonString() << " size: " << expected.objsize() << endl; + out() << "got :" << actual.jsonString() << " size: " << actual.objsize() << endl; + out() << expected.hexDump() << endl; + out() << actual.hexDump() << endl; + } + ASSERT( !expected.woCompare( actual ) ); + } + }; + + class Bad { + public: + virtual ~Bad() {} + void run() { + ASSERT_EXCEPTION( fromjson( json() ), MsgAssertionException ); + } + protected: + virtual string json() const = 0; + }; + + class Empty : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + return b.obj(); + } + virtual string json() const { + return "{}"; + } + }; + + class EmptyWithSpace : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + return b.obj(); + } + virtual string json() const { + return "{ }"; + } + }; + + class SingleString : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", "b" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"b\" }"; + } + }; + + class EmptyStrings : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "", "" ); + return b.obj(); + } + virtual string json() const { + return "{ \"\" : \"\" }"; + } + }; + + class ReservedFieldName : public Bad { + virtual string json() const { + return "{ \"$oid\" : \"b\" }"; + } + }; + + class OkDollarFieldName : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "$where", 1 ); + return b.obj(); + } + virtual string json() const { + return "{ \"$where\" : 1 }"; + } + }; + + class SingleNumber : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", 1 ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : 1 }"; + } + }; + + class FancyNumber { + public: + virtual ~FancyNumber() {} + void run() { + ASSERT_EQUALS( int( 1000000 * bson().firstElement().number() ), + int( 1000000 * fromjson( json() ).firstElement().number() ) ); + } + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", -4.4433e-2 ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : -4.4433e-2 }"; + } + }; + + class TwoElements : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", 1 ); + b.append( "b", "foo" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : 1, \"b\" : \"foo\" }"; + } + }; + + class Subobject : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", 1 ); + BSONObjBuilder c; + c.append( "z", b.done() ); + return c.obj(); + } + virtual string json() const { + return "{ \"z\" : { \"a\" : 1 } }"; + } + }; + + class ArrayEmpty : public Base { + virtual BSONObj bson() const { + vector< int > arr; + BSONObjBuilder b; + b.append( "a", arr ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : [] }"; + } + }; + + class Array : public Base { + virtual BSONObj bson() const { + vector< int > arr; + arr.push_back( 1 ); + arr.push_back( 2 ); + arr.push_back( 3 ); + BSONObjBuilder b; + b.append( "a", arr ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : [ 1, 2, 3 ] }"; + } + }; + + class True : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendBool( "a", true ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : true }"; + } + }; + + class False : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendBool( "a", false ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : false }"; + } + }; + + class Null : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendNull( "a" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : null }"; + } + }; + + class EscapedCharacters : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", "\" \\ / \b \f \n \r \t \v" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"\\\" \\\\ \\/ \\b \\f \\n \\r \\t \\v\" }"; + } + }; + + class NonEscapedCharacters : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", "% { a z $ # ' " ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"\\% \\{ \\a \\z \\$ \\# \\' \\ \" }"; + } + }; + + class AllowedControlCharacter : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a", "\x7f" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"\x7f\" }"; + } + }; + + class EscapeFieldName : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "\n", "b" ); + return b.obj(); + } + virtual string json() const { + return "{ \"\\n\" : \"b\" }"; + } + }; + + class EscapedUnicodeToUtf8 : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + unsigned char u[ 7 ]; + u[ 0 ] = 0xe0 | 0x0a; + u[ 1 ] = 0x80; + u[ 2 ] = 0x80; + u[ 3 ] = 0xe0 | 0x0a; + u[ 4 ] = 0x80; + u[ 5 ] = 0x80; + u[ 6 ] = 0; + b.append( "a", (char *) u ); + BSONObj built = b.obj(); + ASSERT_EQUALS( string( (char *) u ), built.firstElement().valuestr() ); + return built; + } + virtual string json() const { + return "{ \"a\" : \"\\ua000\\uA000\" }"; + } + }; + + class Utf8AllOnes : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + unsigned char u[ 8 ]; + u[ 0 ] = 0x01; + + u[ 1 ] = 0x7f; + + u[ 2 ] = 0xdf; + u[ 3 ] = 0xbf; + + u[ 4 ] = 0xef; + u[ 5 ] = 0xbf; + u[ 6 ] = 0xbf; + + u[ 7 ] = 0; + + b.append( "a", (char *) u ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"\\u0001\\u007f\\u07ff\\uffff\" }"; + } + }; + + class Utf8FirstByteOnes : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + unsigned char u[ 6 ]; + u[ 0 ] = 0xdc; + u[ 1 ] = 0x80; + + u[ 2 ] = 0xef; + u[ 3 ] = 0xbc; + u[ 4 ] = 0x80; + + u[ 5 ] = 0; + + b.append( "a", (char *) u ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : \"\\u0700\\uff00\" }"; + } + }; + + class DBRef : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + OID o; + memset( &o, 0, 12 ); + b.appendDBRef( "a", "foo", o ); + return b.obj(); + } + // NOTE Testing other formats handled by by Base class. + virtual string json() const { + return "{ \"a\" : { \"$ref\" : \"foo\", \"$id\" : \"000000000000000000000000\" } }"; + } + }; + + class NewDBRef : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + OID o; + memset( &o, 0, 12 ); + b.append( "$ref", "items" ); + b.appendOID( "$id", &o ); + BSONObjBuilder c; + c.append( "refval", b.done() ); + return c.obj(); + } + virtual string json() const { + return "{ \"refval\" : { \"$ref\" : \"items\", \"$id\" : ObjectId( \"000000000000000000000000\" ) } }"; + } + }; + + class Oid : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendOID( "_id" ); + return b.obj(); + } + virtual string json() const { + return "{ \"_id\" : { \"$oid\" : \"000000000000000000000000\" } }"; + } + }; + + class Oid2 : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + OID o; + memset( &o, 0x0f, 12 ); + b.appendOID( "_id", &o ); + return b.obj(); + } + virtual string json() const { + return "{ \"_id\" : ObjectId( \"0f0f0f0f0f0f0f0f0f0f0f0f\" ) }"; + } + }; + + class StringId : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append("_id", "000000000000000000000000"); + return b.obj(); + } + virtual string json() const { + return "{ \"_id\" : \"000000000000000000000000\" }"; + } + }; + + class BinData : public Base { + virtual BSONObj bson() const { + char z[ 3 ]; + z[ 0 ] = 'a'; + z[ 1 ] = 'b'; + z[ 2 ] = 'c'; + BSONObjBuilder b; + b.appendBinData( "a", 3, ByteArray, z ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$binary\" : \"YWJj\", \"$type\" : \"02\" } }"; + } + }; + + class BinDataPaddedSingle : public Base { + virtual BSONObj bson() const { + char z[ 2 ]; + z[ 0 ] = 'a'; + z[ 1 ] = 'b'; + BSONObjBuilder b; + b.appendBinData( "a", 2, ByteArray, z ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$binary\" : \"YWI=\", \"$type\" : \"02\" } }"; + } + }; + + class BinDataPaddedDouble : public Base { + virtual BSONObj bson() const { + char z[ 1 ]; + z[ 0 ] = 'a'; + BSONObjBuilder b; + b.appendBinData( "a", 1, ByteArray, z ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$binary\" : \"YQ==\", \"$type\" : \"02\" } }"; + } + }; + + class BinDataAllChars : public Base { + virtual BSONObj bson() const { + unsigned char z[] = { + 0x00, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8B, 0x30, + 0xD3, 0x8F, 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96, + 0x9B, 0x71, 0xD7, 0x9F, 0x82, 0x18, 0xA3, 0x92, 0x59, 0xA7, + 0xA2, 0x9A, 0xAB, 0xB2, 0xDB, 0xAF, 0xC3, 0x1C, 0xB3, 0xD3, + 0x5D, 0xB7, 0xE3, 0x9E, 0xBB, 0xF3, 0xDF, 0xBF + }; + BSONObjBuilder b; + b.appendBinData( "a", 48, ByteArray, z ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$binary\" : \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\", \"$type\" : \"02\" } }"; + } + }; + + class Date : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendDate( "a", 0 ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$date\" : 0 } }"; + } + }; + + class DateNonzero : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendDate( "a", 100 ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$date\" : 100 } }"; + } + }; + + class DateTooLong : public Bad { + virtual string json() const { + stringstream ss; + ss << "{ \"a\" : { \"$date\" : " << ~(0LL) << "0" << " } }"; + return ss.str(); + } + }; + + class Regex : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendRegex( "a", "b", "i" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$regex\" : \"b\", \"$options\" : \"i\" } }"; + } + }; + + class RegexEscape : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendRegex( "a", "\t", "i" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : { \"$regex\" : \"\\t\", \"$options\" : \"i\" } }"; + } + }; + + class RegexWithQuotes : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.appendRegex( "a", "\"", "" ); + return b.obj(); + } + virtual string json() const { + return "{ \"a\" : /\"/ }"; + } + }; + + class RegexInvalidOption : public Bad { + virtual string json() const { + return "{ \"a\" : { \"$regex\" : \"b\", \"$options\" : \"1\" } }"; + } + }; + + class RegexInvalidOption2 : public Bad { + virtual string json() const { + return "{ \"a\" : /b/c }"; + } + }; + + class Malformed : public Bad { + string json() const { + return "{"; + } + }; + + class UnquotedFieldName : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "a_b", 1 ); + return b.obj(); + } + virtual string json() const { + return "{ a_b : 1 }"; + } + }; + + class UnquotedFieldNameDollar : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "$a_b", 1 ); + return b.obj(); + } + virtual string json() const { + return "{ $a_b : 1 }"; + } + }; + + class SingleQuotes : public Base { + virtual BSONObj bson() const { + BSONObjBuilder b; + b.append( "ab'c\"", "bb\b '\"" ); + return b.obj(); + } + virtual string json() const { + return "{ 'ab\\'c\"' : 'bb\\b \\'\"' }"; + } + }; + + class ObjectId : public Base { + virtual BSONObj bson() const { + OID id; + id.init( "deadbeeff00ddeadbeeff00d" ); + BSONObjBuilder b; + b.appendOID( "_id", &id ); + return b.obj(); + } + virtual string json() const { + return "{ \"_id\": ObjectId( \"deadbeeff00ddeadbeeff00d\" ) }"; + } + }; + + class ObjectId2 : public Base { + virtual BSONObj bson() const { + OID id; + id.init( "deadbeeff00ddeadbeeff00d" ); + BSONObjBuilder b; + b.appendOID( "foo", &id ); + return b.obj(); + } + virtual string json() const { + return "{ \"foo\": ObjectId( \"deadbeeff00ddeadbeeff00d\" ) }"; + } + }; + + class NumericTypes : public Base { + public: + void run(){ + Base::run(); + + BSONObj o = fromjson(json()); + + ASSERT(o["int"].type() == NumberInt); + ASSERT(o["long"].type() == NumberLong); + ASSERT(o["double"].type() == NumberDouble); + + ASSERT(o["long"].numberLong() == 9223372036854775807ll); + } + + virtual BSONObj bson() const { + return BSON( "int" << 123 + << "long" << 9223372036854775807ll // 2**63 - 1 + << "double" << 3.14 + ); + } + virtual string json() const { + return "{ \"int\": 123, \"long\": 9223372036854775807, \"double\": 3.14 }"; + } + }; + + class NegativeNumericTypes : public Base { + public: + void run(){ + Base::run(); + + BSONObj o = fromjson(json()); + + ASSERT(o["int"].type() == NumberInt); + ASSERT(o["long"].type() == NumberLong); + ASSERT(o["double"].type() == NumberDouble); + + ASSERT(o["long"].numberLong() == -9223372036854775807ll); + } + + virtual BSONObj bson() const { + return BSON( "int" << -123 + << "long" << -9223372036854775807ll // -1 * (2**63 - 1) + << "double" << -3.14 + ); + } + virtual string json() const { + return "{ \"int\": -123, \"long\": -9223372036854775807, \"double\": -3.14 }"; + } + }; + + class EmbeddedDatesBase : public Base { + public: + + virtual void run(){ + BSONObj o = fromjson( json() ); + ASSERT_EQUALS( 3 , (o["time.valid"].type()) ); + BSONObj e = o["time.valid"].embeddedObjectUserCheck(); + ASSERT_EQUALS( 9 , e["$gt"].type() ); + ASSERT_EQUALS( 9 , e["$lt"].type() ); + Base::run(); + } + + BSONObj bson() const { + BSONObjBuilder e; + e.appendDate( "$gt" , 1257829200000LL ); + e.appendDate( "$lt" , 1257829200100LL ); + + BSONObjBuilder b; + b.append( "time.valid" , e.obj() ); + return b.obj(); + } + virtual string json() const = 0; + }; + + struct EmbeddedDatesFormat1 : EmbeddedDatesBase { + string json() const { + return "{ \"time.valid\" : { $gt : { \"$date\" : 1257829200000 } , $lt : { \"$date\" : 1257829200100 } } }"; + } + }; + struct EmbeddedDatesFormat2 : EmbeddedDatesBase { + string json() const { + return "{ \"time.valid\" : { $gt : Date(1257829200000) , $lt : Date( 1257829200100 ) } }"; + } + }; + struct EmbeddedDatesFormat3 : EmbeddedDatesBase { + string json() const { + return "{ \"time.valid\" : { $gt : new Date(1257829200000) , $lt : new Date( 1257829200100 ) } }"; + } + }; + + + } // namespace FromJsonTests + + class All : public Suite { + public: + All() : Suite( "json" ){ + } + + void setupTests(){ + add< JsonStringTests::Empty >(); + add< JsonStringTests::SingleStringMember >(); + add< JsonStringTests::EscapedCharacters >(); + add< JsonStringTests::AdditionalControlCharacters >(); + add< JsonStringTests::ExtendedAscii >(); + add< JsonStringTests::EscapeFieldName >(); + add< JsonStringTests::SingleIntMember >(); + add< JsonStringTests::SingleNumberMember >(); + add< JsonStringTests::InvalidNumbers >(); + add< JsonStringTests::NumberPrecision >(); + add< JsonStringTests::NegativeNumber >(); + add< JsonStringTests::SingleBoolMember >(); + add< JsonStringTests::SingleNullMember >(); + add< JsonStringTests::SingleObjectMember >(); + add< JsonStringTests::TwoMembers >(); + add< JsonStringTests::EmptyArray >(); + add< JsonStringTests::Array >(); + add< JsonStringTests::DBRef >(); + add< JsonStringTests::DBRefZero >(); + add< JsonStringTests::ObjectId >(); + add< JsonStringTests::BinData >(); + add< JsonStringTests::Symbol >(); + add< JsonStringTests::Date >(); + add< JsonStringTests::Regex >(); + add< JsonStringTests::RegexEscape >(); + add< JsonStringTests::RegexManyOptions >(); + add< JsonStringTests::CodeTests >(); + add< JsonStringTests::TimestampTests >(); + + add< FromJsonTests::Empty >(); + add< FromJsonTests::EmptyWithSpace >(); + add< FromJsonTests::SingleString >(); + add< FromJsonTests::EmptyStrings >(); + add< FromJsonTests::ReservedFieldName >(); + add< FromJsonTests::OkDollarFieldName >(); + add< FromJsonTests::SingleNumber >(); + add< FromJsonTests::FancyNumber >(); + add< FromJsonTests::TwoElements >(); + add< FromJsonTests::Subobject >(); + add< FromJsonTests::ArrayEmpty >(); + add< FromJsonTests::Array >(); + add< FromJsonTests::True >(); + add< FromJsonTests::False >(); + add< FromJsonTests::Null >(); + add< FromJsonTests::EscapedCharacters >(); + add< FromJsonTests::NonEscapedCharacters >(); + add< FromJsonTests::AllowedControlCharacter >(); + add< FromJsonTests::EscapeFieldName >(); + add< FromJsonTests::EscapedUnicodeToUtf8 >(); + add< FromJsonTests::Utf8AllOnes >(); + add< FromJsonTests::Utf8FirstByteOnes >(); + add< FromJsonTests::DBRef >(); + add< FromJsonTests::NewDBRef >(); + add< FromJsonTests::Oid >(); + add< FromJsonTests::Oid2 >(); + add< FromJsonTests::StringId >(); + add< FromJsonTests::BinData >(); + add< FromJsonTests::BinDataPaddedSingle >(); + add< FromJsonTests::BinDataPaddedDouble >(); + add< FromJsonTests::BinDataAllChars >(); + add< FromJsonTests::Date >(); + add< FromJsonTests::DateNonzero >(); + add< FromJsonTests::DateTooLong >(); + add< FromJsonTests::Regex >(); + add< FromJsonTests::RegexEscape >(); + add< FromJsonTests::RegexWithQuotes >(); + add< FromJsonTests::RegexInvalidOption >(); + add< FromJsonTests::RegexInvalidOption2 >(); + add< FromJsonTests::Malformed >(); + add< FromJsonTests::UnquotedFieldName >(); + add< FromJsonTests::UnquotedFieldNameDollar >(); + add< FromJsonTests::SingleQuotes >(); + add< FromJsonTests::ObjectId >(); + add< FromJsonTests::ObjectId2 >(); + add< FromJsonTests::NumericTypes >(); + add< FromJsonTests::NegativeNumericTypes >(); + add< FromJsonTests::EmbeddedDatesFormat1 >(); + add< FromJsonTests::EmbeddedDatesFormat2 >(); + add< FromJsonTests::EmbeddedDatesFormat3 >(); + } + } myall; + +} // namespace JsonTests + diff --git a/dbtests/jstests.cpp b/dbtests/jstests.cpp new file mode 100644 index 0000000..86b0218 --- /dev/null +++ b/dbtests/jstests.cpp @@ -0,0 +1,768 @@ +// javajstests.cpp +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/instance.h" + +#include "../stdafx.h" +#include "../scripting/engine.h" + +#include "dbtests.h" + +namespace mongo { + bool dbEval(const char *ns, BSONObj& cmd, BSONObjBuilder& result, string& errmsg); +} // namespace mongo + +namespace JSTests { + + class Fundamental { + public: + void run() { + // By calling JavaJSImpl() inside run(), we ensure the unit test framework's + // signal handlers are pre-installed from JNI's perspective. This allows + // JNI to catch signals generated within the JVM and forward other signals + // as appropriate. + ScriptEngine::setup(); + globalScriptEngine->runTest(); + } + }; + + class BasicScope { + public: + void run(){ + auto_ptr<Scope> s; + s.reset( globalScriptEngine->newScope() ); + + s->setNumber( "x" , 5 ); + ASSERT( 5 == s->getNumber( "x" ) ); + + s->setNumber( "x" , 1.67 ); + ASSERT( 1.67 == s->getNumber( "x" ) ); + + s->setString( "s" , "eliot was here" ); + ASSERT( "eliot was here" == s->getString( "s" ) ); + + s->setBoolean( "b" , true ); + ASSERT( s->getBoolean( "b" ) ); + + if ( 0 ){ + s->setBoolean( "b" , false ); + ASSERT( ! s->getBoolean( "b" ) ); + } + } + }; + + class ResetScope { + public: + void run(){ + // Not worrying about this for now SERVER-446. + /* + auto_ptr<Scope> s; + s.reset( globalScriptEngine->newScope() ); + + s->setBoolean( "x" , true ); + ASSERT( s->getBoolean( "x" ) ); + + s->reset(); + ASSERT( !s->getBoolean( "x" ) ); + */ + } + }; + + class FalseTests { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + ASSERT( ! s->getBoolean( "x" ) ); + + s->setString( "z" , "" ); + ASSERT( ! s->getBoolean( "z" ) ); + + + delete s ; + } + }; + + class SimpleFunctions { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + s->invoke( "x=5;" , BSONObj() ); + ASSERT( 5 == s->getNumber( "x" ) ); + + s->invoke( "return 17;" , BSONObj() ); + ASSERT( 17 == s->getNumber( "return" ) ); + + s->invoke( "function(){ return 17; }" , BSONObj() ); + ASSERT( 17 == s->getNumber( "return" ) ); + + s->setNumber( "x" , 1.76 ); + s->invoke( "return x == 1.76; " , BSONObj() ); + ASSERT( s->getBoolean( "return" ) ); + + s->setNumber( "x" , 1.76 ); + s->invoke( "return x == 1.79; " , BSONObj() ); + ASSERT( ! s->getBoolean( "return" ) ); + + s->invoke( "function( z ){ return 5 + z; }" , BSON( "" << 11 ) ); + ASSERT_EQUALS( 16 , s->getNumber( "return" ) ); + + delete s; + } + }; + + class ObjectMapping { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + BSONObj o = BSON( "x" << 17 << "y" << "eliot" << "z" << "sara" ); + s->setObject( "blah" , o ); + + s->invoke( "return blah.x;" , BSONObj() ); + ASSERT_EQUALS( 17 , s->getNumber( "return" ) ); + s->invoke( "return blah.y;" , BSONObj() ); + ASSERT_EQUALS( "eliot" , s->getString( "return" ) ); + + s->setThis( & o ); + s->invoke( "return this.z;" , BSONObj() ); + ASSERT_EQUALS( "sara" , s->getString( "return" ) ); + + s->invoke( "return this.z == 'sara';" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "return" ) ); + + s->invoke( "this.z == 'sara';" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "return" ) ); + + s->invoke( "this.z == 'asara';" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "return this.x == 17;" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "return" ) ); + + s->invoke( "return this.x == 18;" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "function(){ return this.x == 17; }" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "return" ) ); + + s->invoke( "function(){ return this.x == 18; }" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "function (){ return this.x == 17; }" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "return" ) ); + + s->invoke( "function z(){ return this.x == 18; }" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "function (){ this.x == 17; }" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "function z(){ this.x == 18; }" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "return" ) ); + + s->invoke( "x = 5; for( ; x <10; x++){ a = 1; }" , BSONObj() ); + ASSERT_EQUALS( 10 , s->getNumber( "x" ) ); + + delete s; + } + }; + + class ObjectDecoding { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + s->invoke( "z = { num : 1 };" , BSONObj() ); + BSONObj out = s->getObject( "z" ); + ASSERT_EQUALS( 1 , out["num"].number() ); + ASSERT_EQUALS( 1 , out.nFields() ); + + s->invoke( "z = { x : 'eliot' };" , BSONObj() ); + out = s->getObject( "z" ); + ASSERT_EQUALS( (string)"eliot" , out["x"].valuestr() ); + ASSERT_EQUALS( 1 , out.nFields() ); + + BSONObj o = BSON( "x" << 17 ); + s->setObject( "blah" , o ); + out = s->getObject( "blah" ); + ASSERT_EQUALS( 17 , out["x"].number() ); + + delete s; + } + }; + + class JSOIDTests { + public: + void run(){ +#ifdef MOZJS + Scope * s = globalScriptEngine->newScope(); + + s->localConnect( "blah" ); + + s->invoke( "z = { _id : new ObjectId() , a : 123 };" , BSONObj() ); + BSONObj out = s->getObject( "z" ); + ASSERT_EQUALS( 123 , out["a"].number() ); + ASSERT_EQUALS( jstOID , out["_id"].type() ); + + OID save = out["_id"].__oid(); + + s->setObject( "a" , out ); + + s->invoke( "y = { _id : a._id , a : 124 };" , BSONObj() ); + out = s->getObject( "y" ); + ASSERT_EQUALS( 124 , out["a"].number() ); + ASSERT_EQUALS( jstOID , out["_id"].type() ); + ASSERT_EQUALS( out["_id"].__oid().str() , save.str() ); + + s->invoke( "y = { _id : new ObjectId( a._id ) , a : 125 };" , BSONObj() ); + out = s->getObject( "y" ); + ASSERT_EQUALS( 125 , out["a"].number() ); + ASSERT_EQUALS( jstOID , out["_id"].type() ); + ASSERT_EQUALS( out["_id"].__oid().str() , save.str() ); + + delete s; +#endif + } + }; + + class SetImplicit { + public: + void run() { + Scope *s = globalScriptEngine->newScope(); + + BSONObj o = BSON( "foo" << "bar" ); + s->setObject( "a.b", o ); + ASSERT( s->getObject( "a" ).isEmpty() ); + + BSONObj o2 = BSONObj(); + s->setObject( "a", o2 ); + s->setObject( "a.b", o ); + ASSERT( s->getObject( "a" ).isEmpty() ); + + o2 = fromjson( "{b:{}}" ); + s->setObject( "a", o2 ); + s->setObject( "a.b", o ); + ASSERT( !s->getObject( "a" ).isEmpty() ); + } + }; + + class ObjectModReadonlyTests { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + BSONObj o = BSON( "x" << 17 << "y" << "eliot" << "z" << "sara" << "zz" << BSONObj() ); + s->setObject( "blah" , o , true ); + + s->invoke( "blah.y = 'e'", BSONObj() ); + BSONObj out = s->getObject( "blah" ); + ASSERT( strlen( out["y"].valuestr() ) > 1 ); + + s->invoke( "blah.a = 19;" , BSONObj() ); + out = s->getObject( "blah" ); + ASSERT( out["a"].eoo() ); + + s->invoke( "blah.zz.a = 19;" , BSONObj() ); + out = s->getObject( "blah" ); + ASSERT( out["zz"].embeddedObject()["a"].eoo() ); + + s->setObject( "blah.zz", BSON( "a" << 19 ) ); + out = s->getObject( "blah" ); + ASSERT( out["zz"].embeddedObject()["a"].eoo() ); + + s->invoke( "delete blah['x']" , BSONObj() ); + out = s->getObject( "blah" ); + ASSERT( !out["x"].eoo() ); + + // read-only object itself can be overwritten + s->invoke( "blah = {}", BSONObj() ); + out = s->getObject( "blah" ); + ASSERT( out.isEmpty() ); + + // test array - can't implement this in v8 +// o = fromjson( "{a:[1,2,3]}" ); +// s->setObject( "blah", o, true ); +// out = s->getObject( "blah" ); +// s->invoke( "blah.a[ 0 ] = 4;", BSONObj() ); +// s->invoke( "delete blah['a'][ 2 ];", BSONObj() ); +// out = s->getObject( "blah" ); +// ASSERT_EQUALS( 1.0, out[ "a" ].embeddedObject()[ 0 ].number() ); +// ASSERT_EQUALS( 3.0, out[ "a" ].embeddedObject()[ 2 ].number() ); + + delete s; + } + }; + + class OtherJSTypes { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + { // date + BSONObj o; + { + BSONObjBuilder b; + b.appendDate( "d" , 123456789 ); + o = b.obj(); + } + s->setObject( "x" , o ); + + s->invoke( "return x.d.getTime() != 12;" , BSONObj() ); + ASSERT_EQUALS( true, s->getBoolean( "return" ) ); + + s->invoke( "z = x.d.getTime();" , BSONObj() ); + ASSERT_EQUALS( 123456789 , s->getNumber( "z" ) ); + + s->invoke( "z = { z : x.d }" , BSONObj() ); + BSONObj out = s->getObject( "z" ); + ASSERT( out["z"].type() == Date ); + } + + { // regex + BSONObj o; + { + BSONObjBuilder b; + b.appendRegex( "r" , "^a" , "i" ); + o = b.obj(); + } + s->setObject( "x" , o ); + + s->invoke( "z = x.r.test( 'b' );" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "z" ) ); + + s->invoke( "z = x.r.test( 'a' );" , BSONObj() ); + ASSERT_EQUALS( true , s->getBoolean( "z" ) ); + + s->invoke( "z = x.r.test( 'ba' );" , BSONObj() ); + ASSERT_EQUALS( false , s->getBoolean( "z" ) ); + + s->invoke( "z = { a : x.r };" , BSONObj() ); + + BSONObj out = s->getObject("z"); + ASSERT_EQUALS( (string)"^a" , out["a"].regex() ); + ASSERT_EQUALS( (string)"i" , out["a"].regexFlags() ); + + } + + // array + { + BSONObj o = fromjson( "{r:[1,2,3]}" ); + s->setObject( "x", o, false ); + BSONObj out = s->getObject( "x" ); + ASSERT_EQUALS( Array, out.firstElement().type() ); + + s->setObject( "x", o, true ); + out = s->getObject( "x" ); + ASSERT_EQUALS( Array, out.firstElement().type() ); + } + + delete s; + } + }; + + class SpecialDBTypes { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + BSONObjBuilder b; + b.appendTimestamp( "a" , 123456789 ); + b.appendMinKey( "b" ); + b.appendMaxKey( "c" ); + b.appendTimestamp( "d" , 1234000 , 9876 ); + + + { + BSONObj t = b.done(); + ASSERT_EQUALS( 1234000U , t["d"].timestampTime() ); + ASSERT_EQUALS( 9876U , t["d"].timestampInc() ); + } + + s->setObject( "z" , b.obj() ); + + ASSERT( s->invoke( "y = { a : z.a , b : z.b , c : z.c , d: z.d }" , BSONObj() ) == 0 ); + + BSONObj out = s->getObject( "y" ); + ASSERT_EQUALS( Timestamp , out["a"].type() ); + ASSERT_EQUALS( MinKey , out["b"].type() ); + ASSERT_EQUALS( MaxKey , out["c"].type() ); + ASSERT_EQUALS( Timestamp , out["d"].type() ); + + ASSERT_EQUALS( 9876U , out["d"].timestampInc() ); + ASSERT_EQUALS( 1234000U , out["d"].timestampTime() ); + ASSERT_EQUALS( 123456789U , out["a"].date() ); + + delete s; + } + }; + + class TypeConservation { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + // -- A -- + + BSONObj o; + { + BSONObjBuilder b ; + b.append( "a" , (int)5 ); + b.append( "b" , 5.6 ); + o = b.obj(); + } + ASSERT_EQUALS( NumberInt , o["a"].type() ); + ASSERT_EQUALS( NumberDouble , o["b"].type() ); + + s->setObject( "z" , o ); + s->invoke( "return z" , BSONObj() ); + BSONObj out = s->getObject( "return" ); + ASSERT_EQUALS( 5 , out["a"].number() ); + ASSERT_EQUALS( 5.6 , out["b"].number() ); + + ASSERT_EQUALS( NumberDouble , out["b"].type() ); + ASSERT_EQUALS( NumberInt , out["a"].type() ); + + // -- B -- + + { + BSONObjBuilder b ; + b.append( "a" , (int)5 ); + b.append( "b" , 5.6 ); + o = b.obj(); + } + + s->setObject( "z" , o , false ); + s->invoke( "return z" , BSONObj() ); + out = s->getObject( "return" ); + ASSERT_EQUALS( 5 , out["a"].number() ); + ASSERT_EQUALS( 5.6 , out["b"].number() ); + + ASSERT_EQUALS( NumberDouble , out["b"].type() ); + ASSERT_EQUALS( NumberInt , out["a"].type() ); + + + // -- C -- + + { + BSONObjBuilder b ; + + { + BSONObjBuilder c; + c.append( "0" , 5.5 ); + c.append( "1" , 6 ); + b.appendArray( "a" , c.obj() ); + } + + o = b.obj(); + } + + ASSERT_EQUALS( NumberDouble , o["a"].embeddedObjectUserCheck()["0"].type() ); + ASSERT_EQUALS( NumberInt , o["a"].embeddedObjectUserCheck()["1"].type() ); + + s->setObject( "z" , o , false ); + out = s->getObject( "z" ); + + ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["0"].type() ); + ASSERT_EQUALS( NumberInt , out["a"].embeddedObjectUserCheck()["1"].type() ); + + s->invokeSafe( "z.z = 5;" , BSONObj() ); + out = s->getObject( "z" ); + ASSERT_EQUALS( 5 , out["z"].number() ); + ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["0"].type() ); + // Commenting so that v8 tests will work +// ASSERT_EQUALS( NumberDouble , out["a"].embeddedObjectUserCheck()["1"].type() ); // TODO: this is technically bad, but here to make sure that i understand the behavior + + + // Eliot says I don't have to worry about this case + +// // -- D -- +// +// o = fromjson( "{a:3.0,b:4.5}" ); +// ASSERT_EQUALS( NumberDouble , o["a"].type() ); +// ASSERT_EQUALS( NumberDouble , o["b"].type() ); +// +// s->setObject( "z" , o , false ); +// s->invoke( "return z" , BSONObj() ); +// out = s->getObject( "return" ); +// ASSERT_EQUALS( 3 , out["a"].number() ); +// ASSERT_EQUALS( 4.5 , out["b"].number() ); +// +// ASSERT_EQUALS( NumberDouble , out["b"].type() ); +// ASSERT_EQUALS( NumberDouble , out["a"].type() ); +// + + delete s; + } + + }; + + class WeirdObjects { + public: + + BSONObj build( int depth ){ + BSONObjBuilder b; + b.append( "0" , depth ); + if ( depth > 0 ) + b.appendArray( "1" , build( depth - 1 ) ); + return b.obj(); + } + + void run(){ + Scope * s = globalScriptEngine->newScope(); + + s->localConnect( "blah" ); + + for ( int i=5; i<100 ; i += 10 ){ + s->setObject( "a" , build(i) , false ); + s->invokeSafe( "tojson( a )" , BSONObj() ); + + s->setObject( "a" , build(5) , true ); + s->invokeSafe( "tojson( a )" , BSONObj() ); + } + + delete s; + } + }; + + + void dummy_function_to_force_dbeval_cpp_linking() { + BSONObj cmd; + BSONObjBuilder result; + string errmsg; + dbEval( "", cmd, result, errmsg); + } + + DBDirectClient client; + + class Utf8Check { + public: + Utf8Check() { reset(); } + ~Utf8Check() { reset(); } + void run() { + if( !globalScriptEngine->utf8Ok() ) { + log() << "warning: utf8 not supported" << endl; + return; + } + string utf8ObjSpec = "{'_id':'\\u0001\\u007f\\u07ff\\uffff'}"; + BSONObj utf8Obj = fromjson( utf8ObjSpec ); + client.insert( ns(), utf8Obj ); + client.eval( "unittest", "v = db.jstests.utf8check.findOne(); db.jstests.utf8check.remove( {} ); db.jstests.utf8check.insert( v );" ); + check( utf8Obj, client.findOne( ns(), BSONObj() ) ); + } + private: + void check( const BSONObj &one, const BSONObj &two ) { + if ( one.woCompare( two ) != 0 ) { + static string fail = string( "Assertion failure expected " ) + string( one ) + ", got " + string( two ); + FAIL( fail.c_str() ); + } + } + void reset() { + client.dropCollection( ns() ); + } + static const char *ns() { return "unittest.jstests.utf8check"; } + }; + + class LongUtf8String { + public: + LongUtf8String() { reset(); } + ~LongUtf8String() { reset(); } + void run() { + if( !globalScriptEngine->utf8Ok() ) + return; + client.eval( "unittest", "db.jstests.longutf8string.save( {_id:'\\uffff\\uffff\\uffff\\uffff'} )" ); + } + private: + void reset() { + client.dropCollection( ns() ); + } + static const char *ns() { return "unittest.jstests.longutf8string"; } + }; + + class CodeTests { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + { + BSONObjBuilder b; + b.append( "a" , 1 ); + b.appendCode( "b" , "function(){ out.b = 11; }" ); + b.appendCodeWScope( "c" , "function(){ out.c = 12; }" , BSONObj() ); + b.appendCodeWScope( "d" , "function(){ out.d = 13 + bleh; }" , BSON( "bleh" << 5 ) ); + s->setObject( "foo" , b.obj() ); + } + + s->invokeSafe( "out = {}; out.a = foo.a; foo.b(); foo.c();" , BSONObj() ); + BSONObj out = s->getObject( "out" ); + + ASSERT_EQUALS( 1 , out["a"].number() ); + ASSERT_EQUALS( 11 , out["b"].number() ); + ASSERT_EQUALS( 12 , out["c"].number() ); + + // Guess we don't care about this + //s->invokeSafe( "foo.d() " , BSONObj() ); + //out = s->getObject( "out" ); + //ASSERT_EQUALS( 18 , out["d"].number() ); + + + delete s; + } + }; + + class DBRefTest { + public: + DBRefTest(){ + _a = "unittest.dbref.a"; + _b = "unittest.dbref.b"; + reset(); + } + ~DBRefTest(){ + //reset(); + } + + void run(){ + + client.insert( _a , BSON( "a" << "17" ) ); + + { + BSONObj fromA = client.findOne( _a , BSONObj() ); + cout << "Froma : " << fromA << endl; + BSONObjBuilder b; + b.append( "b" , 18 ); + b.appendDBRef( "c" , "dbref.a" , fromA["_id"].__oid() ); + client.insert( _b , b.obj() ); + } + + ASSERT( client.eval( "unittest" , "x = db.dbref.b.findOne(); assert.eq( 17 , x.c.fetch().a , 'ref working' );" ) ); + + // BSON DBRef <=> JS DBPointer + ASSERT( client.eval( "unittest", "x = db.dbref.b.findOne(); db.dbref.b.drop(); x.c = new DBPointer( x.c.ns, x.c.id ); db.dbref.b.insert( x );" ) ); + ASSERT_EQUALS( DBRef, client.findOne( "unittest.dbref.b", "" )[ "c" ].type() ); + + // BSON Object <=> JS DBRef + ASSERT( client.eval( "unittest", "x = db.dbref.b.findOne(); db.dbref.b.drop(); x.c = new DBRef( x.c.ns, x.c.id ); db.dbref.b.insert( x );" ) ); + ASSERT_EQUALS( Object, client.findOne( "unittest.dbref.b", "" )[ "c" ].type() ); + ASSERT_EQUALS( string( "dbref.a" ), client.findOne( "unittest.dbref.b", "" )[ "c" ].embeddedObject().getStringField( "$ref" ) ); + } + + void reset(){ + client.dropCollection( _a ); + client.dropCollection( _b ); + } + + const char * _a; + const char * _b; + }; + + class BinDataType { + public: + + void pp( const char * s , BSONElement e ){ + int len; + const char * data = e.binData( len ); + cout << s << ":" << e.binDataType() << "\t" << len << endl; + cout << "\t"; + for ( int i=0; i<len; i++ ) + cout << (int)(data[i]) << " "; + cout << endl; + } + + void run(){ + Scope * s = globalScriptEngine->newScope(); + s->localConnect( "asd" ); + const char * foo = "asdas\0asdasd"; + + + BSONObj in; + { + BSONObjBuilder b; + b.append( "a" , 7 ); + b.appendBinData( "b" , 12 , ByteArray , foo ); + in = b.obj(); + s->setObject( "x" , in ); + } + + s->invokeSafe( "myb = x.b; print( myb ); printjson( myb );" , BSONObj() ); + s->invokeSafe( "y = { c : myb };" , BSONObj() ); + + BSONObj out = s->getObject( "y" ); + ASSERT_EQUALS( BinData , out["c"].type() ); + //blah( "in " , in["b"] ); + //blah( "out" , out["c"] ); + ASSERT_EQUALS( 0 , in["b"].woCompare( out["c"] , false ) ); + + // check that BinData js class is utilized + s->invokeSafe( "q = tojson( x.b );", BSONObj() ); + ASSERT_EQUALS( "BinData", s->getString( "q" ).substr( 0, 7 ) ); + + delete s; + } + }; + + class VarTests { + public: + void run(){ + Scope * s = globalScriptEngine->newScope(); + + ASSERT( s->exec( "a = 5;" , "a" , false , true , false ) ); + ASSERT_EQUALS( 5 , s->getNumber("a" ) ); + + ASSERT( s->exec( "var b = 6;" , "b" , false , true , false ) ); + ASSERT_EQUALS( 6 , s->getNumber("b" ) ); + delete s; + } + }; + + class All : public Suite { + public: + All() : Suite( "js" ) { + } + + void setupTests(){ + add< Fundamental >(); + add< BasicScope >(); + add< ResetScope >(); + add< FalseTests >(); + add< SimpleFunctions >(); + + add< ObjectMapping >(); + add< ObjectDecoding >(); + add< JSOIDTests >(); + add< SetImplicit >(); + add< ObjectModReadonlyTests >(); + add< OtherJSTypes >(); + add< SpecialDBTypes >(); + add< TypeConservation >(); + + add< WeirdObjects >(); + add< Utf8Check >(); + add< LongUtf8String >(); + add< CodeTests >(); + add< DBRefTest >(); + add< BinDataType >(); + + add< VarTests >(); + } + } myall; + +} // namespace JavaJSTests + diff --git a/dbtests/matchertests.cpp b/dbtests/matchertests.cpp new file mode 100644 index 0000000..e71988e --- /dev/null +++ b/dbtests/matchertests.cpp @@ -0,0 +1,128 @@ +// matchertests.cpp : matcher unit tests +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/matcher.h" + +#include "../db/json.h" + +#include "dbtests.h" + +namespace MatcherTests { + + class Basic { + public: + void run() { + BSONObj query = fromjson( "{\"a\":\"b\"}" ); + Matcher m( query ); + ASSERT( m.matches( fromjson( "{\"a\":\"b\"}" ) ) ); + } + }; + + class DoubleEqual { + public: + void run() { + BSONObj query = fromjson( "{\"a\":5}" ); + Matcher m( query ); + ASSERT( m.matches( fromjson( "{\"a\":5}" ) ) ); + } + }; + + class MixedNumericEqual { + public: + void run() { + BSONObjBuilder query; + query.append( "a", 5 ); + Matcher m( query.done() ); + ASSERT( m.matches( fromjson( "{\"a\":5}" ) ) ); + } + }; + + class MixedNumericGt { + public: + void run() { + BSONObj query = fromjson( "{\"a\":{\"$gt\":4}}" ); + Matcher m( query ); + BSONObjBuilder b; + b.append( "a", 5 ); + ASSERT( m.matches( b.done() ) ); + } + }; + + class MixedNumericIN { + public: + void run(){ + BSONObj query = fromjson( "{ a : { $in : [4,6] } }" ); + ASSERT_EQUALS( 4 , query["a"].embeddedObject()["$in"].embeddedObject()["0"].number() ); + ASSERT_EQUALS( NumberInt , query["a"].embeddedObject()["$in"].embeddedObject()["0"].type() ); + + Matcher m( query ); + + { + BSONObjBuilder b; + b.append( "a" , 4.0 ); + ASSERT( m.matches( b.done() ) ); + } + + { + BSONObjBuilder b; + b.append( "a" , 5 ); + ASSERT( ! m.matches( b.done() ) ); + } + + + { + BSONObjBuilder b; + b.append( "a" , 4 ); + ASSERT( m.matches( b.done() ) ); + } + + } + }; + + + class Size { + public: + void run() { + Matcher m( fromjson( "{a:{$size:4}}" ) ); + ASSERT( m.matches( fromjson( "{a:[1,2,3,4]}" ) ) ); + ASSERT( !m.matches( fromjson( "{a:[1,2,3]}" ) ) ); + ASSERT( !m.matches( fromjson( "{a:[1,2,3,'a','b']}" ) ) ); + ASSERT( !m.matches( fromjson( "{a:[[1,2,3,4]]}" ) ) ); + } + }; + + + class All : public Suite { + public: + All() : Suite( "matcher" ){ + } + + void setupTests(){ + add< Basic >(); + add< DoubleEqual >(); + add< MixedNumericEqual >(); + add< MixedNumericGt >(); + add< MixedNumericIN >(); + add< Size >(); + } + } dball; + +} // namespace MatcherTests + diff --git a/dbtests/mockdbclient.h b/dbtests/mockdbclient.h new file mode 100644 index 0000000..26f6250 --- /dev/null +++ b/dbtests/mockdbclient.h @@ -0,0 +1,91 @@ +// mockdbclient.h - mocked out client for testing. + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "../client/dbclient.h" +#include "../db/commands.h" + +class MockDBClientConnection : public DBClientConnection { +public: + MockDBClientConnection() : connect_() {} + virtual + BSONObj findOne(const string &ns, Query query, const BSONObj *fieldsToReturn = 0, int queryOptions = 0) { + return one_; + } + virtual + bool connect(const string &serverHostname, string& errmsg) { + return connect_; + } + virtual + bool isMaster(bool& isMaster, BSONObj *info=0) { + return isMaster_; + } + void one( const BSONObj &one ) { + one_ = one; + } + void connect( bool val ) { + connect_ = val; + } + void setIsMaster( bool val ) { + isMaster_ = val; + } +private: + BSONObj one_; + bool connect_; + bool isMaster_; +}; + +class DirectDBClientConnection : public DBClientConnection { +public: + struct ConnectionCallback { + virtual ~ConnectionCallback() {} + virtual void beforeCommand() {} + virtual void afterCommand() {} + }; + DirectDBClientConnection( ReplPair *rp, ConnectionCallback *cc = 0 ) : + rp_( rp ), + cc_( cc ) { + } + virtual BSONObj findOne(const string &ns, Query query, const BSONObj *fieldsToReturn = 0, int queryOptions = 0) { + if ( cc_ ) cc_->beforeCommand(); + SetGlobalReplPair s( rp_ ); + BSONObjBuilder result; + result.append( "ok", Command::runAgainstRegistered( "admin.$cmd", query.obj, result ) ? 1.0 : 0.0 ); + if ( cc_ ) cc_->afterCommand(); + return result.obj(); + } + virtual bool connect( const string &serverHostname, string& errmsg ) { + return true; + } +private: + ReplPair *rp_; + ConnectionCallback *cc_; + class SetGlobalReplPair { + public: + SetGlobalReplPair( ReplPair *rp ) { + backup_ = replPair; + replPair = rp; + } + ~SetGlobalReplPair() { + replPair = backup_; + } + private: + ReplPair *backup_; + }; +}; diff --git a/dbtests/namespacetests.cpp b/dbtests/namespacetests.cpp new file mode 100644 index 0000000..c820ca6 --- /dev/null +++ b/dbtests/namespacetests.cpp @@ -0,0 +1,798 @@ +// namespacetests.cpp : namespace.{h,cpp} unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +// Where IndexDetails defined. +#include "stdafx.h" +#include "../db/namespace.h" + +#include "../db/db.h" +#include "../db/json.h" + +#include "dbtests.h" + +namespace NamespaceTests { + namespace IndexDetailsTests { + class Base { + dblock lk; + public: + Base() { + setClient( ns() ); + } + virtual ~Base() { + if ( id_.info.isNull() ) + return; + theDataFileMgr.deleteRecord( ns(), id_.info.rec(), id_.info ); + ASSERT( theDataFileMgr.findAll( ns() )->eof() ); + } + protected: + void create() { + NamespaceDetailsTransient::get_w( ns() ).deletedIndex(); + BSONObjBuilder builder; + builder.append( "ns", ns() ); + builder.append( "name", "testIndex" ); + builder.append( "key", key() ); + BSONObj bobj = builder.done(); + id_.info = theDataFileMgr.insert( ns(), bobj.objdata(), bobj.objsize() ); + // head not needed for current tests + // idx_.head = BtreeBucket::addHead( id_ ); + } + static const char* ns() { + return "unittests.indexdetailstests"; + } + IndexDetails& id() { + return id_; + } + virtual BSONObj key() const { + BSONObjBuilder k; + k.append( "a", 1 ); + return k.obj(); + } + BSONObj aDotB() const { + BSONObjBuilder k; + k.append( "a.b", 1 ); + return k.obj(); + } + BSONObj aAndB() const { + BSONObjBuilder k; + k.append( "a", 1 ); + k.append( "b", 1 ); + return k.obj(); + } + static vector< int > shortArray() { + vector< int > a; + a.push_back( 1 ); + a.push_back( 2 ); + a.push_back( 3 ); + return a; + } + static BSONObj simpleBC( int i ) { + BSONObjBuilder b; + b.append( "b", i ); + b.append( "c", 4 ); + return b.obj(); + } + static void checkSize( int expected, const BSONObjSetDefaultOrder &objs ) { + ASSERT_EQUALS( BSONObjSetDefaultOrder::size_type( expected ), objs.size() ); + } + static void assertEquals( const BSONObj &a, const BSONObj &b ) { + if ( a.woCompare( b ) != 0 ) { + out() << "expected: " << a.toString() + << ", got: " << b.toString() << endl; + } + ASSERT( a.woCompare( b ) == 0 ); + } + BSONObj nullObj() const { + BSONObjBuilder b; + b.appendNull( "" ); + return b.obj(); + } + private: + dblock lk_; + IndexDetails id_; + }; + + class Create : public Base { + public: + void run() { + create(); + ASSERT_EQUALS( "testIndex", id().indexName() ); + ASSERT_EQUALS( ns(), id().parentNS() ); + assertEquals( key(), id().keyPattern() ); + } + }; + + class GetKeysFromObjectSimple : public Base { + public: + void run() { + create(); + BSONObjBuilder b, e; + b.append( "b", 4 ); + b.append( "a", 5 ); + e.append( "", 5 ); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 1, keys ); + assertEquals( e.obj(), *keys.begin() ); + } + }; + + class GetKeysFromObjectDotted : public Base { + public: + void run() { + create(); + BSONObjBuilder a, e, b; + b.append( "b", 4 ); + a.append( "a", b.done() ); + a.append( "c", "foo" ); + e.append( "", 4 ); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( a.done(), keys ); + checkSize( 1, keys ); + ASSERT_EQUALS( e.obj(), *keys.begin() ); + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class GetKeysFromArraySimple : public Base { + public: + void run() { + create(); + BSONObjBuilder b; + b.append( "a", shortArray()) ; + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder b; + b.append( "", j ); + assertEquals( b.obj(), *i ); + } + } + }; + + class GetKeysFromArrayFirstElement : public Base { + public: + void run() { + create(); + BSONObjBuilder b; + b.append( "a", shortArray() ); + b.append( "b", 2 ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder b; + b.append( "", j ); + b.append( "", 2 ); + assertEquals( b.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + return aAndB(); + } + }; + + class GetKeysFromArraySecondElement : public Base { + public: + void run() { + create(); + BSONObjBuilder b; + b.append( "first", 5 ); + b.append( "a", shortArray()) ; + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder b; + b.append( "", 5 ); + b.append( "", j ); + assertEquals( b.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + BSONObjBuilder k; + k.append( "first", 1 ); + k.append( "a", 1 ); + return k.obj(); + } + }; + + class GetKeysFromSecondLevelArray : public Base { + public: + void run() { + create(); + BSONObjBuilder b; + b.append( "b", shortArray() ); + BSONObjBuilder a; + a.append( "a", b.done() ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( a.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder b; + b.append( "", j ); + assertEquals( b.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class ParallelArraysBasic : public Base { + public: + void run() { + create(); + BSONObjBuilder b; + b.append( "a", shortArray() ); + b.append( "b", shortArray() ); + + BSONObjSetDefaultOrder keys; + ASSERT_EXCEPTION( id().getKeysFromObject( b.done(), keys ), + UserException ); + } + private: + virtual BSONObj key() const { + return aAndB(); + } + }; + + class ArraySubobjectBasic : public Base { + public: + void run() { + create(); + vector< BSONObj > elts; + for ( int i = 1; i < 4; ++i ) + elts.push_back( simpleBC( i ) ); + BSONObjBuilder b; + b.append( "a", elts ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder b; + b.append( "", j ); + assertEquals( b.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class ArraySubobjectMultiFieldIndex : public Base { + public: + void run() { + create(); + vector< BSONObj > elts; + for ( int i = 1; i < 4; ++i ) + elts.push_back( simpleBC( i ) ); + BSONObjBuilder b; + b.append( "a", elts ); + b.append( "d", 99 ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 3, keys ); + int j = 1; + for ( BSONObjSetDefaultOrder::iterator i = keys.begin(); i != keys.end(); ++i, ++j ) { + BSONObjBuilder c; + c.append( "", j ); + c.append( "", 99 ); + assertEquals( c.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + BSONObjBuilder k; + k.append( "a.b", 1 ); + k.append( "d", 1 ); + return k.obj(); + } + }; + + class ArraySubobjectSingleMissing : public Base { + public: + void run() { + create(); + vector< BSONObj > elts; + BSONObjBuilder s; + s.append( "foo", 41 ); + elts.push_back( s.obj() ); + for ( int i = 1; i < 4; ++i ) + elts.push_back( simpleBC( i ) ); + BSONObjBuilder b; + b.append( "a", elts ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 4, keys ); + BSONObjSetDefaultOrder::iterator i = keys.begin(); + assertEquals( nullObj(), *i++ ); + for ( int j = 1; j < 4; ++i, ++j ) { + BSONObjBuilder b; + b.append( "", j ); + assertEquals( b.obj(), *i ); + } + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class ArraySubobjectMissing : public Base { + public: + void run() { + create(); + vector< BSONObj > elts; + BSONObjBuilder s; + s.append( "foo", 41 ); + for ( int i = 1; i < 4; ++i ) + elts.push_back( s.done() ); + BSONObjBuilder b; + b.append( "a", elts ); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( b.done(), keys ); + checkSize( 1, keys ); + assertEquals( nullObj(), *keys.begin() ); + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class MissingField : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( BSON( "b" << 1 ), keys ); + checkSize( 1, keys ); + assertEquals( nullObj(), *keys.begin() ); + } + private: + virtual BSONObj key() const { + return BSON( "a" << 1 ); + } + }; + + class SubobjectMissing : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:[1,2]}" ), keys ); + checkSize( 1, keys ); + assertEquals( nullObj(), *keys.begin() ); + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class CompoundMissing : public Base { + public: + void run(){ + create(); + + { + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{x:'a',y:'b'}" ) , keys ); + checkSize( 1 , keys ); + assertEquals( BSON( "" << "a" << "" << "b" ) , *keys.begin() ); + } + + { + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{x:'a'}" ) , keys ); + checkSize( 1 , keys ); + BSONObjBuilder b; + b.append( "" , "a" ); + b.appendNull( "" ); + assertEquals( b.obj() , *keys.begin() ); + } + + } + + private: + virtual BSONObj key() const { + return BSON( "x" << 1 << "y" << 1 ); + } + + }; + + class ArraySubelementComplex : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:[{b:[2]}]}" ), keys ); + checkSize( 1, keys ); + assertEquals( BSON( "" << 2 ), *keys.begin() ); + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class ParallelArraysComplex : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + ASSERT_EXCEPTION( id().getKeysFromObject( fromjson( "{a:[{b:[1],c:[2]}]}" ), keys ), + UserException ); + } + private: + virtual BSONObj key() const { + return fromjson( "{'a.b':1,'a.c':1}" ); + } + }; + + class AlternateMissing : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:[{b:1},{c:2}]}" ), keys ); + checkSize( 2, keys ); + BSONObjSetDefaultOrder::iterator i = keys.begin(); + { + BSONObjBuilder e; + e.appendNull( "" ); + e.append( "", 2 ); + assertEquals( e.obj(), *i++ ); + } + + { + BSONObjBuilder e; + e.append( "", 1 ); + e.appendNull( "" ); + assertEquals( e.obj(), *i++ ); + } + } + private: + virtual BSONObj key() const { + return fromjson( "{'a.b':1,'a.c':1}" ); + } + }; + + class MultiComplex : public Base { + public: + void run() { + create(); + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:[{b:1},{b:[1,2,3]}]}" ), keys ); + checkSize( 3, keys ); + } + private: + virtual BSONObj key() const { + return aDotB(); + } + }; + + class EmptyArray : Base { + public: + void run(){ + create(); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:[1,2]}" ), keys ); + checkSize(2, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:[1]}" ), keys ); + checkSize(1, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:null}" ), keys ); + checkSize(1, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:[]}" ), keys ); + checkSize(1, keys ); + keys.clear(); + } + }; + + class MultiEmptyArray : Base { + public: + void run(){ + create(); + + BSONObjSetDefaultOrder keys; + id().getKeysFromObject( fromjson( "{a:1,b:[1,2]}" ), keys ); + checkSize(2, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:1,b:[1]}" ), keys ); + checkSize(1, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:1,b:null}" ), keys ); + cout << "YO : " << *(keys.begin()) << endl; + checkSize(1, keys ); + keys.clear(); + + id().getKeysFromObject( fromjson( "{a:1,b:[]}" ), keys ); + checkSize(1, keys ); + cout << "YO : " << *(keys.begin()) << endl; + ASSERT_EQUALS( NumberInt , keys.begin()->firstElement().type() ); + keys.clear(); + } + + protected: + BSONObj key() const { + return aAndB(); + } + }; + + + } // namespace IndexDetailsTests + + namespace NamespaceDetailsTests { + + class Base { + dblock lk; + public: + Base( const char *ns = "unittests.NamespaceDetailsTests" ) : ns_( ns ) {} + virtual ~Base() { + if ( !nsd() ) + return; + string s( ns() ); + string errmsg; + BSONObjBuilder result; + dropCollection( s, errmsg, result ); + } + protected: + void create() { + dblock lk; + setClient( ns() ); + string err; + ASSERT( userCreateNS( ns(), fromjson( spec() ), err, false ) ); + } + virtual string spec() const { + return "{\"capped\":true,\"size\":512}"; + } + int nRecords() const { + int count = 0; + for ( DiskLoc i = nsd()->firstExtent; !i.isNull(); i = i.ext()->xnext ) { + int fileNo = i.ext()->firstRecord.a(); + if ( fileNo == -1 ) + continue; + for ( int j = i.ext()->firstRecord.getOfs(); j != DiskLoc::NullOfs; + j = DiskLoc( fileNo, j ).rec()->nextOfs ) { + ++count; + } + } + ASSERT_EQUALS( count, nsd()->nrecords ); + return count; + } + int nExtents() const { + int count = 0; + for ( DiskLoc i = nsd()->firstExtent; !i.isNull(); i = i.ext()->xnext ) + ++count; + return count; + } + static int min( int a, int b ) { + return a < b ? a : b; + } + const char *ns() const { + return ns_; + } + NamespaceDetails *nsd() const { + return nsdetails( ns() ); + } + static BSONObj bigObj() { + string as( 187, 'a' ); + BSONObjBuilder b; + b.append( "a", as ); + return b.obj(); + } + private: + const char *ns_; + }; + + class Create : public Base { + public: + void run() { + create(); + ASSERT( nsd() ); + ASSERT_EQUALS( 0, nRecords() ); + ASSERT( nsd()->firstExtent == nsd()->capExtent ); + DiskLoc initial = DiskLoc(); + initial.setInvalid(); + ASSERT( initial == nsd()->capFirstNewRecord ); + } + }; + + class SingleAlloc : public Base { + public: + void run() { + create(); + BSONObj b = bigObj(); + ASSERT( !theDataFileMgr.insert( ns(), b.objdata(), b.objsize() ).isNull() ); + ASSERT_EQUALS( 1, nRecords() ); + } + }; + + class Realloc : public Base { + public: + void run() { + create(); + BSONObj b = bigObj(); + + DiskLoc l[ 6 ]; + for ( int i = 0; i < 6; ++i ) { + l[ i ] = theDataFileMgr.insert( ns(), b.objdata(), b.objsize() ); + ASSERT( !l[ i ].isNull() ); + ASSERT_EQUALS( 1 + i % 2, nRecords() ); + if ( i > 1 ) + ASSERT( l[ i ] == l[ i - 2 ] ); + } + } + }; + + class TwoExtent : public Base { + public: + void run() { + create(); + ASSERT_EQUALS( 2, nExtents() ); + BSONObj b = bigObj(); + + DiskLoc l[ 8 ]; + for ( int i = 0; i < 8; ++i ) { + l[ i ] = theDataFileMgr.insert( ns(), b.objdata(), b.objsize() ); + ASSERT( !l[ i ].isNull() ); + ASSERT_EQUALS( i < 2 ? i + 1 : 3 + i % 2, nRecords() ); + if ( i > 3 ) + ASSERT( l[ i ] == l[ i - 4 ] ); + } + + // Too big + BSONObjBuilder bob; + bob.append( "a", string( 787, 'a' ) ); + BSONObj bigger = bob.done(); + ASSERT( theDataFileMgr.insert( ns(), bigger.objdata(), bigger.objsize() ).isNull() ); + ASSERT_EQUALS( 0, nRecords() ); + } + private: + virtual string spec() const { + return "{\"capped\":true,\"size\":512,\"$nExtents\":2}"; + } + }; + + class Migrate : public Base { + public: + void run() { + create(); + nsd()->deletedList[ 2 ] = nsd()->deletedList[ 0 ].drec()->nextDeleted.drec()->nextDeleted; + nsd()->deletedList[ 0 ].drec()->nextDeleted.drec()->nextDeleted = DiskLoc(); + nsd()->deletedList[ 1 ].Null(); + NamespaceDetails *d = nsd(); + zero( &d->capExtent ); + zero( &d->capFirstNewRecord ); + + nsd(); + + ASSERT( nsd()->firstExtent == nsd()->capExtent ); + ASSERT( nsd()->capExtent.getOfs() != 0 ); + ASSERT( !nsd()->capFirstNewRecord.isValid() ); + int nDeleted = 0; + for ( DiskLoc i = nsd()->deletedList[ 0 ]; !i.isNull(); i = i.drec()->nextDeleted, ++nDeleted ); + ASSERT_EQUALS( 10, nDeleted ); + ASSERT( nsd()->deletedList[ 1 ].isNull() ); + } + private: + static void zero( DiskLoc *d ) { + memset( d, 0, sizeof( DiskLoc ) ); + } + virtual string spec() const { + return "{\"capped\":true,\"size\":512,\"$nExtents\":10}"; + } + }; + + // This isn't a particularly useful test, and because it doesn't clean up + // after itself, /tmp/unittest needs to be cleared after running. + // class BigCollection : public Base { + // public: + // BigCollection() : Base( "NamespaceDetailsTests_BigCollection" ) {} + // void run() { + // create(); + // ASSERT_EQUALS( 2, nExtents() ); + // } + // private: + // virtual string spec() const { + // // NOTE 256 added to size in _userCreateNS() + // long long big = MongoDataFile::maxSize() - MDFHeader::headerSize(); + // stringstream ss; + // ss << "{\"capped\":true,\"size\":" << big << "}"; + // return ss.str(); + // } + // }; + + class Size { + public: + void run() { + ASSERT_EQUALS( 496U, sizeof( NamespaceDetails ) ); + } + }; + + } // namespace NamespaceDetailsTests + + class All : public Suite { + public: + All() : Suite( "namespace" ){ + } + + void setupTests(){ + add< IndexDetailsTests::Create >(); + add< IndexDetailsTests::GetKeysFromObjectSimple >(); + add< IndexDetailsTests::GetKeysFromObjectDotted >(); + add< IndexDetailsTests::GetKeysFromArraySimple >(); + add< IndexDetailsTests::GetKeysFromArrayFirstElement >(); + add< IndexDetailsTests::GetKeysFromArraySecondElement >(); + add< IndexDetailsTests::GetKeysFromSecondLevelArray >(); + add< IndexDetailsTests::ParallelArraysBasic >(); + add< IndexDetailsTests::ArraySubobjectBasic >(); + add< IndexDetailsTests::ArraySubobjectMultiFieldIndex >(); + add< IndexDetailsTests::ArraySubobjectSingleMissing >(); + add< IndexDetailsTests::ArraySubobjectMissing >(); + add< IndexDetailsTests::ArraySubelementComplex >(); + add< IndexDetailsTests::ParallelArraysComplex >(); + add< IndexDetailsTests::AlternateMissing >(); + add< IndexDetailsTests::MultiComplex >(); + add< IndexDetailsTests::EmptyArray >(); + add< IndexDetailsTests::MultiEmptyArray >(); + add< IndexDetailsTests::MissingField >(); + add< IndexDetailsTests::SubobjectMissing >(); + add< IndexDetailsTests::CompoundMissing >(); + add< NamespaceDetailsTests::Create >(); + add< NamespaceDetailsTests::SingleAlloc >(); + add< NamespaceDetailsTests::Realloc >(); + add< NamespaceDetailsTests::TwoExtent >(); + add< NamespaceDetailsTests::Migrate >(); + // add< NamespaceDetailsTests::BigCollection >(); + add< NamespaceDetailsTests::Size >(); + } + } myall; +} // namespace NamespaceTests + diff --git a/dbtests/pairingtests.cpp b/dbtests/pairingtests.cpp new file mode 100644 index 0000000..b3e772b --- /dev/null +++ b/dbtests/pairingtests.cpp @@ -0,0 +1,344 @@ +// pairingtests.cpp : Pairing unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/replset.h" +#include "dbtests.h" +#include "mockdbclient.h" +#include "../db/cmdline.h" + +namespace mongo { + extern PairSync *pairSync; +} // namespace mongo + +namespace PairingTests { + class Base { + protected: + Base() { + backup = pairSync; + setSynced(); + } + ~Base() { + pairSync = backup; + dblock lk; + Helpers::emptyCollection( "local.pair.sync" ); + if ( pairSync->initialSyncCompleted() ) { + // save to db + pairSync->setInitialSyncCompleted(); + } + } + static void setSynced() { + init(); + pairSync = synced; + pairSync->setInitialSyncCompletedLocking(); + ASSERT( pairSync->initialSyncCompleted() ); + } + static void setNotSynced() { + init(); + pairSync = notSynced; + ASSERT( !pairSync->initialSyncCompleted() ); + } + static void flipSync() { + if ( pairSync->initialSyncCompleted() ) + setNotSynced(); + else + setSynced(); + } + private: + static void init() { + dblock lk; + Helpers::emptyCollection( "local.pair.sync" ); + if ( synced != 0 && notSynced != 0 ) + return; + notSynced = new PairSync(); + notSynced->init(); + synced = new PairSync(); + synced->init(); + synced->setInitialSyncCompleted(); + Helpers::emptyCollection( "local.pair.sync" ); + } + PairSync *backup; + static PairSync *synced; + static PairSync *notSynced; + }; + PairSync *Base::synced = 0; + PairSync *Base::notSynced = 0; + + namespace ReplPairTests { + class Create : public Base { + public: + void run() { + ReplPair rp1( "foo", "bar" ); + checkFields( rp1, "foo", "foo", CmdLine::DefaultDBPort, "bar" ); + + ReplPair rp2( "foo:1", "bar" ); + checkFields( rp2, "foo:1", "foo", 1, "bar" ); + + // FIXME Should we accept this input? + ReplPair rp3( "", "bar" ); + checkFields( rp3, "", "", CmdLine::DefaultDBPort, "bar" ); + + ASSERT_EXCEPTION( ReplPair( "foo:", "bar" ), + UserException ); + + ASSERT_EXCEPTION( ReplPair( "foo:0", "bar" ), + UserException ); + + ASSERT_EXCEPTION( ReplPair( "foo:10000000", "bar" ), + UserException ); + + ASSERT_EXCEPTION( ReplPair( "foo", "" ), + UserException ); + } + private: + void checkFields( const ReplPair &rp, + const string &remote, + const string &remoteHost, + int remotePort, + const string &arbHost ) { + ASSERT( rp.state == ReplPair::State_Negotiating ); + ASSERT_EQUALS( remote, rp.remote ); + ASSERT_EQUALS( remoteHost, rp.remoteHost ); + ASSERT_EQUALS( remotePort, rp.remotePort ); + ASSERT_EQUALS( arbHost, rp.arbHost ); + } + }; + + class Dominant : public Base { + public: + Dominant() : oldPort_( cmdLine.port ) { + cmdLine.port = 10; + } + ~Dominant() { + cmdLine.port = oldPort_; + } + void run() { + ASSERT( ReplPair( "b:9", "-" ).dominant( "b" ) ); + ASSERT( !ReplPair( "b:10", "-" ).dominant( "b" ) ); + ASSERT( ReplPair( "b", "-" ).dominant( "c" ) ); + ASSERT( !ReplPair( "b", "-" ).dominant( "a" ) ); + } + private: + int oldPort_; + }; + + class SetMaster { + public: + void run() { + ReplPair rp( "a", "b" ); + rp.setMaster( ReplPair::State_CantArb, "foo" ); + ASSERT( rp.state == ReplPair::State_CantArb ); + ASSERT_EQUALS( "foo", rp.info ); + rp.setMaster( ReplPair::State_Confused, "foo" ); + ASSERT( rp.state == ReplPair::State_Confused ); + } + }; + + class Negotiate : public Base { + public: + void run() { + ReplPair rp( "a", "b" ); + MockDBClientConnection cc; + + cc.one( res( 0, 0 ) ); + rp.negotiate( &cc, "dummy" ); + ASSERT( rp.state == ReplPair::State_Confused ); + + rp.state = ReplPair::State_Negotiating; + cc.one( res( 1, 2 ) ); + rp.negotiate( &cc, "dummy" ); + ASSERT( rp.state == ReplPair::State_Negotiating ); + + cc.one( res( 1, ReplPair::State_Slave ) ); + rp.negotiate( &cc, "dummy" ); + ASSERT( rp.state == ReplPair::State_Slave ); + + cc.one( res( 1, ReplPair::State_Master ) ); + rp.negotiate( &cc, "dummy" ); + ASSERT( rp.state == ReplPair::State_Master ); + } + private: + BSONObj res( int ok, int youAre ) { + BSONObjBuilder b; + b.append( "ok", ok ); + b.append( "you_are", youAre ); + return b.obj(); + } + }; + + class Arbitrate : public Base { + public: + void run() { + ReplPair rp1( "a", "-" ); + rp1.arbitrate(); + ASSERT( rp1.state == ReplPair::State_Master ); + + TestableReplPair rp2( false, BSONObj() ); + rp2.arbitrate(); + ASSERT( rp2.state == ReplPair::State_CantArb ); + + TestableReplPair rp3( true, fromjson( "{ok:0}" ) ); + rp3.arbitrate(); + ASSERT( rp3.state == ReplPair::State_Confused ); + + TestableReplPair rp4( true, fromjson( "{ok:1,you_are:1}" ) ); + rp4.arbitrate(); + ASSERT( rp4.state == ReplPair::State_Master ); + + TestableReplPair rp5( true, fromjson( "{ok:1,you_are:0}" ) ); + rp5.arbitrate(); + ASSERT( rp5.state == ReplPair::State_Slave ); + + TestableReplPair rp6( true, fromjson( "{ok:1,you_are:-1}" ) ); + rp6.arbitrate(); + // unchanged from initial value + ASSERT( rp6.state == ReplPair::State_Negotiating ); + } + private: + class TestableReplPair : public ReplPair { + public: + TestableReplPair( bool connect, const BSONObj &one ) : + ReplPair( "a", "z" ), + connect_( connect ), + one_( one ) { + } + virtual + DBClientConnection *newClientConnection() const { + MockDBClientConnection * c = new MockDBClientConnection(); + c->connect( connect_ ); + c->one( one_ ); + return c; + } + private: + bool connect_; + BSONObj one_; + }; + }; + } // namespace ReplPairTests + + class DirectConnectBase : public Base { + public: + virtual ~DirectConnectBase() {} + protected: + void negotiate( ReplPair &a, ReplPair &b ) { + auto_ptr< DBClientConnection > c( new DirectDBClientConnection( &b, cc() ) ); + a.negotiate( c.get(), "dummy" ); + } + virtual DirectDBClientConnection::ConnectionCallback *cc() { + return 0; + } + void checkNegotiation( const char *host1, const char *arb1, int state1, int newState1, + const char *host2, const char *arb2, int state2, int newState2 ) { + ReplPair one( host1, arb1 ); + one.state = state1; + ReplPair two( host2, arb2 ); + two.state = state2; + negotiate( one, two ); + ASSERT( one.state == newState1 ); + ASSERT( two.state == newState2 ); + } + }; + + class Negotiate : public DirectConnectBase { + public: + void run() { + checkNegotiation( "a", "-", ReplPair::State_Negotiating, ReplPair::State_Negotiating, + "b", "-", ReplPair::State_Negotiating, ReplPair::State_Negotiating ); + checkNegotiation( "b", "-", ReplPair::State_Negotiating, ReplPair::State_Slave, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Master ); + + checkNegotiation( "b", "-", ReplPair::State_Master, ReplPair::State_Master, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Slave ); + + // No change when negotiate() called on a. + checkNegotiation( "a", "-", ReplPair::State_Master, ReplPair::State_Master, + "b", "-", ReplPair::State_Master, ReplPair::State_Master ); + // Resolve Master - Master. + checkNegotiation( "b", "-", ReplPair::State_Master, ReplPair::State_Slave, + "a", "-", ReplPair::State_Master, ReplPair::State_Master ); + + // FIXME Move from negotiating to master? + checkNegotiation( "b", "-", ReplPair::State_Slave, ReplPair::State_Slave, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Master ); + } + }; + + class NegotiateWithCatchup : public DirectConnectBase { + public: + void run() { + // a caught up, b not + setNotSynced(); + checkNegotiation( "b", "-", ReplPair::State_Negotiating, ReplPair::State_Slave, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Master ); + // b caught up, a not + setSynced(); + checkNegotiation( "b", "-", ReplPair::State_Negotiating, ReplPair::State_Master, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Slave ); + + // a caught up, b not + setNotSynced(); + checkNegotiation( "b", "-", ReplPair::State_Slave, ReplPair::State_Slave, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Master ); + // b caught up, a not + setSynced(); + checkNegotiation( "b", "-", ReplPair::State_Slave, ReplPair::State_Master, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Slave ); + } + private: + class NegateCatchup : public DirectDBClientConnection::ConnectionCallback { + virtual void beforeCommand() { + Base::flipSync(); + } + virtual void afterCommand() { + Base::flipSync(); + } + }; + virtual DirectDBClientConnection::ConnectionCallback *cc() { + return &cc_; + } + NegateCatchup cc_; + }; + + class NobodyCaughtUp : public DirectConnectBase { + public: + void run() { + setNotSynced(); + checkNegotiation( "b", "-", ReplPair::State_Negotiating, ReplPair::State_Negotiating, + "a", "-", ReplPair::State_Negotiating, ReplPair::State_Slave ); + } + }; + + class All : public Suite { + public: + All() : Suite( "pairing" ){ + } + + void setupTests(){ + add< ReplPairTests::Create >(); + add< ReplPairTests::Dominant >(); + add< ReplPairTests::SetMaster >(); + add< ReplPairTests::Negotiate >(); + add< ReplPairTests::Arbitrate >(); + add< Negotiate >(); + add< NegotiateWithCatchup >(); + add< NobodyCaughtUp >(); + } + } myall; +} // namespace PairingTests + diff --git a/dbtests/pdfiletests.cpp b/dbtests/pdfiletests.cpp new file mode 100644 index 0000000..17659c0 --- /dev/null +++ b/dbtests/pdfiletests.cpp @@ -0,0 +1,328 @@ +// pdfiletests.cpp : pdfile unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/pdfile.h" + +#include "../db/db.h" +#include "../db/json.h" + +#include "dbtests.h" + +namespace PdfileTests { + + namespace ScanCapped { + + class Base { + public: + Base() { + setClient( ns() ); + } + virtual ~Base() { + if ( !nsd() ) + return; + string n( ns() ); + dropNS( n ); + } + void run() { + stringstream spec; + spec << "{\"capped\":true,\"size\":2000,\"$nExtents\":" << nExtents() << "}"; + string err; + ASSERT( userCreateNS( ns(), fromjson( spec.str() ), err, false ) ); + prepare(); + int j = 0; + for ( auto_ptr< Cursor > i = theDataFileMgr.findAll( ns() ); + i->ok(); i->advance(), ++j ) + ASSERT_EQUALS( j, i->current().firstElement().number() ); + ASSERT_EQUALS( count(), j ); + + j = count() - 1; + for ( auto_ptr< Cursor > i = + findTableScan( ns(), fromjson( "{\"$natural\":-1}" ) ); + i->ok(); i->advance(), --j ) + ASSERT_EQUALS( j, i->current().firstElement().number() ); + ASSERT_EQUALS( -1, j ); + } + protected: + virtual void prepare() = 0; + virtual int count() const = 0; + virtual int nExtents() const { + return 0; + } + // bypass standard alloc/insert routines to use the extent we want. + static DiskLoc insert( DiskLoc ext, int i ) { + BSONObjBuilder b; + b.append( "a", i ); + BSONObj o = b.done(); + int len = o.objsize(); + Extent *e = ext.ext(); + int ofs; + if ( e->lastRecord.isNull() ) + ofs = ext.getOfs() + ( e->extentData - (char *)e ); + else + ofs = e->lastRecord.getOfs() + e->lastRecord.rec()->lengthWithHeaders; + DiskLoc dl( ext.a(), ofs ); + Record *r = dl.rec(); + r->lengthWithHeaders = Record::HeaderSize + len; + r->extentOfs = e->myLoc.getOfs(); + r->nextOfs = DiskLoc::NullOfs; + r->prevOfs = e->lastRecord.isNull() ? DiskLoc::NullOfs : e->lastRecord.getOfs(); + memcpy( r->data, o.objdata(), len ); + if ( e->firstRecord.isNull() ) + e->firstRecord = dl; + else + e->lastRecord.rec()->nextOfs = ofs; + e->lastRecord = dl; + return dl; + } + static const char *ns() { + return "unittests.ScanCapped"; + } + static NamespaceDetails *nsd() { + return nsdetails( ns() ); + } + private: + dblock lk_; + }; + + class Empty : public Base { + virtual void prepare() {} + virtual int count() const { + return 0; + } + }; + + class EmptyLooped : public Base { + virtual void prepare() { + nsd()->capFirstNewRecord = DiskLoc(); + } + virtual int count() const { + return 0; + } + }; + + class EmptyMultiExtentLooped : public Base { + virtual void prepare() { + nsd()->capFirstNewRecord = DiskLoc(); + } + virtual int count() const { + return 0; + } + virtual int nExtents() const { + return 3; + } + }; + + class Single : public Base { + virtual void prepare() { + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 0 ); + } + virtual int count() const { + return 1; + } + }; + + class NewCapFirst : public Base { + virtual void prepare() { + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 0 ); + insert( nsd()->capExtent, 1 ); + } + virtual int count() const { + return 2; + } + }; + + class NewCapLast : public Base { + virtual void prepare() { + insert( nsd()->capExtent, 0 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 1 ); + } + virtual int count() const { + return 2; + } + }; + + class NewCapMiddle : public Base { + virtual void prepare() { + insert( nsd()->capExtent, 0 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 1 ); + insert( nsd()->capExtent, 2 ); + } + virtual int count() const { + return 3; + } + }; + + class FirstExtent : public Base { + virtual void prepare() { + insert( nsd()->capExtent, 0 ); + insert( nsd()->lastExtent, 1 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 2 ); + insert( nsd()->capExtent, 3 ); + } + virtual int count() const { + return 4; + } + virtual int nExtents() const { + return 2; + } + }; + + class LastExtent : public Base { + virtual void prepare() { + nsd()->capExtent = nsd()->lastExtent; + insert( nsd()->capExtent, 0 ); + insert( nsd()->firstExtent, 1 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 2 ); + insert( nsd()->capExtent, 3 ); + } + virtual int count() const { + return 4; + } + virtual int nExtents() const { + return 2; + } + }; + + class MidExtent : public Base { + virtual void prepare() { + nsd()->capExtent = nsd()->firstExtent.ext()->xnext; + insert( nsd()->capExtent, 0 ); + insert( nsd()->lastExtent, 1 ); + insert( nsd()->firstExtent, 2 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 3 ); + insert( nsd()->capExtent, 4 ); + } + virtual int count() const { + return 5; + } + virtual int nExtents() const { + return 3; + } + }; + + class AloneInExtent : public Base { + virtual void prepare() { + nsd()->capExtent = nsd()->firstExtent.ext()->xnext; + insert( nsd()->lastExtent, 0 ); + insert( nsd()->firstExtent, 1 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 2 ); + } + virtual int count() const { + return 3; + } + virtual int nExtents() const { + return 3; + } + }; + + class FirstInExtent : public Base { + virtual void prepare() { + nsd()->capExtent = nsd()->firstExtent.ext()->xnext; + insert( nsd()->lastExtent, 0 ); + insert( nsd()->firstExtent, 1 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 2 ); + insert( nsd()->capExtent, 3 ); + } + virtual int count() const { + return 4; + } + virtual int nExtents() const { + return 3; + } + }; + + class LastInExtent : public Base { + virtual void prepare() { + nsd()->capExtent = nsd()->firstExtent.ext()->xnext; + insert( nsd()->capExtent, 0 ); + insert( nsd()->lastExtent, 1 ); + insert( nsd()->firstExtent, 2 ); + nsd()->capFirstNewRecord = insert( nsd()->capExtent, 3 ); + } + virtual int count() const { + return 4; + } + virtual int nExtents() const { + return 3; + } + }; + + } // namespace ScanCapped + + namespace Insert { + class Base { + public: + Base() { + setClient( ns() ); + } + virtual ~Base() { + if ( !nsd() ) + return; + string n( ns() ); + dropNS( n ); + } + protected: + static const char *ns() { + return "unittests.pdfiletests.Insert"; + } + static NamespaceDetails *nsd() { + return nsdetails( ns() ); + } + private: + dblock lk_; + }; + + class UpdateDate : public Base { + public: + void run() { + BSONObjBuilder b; + b.appendTimestamp( "a" ); + BSONObj o = b.done(); + ASSERT( 0 == o.getField( "a" ).date() ); + theDataFileMgr.insert( ns(), o ); + ASSERT( 0 != o.getField( "a" ).date() ); + } + }; + } // namespace Insert + + class All : public Suite { + public: + All() : Suite( "pdfile" ){} + + void setupTests(){ + add< ScanCapped::Empty >(); + add< ScanCapped::EmptyLooped >(); + add< ScanCapped::EmptyMultiExtentLooped >(); + add< ScanCapped::Single >(); + add< ScanCapped::NewCapFirst >(); + add< ScanCapped::NewCapLast >(); + add< ScanCapped::NewCapMiddle >(); + add< ScanCapped::FirstExtent >(); + add< ScanCapped::LastExtent >(); + add< ScanCapped::MidExtent >(); + add< ScanCapped::AloneInExtent >(); + add< ScanCapped::FirstInExtent >(); + add< ScanCapped::LastInExtent >(); + add< Insert::UpdateDate >(); + } + } myall; + +} // namespace PdfileTests + diff --git a/dbtests/perf/perftest.cpp b/dbtests/perf/perftest.cpp new file mode 100644 index 0000000..6fe9d6a --- /dev/null +++ b/dbtests/perf/perftest.cpp @@ -0,0 +1,695 @@ +// perftest.cpp : Run db performance tests. +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#include "../../client/dbclient.h" +#include "../../db/instance.h" +#include "../../db/query.h" +#include "../../db/queryoptimizer.h" +#include "../../util/file_allocator.h" + +#include "../framework.h" +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace mongo { + extern string dbpath; +} // namespace mongo + + +using namespace mongo; +using namespace mongo::regression; + +DBClientBase *client_; + +// Each test runs with a separate db, so no test does any of the startup +// (ie allocation) work for another test. +template< class T > +string testDb( T *t = 0 ) { + string name = mongo::regression::demangleName( typeid( T ) ); + // Make filesystem safe. + for( string::iterator i = name.begin(); i != name.end(); ++i ) + if ( *i == ':' ) + *i = '_'; + return name; +} + +template< class T > +string testNs( T *t ) { + stringstream ss; + ss << testDb( t ) << ".perftest"; + return ss.str(); +} + +template <class T> +class Runner { +public: + void run() { + T test; + string name = testDb( &test ); + boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time(); + test.run(); + boost::posix_time::ptime end = boost::posix_time::microsec_clock::universal_time(); + long long micro = ( end - start ).total_microseconds(); + cout << "{'" << name << "': " + << micro / 1000000 + << "." + << setw( 6 ) << setfill( '0' ) << micro % 1000000 + << "}" << endl; + } + ~Runner() { + theFileAllocator().waitUntilFinished(); + client_->dropDatabase( testDb< T >().c_str() ); + } +}; + +class RunnerSuite : public Suite { +public: + RunnerSuite( string name ) : Suite( name ){} +protected: + template< class T > + void add() { + Suite::add< Runner< T > >(); + } +}; + +namespace Insert { + class IdIndex { + public: + void run() { + string ns = testNs( this ); + for( int i = 0; i < 100000; ++i ) { + client_->insert( ns.c_str(), BSON( "_id" << i ) ); + } + } + }; + + class TwoIndex { + public: + TwoIndex() : ns_( testNs( this ) ) { + client_->ensureIndex( ns_, BSON( "_id" << 1 ), "my_id" ); + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + string ns_; + }; + + class TenIndex { + public: + TenIndex() : ns_( testNs( this ) ) { + const char *names = "aaaaaaaaa"; + for( int i = 0; i < 9; ++i ) { + client_->resetIndexCache(); + client_->ensureIndex( ns_.c_str(), BSON( "_id" << 1 ), false, names + i ); + } + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + string ns_; + }; + + class Capped { + public: + Capped() : ns_( testNs( this ) ) { + client_->createCollection( ns_.c_str(), 100000, true ); + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + string ns_; + }; + + class OneIndexReverse { + public: + OneIndexReverse() : ns_( testNs( this ) ) { + client_->ensureIndex( ns_, BSON( "_id" << 1 ) ); + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << ( 100000 - 1 - i ) ) ); + } + string ns_; + }; + + class OneIndexHighLow { + public: + OneIndexHighLow() : ns_( testNs( this ) ) { + client_->ensureIndex( ns_, BSON( "_id" << 1 ) ); + } + void run() { + for( int i = 0; i < 100000; ++i ) { + int j = 50000 + ( ( i % 2 == 0 ) ? 1 : -1 ) * ( i / 2 + 1 ); + client_->insert( ns_.c_str(), BSON( "_id" << j ) ); + } + } + string ns_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "insert" ){} + + void setupTests(){ + add< IdIndex >(); + add< TwoIndex >(); + add< TenIndex >(); + add< Capped >(); + add< OneIndexReverse >(); + add< OneIndexHighLow >(); + } + } all; +} // namespace Insert + +namespace Update { + class Smaller { + public: + Smaller() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i << "b" << 2 ) ); + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->update( ns_.c_str(), QUERY( "_id" << i ), BSON( "_id" << i ) ); + } + string ns_; + }; + + class Bigger { + public: + Bigger() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + void run() { + for( int i = 0; i < 100000; ++i ) + client_->update( ns_.c_str(), QUERY( "_id" << i ), BSON( "_id" << i << "b" << 2 ) ); + } + string ns_; + }; + + class Inc { + public: + Inc() : ns_( testNs( this ) ) { + for( int i = 0; i < 10000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i << "i" << 0 ) ); + } + void run() { + for( int j = 0; j < 10; ++j ) + for( int i = 0; i < 10000; ++i ) + client_->update( ns_.c_str(), QUERY( "_id" << i ), BSON( "$inc" << BSON( "i" << 1 ) ) ); + } + string ns_; + }; + + class Set { + public: + Set() : ns_( testNs( this ) ) { + for( int i = 0; i < 10000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i << "i" << 0 ) ); + } + void run() { + for( int j = 1; j < 11; ++j ) + for( int i = 0; i < 10000; ++i ) + client_->update( ns_.c_str(), QUERY( "_id" << i ), BSON( "$set" << BSON( "i" << j ) ) ); + } + string ns_; + }; + + class SetGrow { + public: + SetGrow() : ns_( testNs( this ) ) { + for( int i = 0; i < 10000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i << "i" << "" ) ); + } + void run() { + for( int j = 9; j > -1; --j ) + for( int i = 0; i < 10000; ++i ) + client_->update( ns_.c_str(), QUERY( "_id" << i ), BSON( "$set" << BSON( "i" << "aaaaaaaaaa"[j] ) ) ); + } + string ns_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "update" ){} + void setupTests(){ + add< Smaller >(); + add< Bigger >(); + add< Inc >(); + add< Set >(); + add< SetGrow >(); + } + } all; +} // namespace Update + +namespace BSON { + + const char *sample = + "{\"one\":2, \"two\":5, \"three\": {}," + "\"four\": { \"five\": { \"six\" : 11 } }," + "\"seven\": [ \"a\", \"bb\", \"ccc\", 5 ]," + "\"eight\": Dbref( \"rrr\", \"01234567890123456789aaaa\" )," + "\"_id\": ObjectId( \"deadbeefdeadbeefdeadbeef\" )," + "\"nine\": { \"$binary\": \"abc=\", \"$type\": \"02\" }," + "\"ten\": Date( 44 ), \"eleven\": /foooooo/i }"; + + const char *shopwikiSample = + "{ '_id' : '289780-80f85380b5c1d4a0ad75d1217673a4a2' , 'site_id' : 289780 , 'title'" + ": 'Jubilee - Margaret Walker' , 'image_url' : 'http://www.heartlanddigsandfinds.c" + "om/store/graphics/Product_Graphics/Product_8679.jpg' , 'url' : 'http://www.heartla" + "nddigsandfinds.com/store/store_product_detail.cfm?Product_ID=8679&Category_ID=2&Su" + "b_Category_ID=910' , 'url_hash' : 3450626119933116345 , 'last_update' : null , '" + "features' : { '$imagePrefetchDate' : '2008Aug30 22:39' , '$image.color.rgb' : '5a7" + "574' , 'Price' : '$10.99' , 'Description' : 'Author--s 1st Novel. A Houghton Miffl" + "in Literary Fellowship Award novel by the esteemed poet and novelist who has demon" + "strated a lifelong commitment to the heritage of black culture. An acclaimed story" + "of Vyry, a negro slave during the 19th Century, facing the biggest challenge of h" + "er lifetime - that of gaining her freedom, fighting for all the things she had nev" + "er known before. The author, great-granddaughter of Vyry, reveals what the Civil W" + "ar in America meant to the Negroes. Slavery W' , '$priceHistory-1' : '2008Dec03 $1" + "0.99' , 'Brand' : 'Walker' , '$brands_in_title' : 'Walker' , '--path' : '//HTML[1]" + "/BODY[1]/TABLE[1]/TR[1]/TD[1]/P[1]/TABLE[1]/TR[1]/TD[1]/TABLE[1]/TR[2]/TD[2]/TABLE" + "[1]/TR[1]/TD[1]/P[1]/TABLE[1]/TR[1]' , '~location' : 'en_US' , '$crawled' : '2009J" + "an11 03:22' , '$priceHistory-2' : '2008Nov15 $10.99' , '$priceHistory-0' : '2008De" + "c24 $10.99'}}"; + + class Parse { + public: + void run() { + for( int i = 0; i < 10000; ++i ) + fromjson( sample ); + } + }; + + class ShopwikiParse { + public: + void run() { + for( int i = 0; i < 10000; ++i ) + fromjson( shopwikiSample ); + } + }; + + class Json { + public: + Json() : o_( fromjson( sample ) ) {} + void run() { + for( int i = 0; i < 10000; ++i ) + o_.jsonString(); + } + BSONObj o_; + }; + + class ShopwikiJson { + public: + ShopwikiJson() : o_( fromjson( shopwikiSample ) ) {} + void run() { + for( int i = 0; i < 10000; ++i ) + o_.jsonString(); + } + BSONObj o_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "bson" ){} + void setupTests(){ + add< Parse >(); + add< ShopwikiParse >(); + add< Json >(); + add< ShopwikiJson >(); + } + } all; + +} // namespace BSON + +namespace Index { + + class Int { + public: + Int() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "a" << i ) ); + } + void run() { + client_->ensureIndex( ns_, BSON( "a" << 1 ) ); + } + string ns_; + }; + + class ObjectId { + public: + ObjectId() : ns_( testNs( this ) ) { + OID id; + for( int i = 0; i < 100000; ++i ) { + id.init(); + client_->insert( ns_.c_str(), BSON( "a" << id ) ); + } + } + void run() { + client_->ensureIndex( ns_, BSON( "a" << 1 ) ); + } + string ns_; + }; + + class String { + public: + String() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) { + stringstream ss; + ss << i; + client_->insert( ns_.c_str(), BSON( "a" << ss.str() ) ); + } + } + void run() { + client_->ensureIndex( ns_, BSON( "a" << 1 ) ); + } + string ns_; + }; + + class Object { + public: + Object() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) { + client_->insert( ns_.c_str(), BSON( "a" << BSON( "a" << i ) ) ); + } + } + void run() { + client_->ensureIndex( ns_, BSON( "a" << 1 ) ); + } + string ns_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "index" ){} + void setupTests(){ + add< Int >(); + add< ObjectId >(); + add< String >(); + add< Object >(); + } + } all; + +} // namespace Index + +namespace QueryTests { + + class NoMatch { + public: + NoMatch() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + void run() { + client_->findOne( ns_.c_str(), QUERY( "_id" << 100000 ) ); + } + string ns_; + }; + + class NoMatchIndex { + public: + NoMatchIndex() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + void run() { + client_->findOne( ns_.c_str(), + QUERY( "a" << "b" ).hint( BSON( "_id" << 1 ) ) ); + } + string ns_; + }; + + class NoMatchLong { + public: + NoMatchLong() : ns_( testNs( this ) ) { + const char *names = "aaaaaaaaaa"; + for( int i = 0; i < 100000; ++i ) { + BSONObjBuilder b; + for( int j = 0; j < 10; ++j ) + b << ( names + j ) << i; + client_->insert( ns_.c_str(), b.obj() ); + } + } + void run() { + client_->findOne( ns_.c_str(), QUERY( "a" << 100000 ) ); + } + string ns_; + }; + + class SortOrdered { + public: + SortOrdered() : ns_( testNs( this ) ) { + for( int i = 0; i < 50000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << i ) ); + } + void run() { + auto_ptr< DBClientCursor > c = + client_->query( ns_.c_str(), Query( BSONObj() ).sort( BSON( "_id" << 1 ) ) ); + int i = 0; + for( ; c->more(); c->nextSafe(), ++i ); + ASSERT_EQUALS( 50000, i ); + } + string ns_; + }; + + class SortReverse { + public: + SortReverse() : ns_( testNs( this ) ) { + for( int i = 0; i < 50000; ++i ) + client_->insert( ns_.c_str(), BSON( "_id" << ( 50000 - 1 - i ) ) ); + } + void run() { + auto_ptr< DBClientCursor > c = + client_->query( ns_.c_str(), Query( BSONObj() ).sort( BSON( "_id" << 1 ) ) ); + int i = 0; + for( ; c->more(); c->nextSafe(), ++i ); + ASSERT_EQUALS( 50000, i ); + } + string ns_; + }; + + class GetMore { + public: + GetMore() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "a" << i ) ); + c_ = client_->query( ns_.c_str(), Query() ); + } + void run() { + int i = 0; + for( ; c_->more(); c_->nextSafe(), ++i ); + ASSERT_EQUALS( 100000, i ); + } + string ns_; + auto_ptr< DBClientCursor > c_; + }; + + class GetMoreIndex { + public: + GetMoreIndex() : ns_( testNs( this ) ) { + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_.c_str(), BSON( "a" << i ) ); + client_->ensureIndex( ns_, BSON( "a" << 1 ) ); + c_ = client_->query( ns_.c_str(), QUERY( "a" << GT << -1 ).hint( BSON( "a" << 1 ) ) ); + } + void run() { + int i = 0; + for( ; c_->more(); c_->nextSafe(), ++i ); + ASSERT_EQUALS( 100000, i ); + } + string ns_; + auto_ptr< DBClientCursor > c_; + }; + + class GetMoreKeyMatchHelps { + public: + GetMoreKeyMatchHelps() : ns_( testNs( this ) ) { + for( int i = 0; i < 1000000; ++i ) + client_->insert( ns_.c_str(), BSON( "a" << i << "b" << i % 10 << "c" << "d" ) ); + client_->ensureIndex( ns_, BSON( "a" << 1 << "b" << 1 ) ); + c_ = client_->query( ns_.c_str(), QUERY( "a" << GT << -1 << "b" << 0 ).hint( BSON( "a" << 1 << "b" << 1 ) ) ); + } + void run() { + int i = 0; + for( ; c_->more(); c_->nextSafe(), ++i ); + ASSERT_EQUALS( 100000, i ); + } + string ns_; + auto_ptr< DBClientCursor > c_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "query" ){} + void setupTests(){ + add< NoMatch >(); + add< NoMatchIndex >(); + add< NoMatchLong >(); + add< SortOrdered >(); + add< SortReverse >(); + add< GetMore >(); + add< GetMoreIndex >(); + add< GetMoreKeyMatchHelps >(); + } + } all; + +} // namespace QueryTests + +namespace Count { + + class Count { + public: + Count() : ns_( testNs( this ) ) { + BSONObj obj = BSON( "a" << 1 ); + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_, obj ); + } + void run() { + ASSERT_EQUALS( 100000U, client_->count( ns_, BSON( "a" << 1 ) ) ); + } + string ns_; + }; + + class CountIndex { + public: + CountIndex() : ns_( testNs( this ) ) { + BSONObj obj = BSON( "a" << 1 ); + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_, obj ); + client_->ensureIndex( ns_, obj ); + } + void run() { + // 'simple' match does not work for numbers + ASSERT_EQUALS( 100000U, client_->count( ns_, BSON( "a" << 1 ) ) ); + } + string ns_; + }; + + class CountSimpleIndex { + public: + CountSimpleIndex() : ns_( testNs( this ) ) { + BSONObj obj = BSON( "a" << "b" ); + for( int i = 0; i < 100000; ++i ) + client_->insert( ns_, obj ); + client_->ensureIndex( ns_, obj ); + } + void run() { + ASSERT_EQUALS( 100000U, client_->count( ns_, BSON( "a" << "b" ) ) ); + } + string ns_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite( "count" ){} + void setupTests(){ + add< Count >(); + add< CountIndex >(); + add< CountSimpleIndex >(); + } + } all; + +} // namespace Count + +namespace Plan { + + class Hint { + public: + Hint() : ns_( testNs( this ) ) { + const char *names = "aaaaaaaaa"; + for( int i = 0; i < 9; ++i ) { + client_->resetIndexCache(); + client_->ensureIndex( ns_.c_str(), BSON( ( names + i ) << 1 ), false, names + i ); + } + lk_.reset( new dblock ); + setClient( ns_.c_str() ); + hint_ = BSON( "hint" << BSON( "a" << 1 ) ); + hintElt_ = hint_.firstElement(); + } + void run() { + for( int i = 0; i < 10000; ++i ) + QueryPlanSet s( ns_.c_str(), BSONObj(), BSONObj(), &hintElt_ ); + } + string ns_; + auto_ptr< dblock > lk_; + BSONObj hint_; + BSONElement hintElt_; + }; + + class Sort { + public: + Sort() : ns_( testNs( this ) ) { + const char *names = "aaaaaaaaaa"; + for( int i = 0; i < 10; ++i ) { + client_->resetIndexCache(); + client_->ensureIndex( ns_.c_str(), BSON( ( names + i ) << 1 ), false, names + i ); + } + lk_.reset( new dblock ); + setClient( ns_.c_str() ); + } + void run() { + for( int i = 0; i < 10000; ++i ) + QueryPlanSet s( ns_.c_str(), BSONObj(), BSON( "a" << 1 ) ); + } + string ns_; + auto_ptr< dblock > lk_; + }; + + class Query { + public: + Query() : ns_( testNs( this ) ) { + const char *names = "aaaaaaaaaa"; + for( int i = 0; i < 10; ++i ) { + client_->resetIndexCache(); + client_->ensureIndex( ns_.c_str(), BSON( ( names + i ) << 1 ), false, names + i ); + } + lk_.reset( new dblock ); + setClient( ns_.c_str() ); + } + void run() { + for( int i = 0; i < 10000; ++i ) + QueryPlanSet s( ns_.c_str(), BSON( "a" << 1 ), BSONObj() ); + } + string ns_; + auto_ptr< dblock > lk_; + }; + + class All : public RunnerSuite { + public: + All() : RunnerSuite("plan" ){} + void setupTests(){ + add< Hint >(); + add< Sort >(); + add< Query >(); + } + } all; + +} // namespace Plan + +int main( int argc, char **argv ) { + logLevel = -1; + client_ = new DBDirectClient(); + + return Suite::run(argc, argv, "/data/db/perftest"); +} diff --git a/dbtests/queryoptimizertests.cpp b/dbtests/queryoptimizertests.cpp new file mode 100644 index 0000000..c9465f3 --- /dev/null +++ b/dbtests/queryoptimizertests.cpp @@ -0,0 +1,1191 @@ +// queryoptimizertests.cpp : query optimizer unit tests +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/queryoptimizer.h" + +#include "../db/db.h" +#include "../db/dbhelpers.h" +#include "../db/instance.h" +#include "../db/query.h" + +#include "dbtests.h" + +namespace mongo { + extern BSONObj id_obj; + auto_ptr< QueryResult > runQuery(Message& m, QueryMessage& q ){ + CurOp op; + return runQuery( m , q , op ); + } +} // namespace mongo + +namespace QueryOptimizerTests { + + namespace FieldRangeTests { + class Base { + public: + virtual ~Base() {} + void run() { + FieldRangeSet s( "ns", query() ); + checkElt( lower(), s.range( "a" ).min() ); + checkElt( upper(), s.range( "a" ).max() ); + ASSERT_EQUALS( lowerInclusive(), s.range( "a" ).minInclusive() ); + ASSERT_EQUALS( upperInclusive(), s.range( "a" ).maxInclusive() ); + } + protected: + virtual BSONObj query() = 0; + virtual BSONElement lower() { return minKey.firstElement(); } + virtual bool lowerInclusive() { return true; } + virtual BSONElement upper() { return maxKey.firstElement(); } + virtual bool upperInclusive() { return true; } + private: + static void checkElt( BSONElement expected, BSONElement actual ) { + if ( expected.woCompare( actual, false ) ) { + stringstream ss; + ss << "expected: " << expected << ", got: " << actual; + FAIL( ss.str() ); + } + } + }; + + + class NumericBase : public Base { + public: + NumericBase(){ + o = BSON( "min" << -numeric_limits<double>::max() << "max" << numeric_limits<double>::max() ); + } + + virtual BSONElement lower() { return o["min"]; } + virtual BSONElement upper() { return o["max"]; } + private: + BSONObj o; + }; + + class Empty : public Base { + virtual BSONObj query() { return BSONObj(); } + }; + + class Eq : public Base { + public: + Eq() : o_( BSON( "a" << 1 ) ) {} + virtual BSONObj query() { return o_; } + virtual BSONElement lower() { return o_.firstElement(); } + virtual BSONElement upper() { return o_.firstElement(); } + BSONObj o_; + }; + + class DupEq : public Eq { + public: + virtual BSONObj query() { return BSON( "a" << 1 << "b" << 2 << "a" << 1 ); } + }; + + class Lt : public NumericBase { + public: + Lt() : o_( BSON( "-" << 1 ) ) {} + virtual BSONObj query() { return BSON( "a" << LT << 1 ); } + virtual BSONElement upper() { return o_.firstElement(); } + virtual bool upperInclusive() { return false; } + BSONObj o_; + }; + + class Lte : public Lt { + virtual BSONObj query() { return BSON( "a" << LTE << 1 ); } + virtual bool upperInclusive() { return true; } + }; + + class Gt : public NumericBase { + public: + Gt() : o_( BSON( "-" << 1 ) ) {} + virtual BSONObj query() { return BSON( "a" << GT << 1 ); } + virtual BSONElement lower() { return o_.firstElement(); } + virtual bool lowerInclusive() { return false; } + BSONObj o_; + }; + + class Gte : public Gt { + virtual BSONObj query() { return BSON( "a" << GTE << 1 ); } + virtual bool lowerInclusive() { return true; } + }; + + class TwoLt : public Lt { + virtual BSONObj query() { return BSON( "a" << LT << 1 << LT << 5 ); } + }; + + class TwoGt : public Gt { + virtual BSONObj query() { return BSON( "a" << GT << 0 << GT << 1 ); } + }; + + class EqGte : public Eq { + virtual BSONObj query() { return BSON( "a" << 1 << "a" << GTE << 1 ); } + }; + + class EqGteInvalid { + public: + void run() { + FieldRangeSet fbs( "ns", BSON( "a" << 1 << "a" << GTE << 2 ) ); + ASSERT( !fbs.matchPossible() ); + } + }; + + class Regex : public Base { + public: + Regex() : o1_( BSON( "" << "abc" ) ), o2_( BSON( "" << "abd" ) ) {} + virtual BSONObj query() { + BSONObjBuilder b; + b.appendRegex( "a", "^abc" ); + return b.obj(); + } + virtual BSONElement lower() { return o1_.firstElement(); } + virtual BSONElement upper() { return o2_.firstElement(); } + virtual bool upperInclusive() { return false; } + BSONObj o1_, o2_; + }; + + class RegexObj : public Base { + public: + RegexObj() : o1_( BSON( "" << "abc" ) ), o2_( BSON( "" << "abd" ) ) {} + virtual BSONObj query() { return BSON("a" << BSON("$regex" << "^abc")); } + virtual BSONElement lower() { return o1_.firstElement(); } + virtual BSONElement upper() { return o2_.firstElement(); } + virtual bool upperInclusive() { return false; } + BSONObj o1_, o2_; + }; + + class UnhelpfulRegex : public Base { + virtual BSONObj query() { + BSONObjBuilder b; + b.appendRegex( "a", "abc" ); + return b.obj(); + } + }; + + class In : public Base { + public: + In() : o1_( BSON( "-" << -3 ) ), o2_( BSON( "-" << 44 ) ) {} + virtual BSONObj query() { + vector< int > vals; + vals.push_back( 4 ); + vals.push_back( 8 ); + vals.push_back( 44 ); + vals.push_back( -1 ); + vals.push_back( -3 ); + vals.push_back( 0 ); + BSONObjBuilder bb; + bb.append( "$in", vals ); + BSONObjBuilder b; + b.append( "a", bb.done() ); + return b.obj(); + } + virtual BSONElement lower() { return o1_.firstElement(); } + virtual BSONElement upper() { return o2_.firstElement(); } + BSONObj o1_, o2_; + }; + + class Equality { + public: + void run() { + FieldRangeSet s( "ns", BSON( "a" << 1 ) ); + ASSERT( s.range( "a" ).equality() ); + FieldRangeSet s2( "ns", BSON( "a" << GTE << 1 << LTE << 1 ) ); + ASSERT( s2.range( "a" ).equality() ); + FieldRangeSet s3( "ns", BSON( "a" << GT << 1 << LTE << 1 ) ); + ASSERT( !s3.range( "a" ).equality() ); + FieldRangeSet s4( "ns", BSON( "a" << GTE << 1 << LT << 1 ) ); + ASSERT( !s4.range( "a" ).equality() ); + FieldRangeSet s5( "ns", BSON( "a" << GTE << 1 << LTE << 1 << GT << 1 ) ); + ASSERT( !s5.range( "a" ).equality() ); + FieldRangeSet s6( "ns", BSON( "a" << GTE << 1 << LTE << 1 << LT << 1 ) ); + ASSERT( !s6.range( "a" ).equality() ); + } + }; + + class SimplifiedQuery { + public: + void run() { + FieldRangeSet fbs( "ns", BSON( "a" << GT << 1 << GT << 5 << LT << 10 << "b" << 4 << "c" << LT << 4 << LT << 6 << "d" << GTE << 0 << GT << 0 << "e" << GTE << 0 << LTE << 10 ) ); + BSONObj simple = fbs.simplifiedQuery(); + cout << "simple: " << simple << endl; + ASSERT( !simple.getObjectField( "a" ).woCompare( fromjson( "{$gt:5,$lt:10}" ) ) ); + ASSERT_EQUALS( 4, simple.getIntField( "b" ) ); + ASSERT( !simple.getObjectField( "c" ).woCompare( BSON("$gte" << -numeric_limits<double>::max() << "$lt" << 4 ) ) ); + ASSERT( !simple.getObjectField( "d" ).woCompare( BSON("$gt" << 0 << "$lte" << numeric_limits<double>::max() ) ) ); + ASSERT( !simple.getObjectField( "e" ).woCompare( fromjson( "{$gte:0,$lte:10}" ) ) ); + } + }; + + class QueryPatternTest { + public: + void run() { + ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 5 ) ) ); + ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "b" << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << LTE << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << 1 << "b" << 2 ) ) ); + ASSERT( p( BSON( "a" << 1 << "b" << 3 ) ) != p( BSON( "a" << 1 ) ) ); + ASSERT( p( BSON( "a" << LT << 1 ) ) == p( BSON( "a" << LTE << 5 ) ) ); + ASSERT( p( BSON( "a" << LT << 1 << GTE << 0 ) ) == p( BSON( "a" << LTE << 5 << GTE << 0 ) ) ); + ASSERT( p( BSON( "a" << 1 ) ) < p( BSON( "a" << 1 << "b" << 1 ) ) ); + ASSERT( !( p( BSON( "a" << 1 << "b" << 1 ) ) < p( BSON( "a" << 1 ) ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << "a" ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "c" << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << -1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 << "c" << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 ) ) ); + ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) ) ); + } + private: + static QueryPattern p( const BSONObj &query, const BSONObj &sort = BSONObj() ) { + return FieldRangeSet( "", query ).pattern( sort ); + } + }; + + class NoWhere { + public: + void run() { + ASSERT_EQUALS( 0, FieldRangeSet( "ns", BSON( "$where" << 1 ) ).nNontrivialRanges() ); + } + }; + + class Numeric { + public: + void run() { + FieldRangeSet f( "", BSON( "a" << 1 ) ); + ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 2.0 ).firstElement() ) < 0 ); + ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 0.0 ).firstElement() ) > 0 ); + } + }; + + class InLowerBound { + public: + void run() { + FieldRangeSet f( "", fromjson( "{a:{$gt:4,$in:[1,2,3,4,5,6]}}" ) ); + ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 5.0 ).firstElement(), false ) == 0 ); + ASSERT( f.range( "a" ).max().woCompare( BSON( "a" << 6.0 ).firstElement(), false ) == 0 ); + } + }; + + class InUpperBound { + public: + void run() { + FieldRangeSet f( "", fromjson( "{a:{$lt:4,$in:[1,2,3,4,5,6]}}" ) ); + ASSERT( f.range( "a" ).min().woCompare( BSON( "a" << 1.0 ).firstElement(), false ) == 0 ); + ASSERT( f.range( "a" ).max().woCompare( BSON( "a" << 3.0 ).firstElement(), false ) == 0 ); + } + }; + + class MultiBound { + public: + void run() { + FieldRangeSet frs1( "", fromjson( "{a:{$in:[1,3,5,7,9]}}" ) ); + FieldRangeSet frs2( "", fromjson( "{a:{$in:[2,3,5,8,9]}}" ) ); + FieldRange fr1 = frs1.range( "a" ); + FieldRange fr2 = frs2.range( "a" ); + fr1 &= fr2; + ASSERT( fr1.min().woCompare( BSON( "a" << 3.0 ).firstElement(), false ) == 0 ); + ASSERT( fr1.max().woCompare( BSON( "a" << 9.0 ).firstElement(), false ) == 0 ); + vector< FieldInterval > intervals = fr1.intervals(); + vector< FieldInterval >::const_iterator j = intervals.begin(); + double expected[] = { 3, 5, 9 }; + for( int i = 0; i < 3; ++i, ++j ) { + ASSERT_EQUALS( expected[ i ], j->lower_.bound_.number() ); + ASSERT( j->lower_.inclusive_ ); + ASSERT( j->lower_ == j->upper_ ); + } + ASSERT( j == intervals.end() ); + } + }; + + } // namespace FieldRangeTests + + namespace QueryPlanTests { + class Base { + public: + Base() : indexNum_( 0 ) { + setClient( ns() ); + string err; + userCreateNS( ns(), BSONObj(), err, false ); + } + ~Base() { + if ( !nsd() ) + return; + string s( ns() ); + dropNS( s ); + } + protected: + static const char *ns() { return "unittests.QueryPlanTests"; } + static NamespaceDetails *nsd() { return nsdetails( ns() ); } + IndexDetails *index( const BSONObj &key ) { + stringstream ss; + ss << indexNum_++; + string name = ss.str(); + client_.resetIndexCache(); + client_.ensureIndex( ns(), key, false, name.c_str() ); + NamespaceDetails *d = nsd(); + for( int i = 0; i < d->nIndexes; ++i ) { + if ( d->idx(i).keyPattern() == key /*indexName() == name*/ || ( d->idx(i).isIdIndex() && IndexDetails::isIdIndexPattern( key ) ) ) + return &d->idx(i); + } + assert( false ); + return 0; + } + int indexno( const BSONObj &key ) { + return nsd()->idxNo( *index(key) ); + } + BSONObj startKey( const QueryPlan &p ) const { + BoundList bl = p.indexBounds(); + return bl[ 0 ].first.getOwned(); + } + BSONObj endKey( const QueryPlan &p ) const { + BoundList bl = p.indexBounds(); + return bl[ bl.size() - 1 ].second.getOwned(); + } + private: + dblock lk_; + int indexNum_; + static DBDirectClient client_; + }; + DBDirectClient Base::client_; + + // There's a limit of 10 indexes total, make sure not to exceed this in a given test. +#define INDEXNO(x) nsd()->idxNo( *this->index( BSON(x) ) ) +#define INDEX(x) this->index( BSON(x) ) + auto_ptr< FieldRangeSet > FieldRangeSet_GLOBAL; +#define FBS(x) ( FieldRangeSet_GLOBAL.reset( new FieldRangeSet( ns(), x ) ), *FieldRangeSet_GLOBAL ) + + class NoIndex : public Base { + public: + void run() { + QueryPlan p( nsd(), -1, FBS( BSONObj() ), BSONObj() ); + ASSERT( !p.optimal() ); + ASSERT( !p.scanAndOrderRequired() ); + ASSERT( !p.exactKeyMatch() ); + } + }; + + class SimpleOrder : public Base { + public: + void run() { + BSONObjBuilder b; + b.appendMinKey( "" ); + BSONObj start = b.obj(); + BSONObjBuilder b2; + b2.appendMaxKey( "" ); + BSONObj end = b2.obj(); + + QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( !p.scanAndOrderRequired() ); + ASSERT( !startKey( p ).woCompare( start ) ); + ASSERT( !endKey( p ).woCompare( end ) ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 << "b" << 1 ) ); + ASSERT( !p2.scanAndOrderRequired() ); + QueryPlan p3( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSON( "b" << 1 ) ); + ASSERT( p3.scanAndOrderRequired() ); + ASSERT( !startKey( p3 ).woCompare( start ) ); + ASSERT( !endKey( p3 ).woCompare( end ) ); + } + }; + + class MoreIndexThanNeeded : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( !p.scanAndOrderRequired() ); + } + }; + + class IndexSigns : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << -1 ) , FBS( BSONObj() ), BSON( "a" << 1 << "b" << -1 ) ); + ASSERT( !p.scanAndOrderRequired() ); + ASSERT_EQUALS( 1, p.direction() ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 << "b" << -1 ) ); + ASSERT( p2.scanAndOrderRequired() ); + ASSERT_EQUALS( 0, p2.direction() ); + QueryPlan p3( nsd(), indexno( id_obj ), FBS( BSONObj() ), BSON( "_id" << 1 ) ); + ASSERT( !p3.scanAndOrderRequired() ); + ASSERT_EQUALS( 1, p3.direction() ); + } + }; + + class IndexReverse : public Base { + public: + void run() { + BSONObjBuilder b; + b.appendMinKey( "" ); + b.appendMaxKey( "" ); + BSONObj start = b.obj(); + BSONObjBuilder b2; + b2.appendMaxKey( "" ); + b2.appendMinKey( "" ); + BSONObj end = b2.obj(); + QueryPlan p( nsd(), INDEXNO( "a" << -1 << "b" << 1 ),FBS( BSONObj() ), BSON( "a" << 1 << "b" << -1 ) ); + ASSERT( !p.scanAndOrderRequired() ); + ASSERT_EQUALS( -1, p.direction() ); + ASSERT( !startKey( p ).woCompare( start ) ); + ASSERT( !endKey( p ).woCompare( end ) ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSON( "a" << -1 << "b" << -1 ) ); + ASSERT( !p2.scanAndOrderRequired() ); + ASSERT_EQUALS( -1, p2.direction() ); + QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << -1 ), FBS( BSONObj() ), BSON( "a" << -1 << "b" << -1 ) ); + ASSERT( p3.scanAndOrderRequired() ); + ASSERT_EQUALS( 0, p3.direction() ); + } + }; + + class NoOrder : public Base { + public: + void run() { + BSONObjBuilder b; + b.append( "", 3 ); + b.appendMinKey( "" ); + BSONObj start = b.obj(); + BSONObjBuilder b2; + b2.append( "", 3 ); + b2.appendMaxKey( "" ); + BSONObj end = b2.obj(); + QueryPlan p( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FBS( BSON( "a" << 3 ) ), BSONObj() ); + ASSERT( !p.scanAndOrderRequired() ); + ASSERT( !startKey( p ).woCompare( start ) ); + ASSERT( !endKey( p ).woCompare( end ) ); + QueryPlan p2( nsd(), INDEXNO( "a" << -1 << "b" << 1 ), FBS( BSON( "a" << 3 ) ), BSONObj() ); + ASSERT( !p2.scanAndOrderRequired() ); + ASSERT( !startKey( p ).woCompare( start ) ); + ASSERT( !endKey( p ).woCompare( end ) ); + } + }; + + class EqualWithOrder : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 4 ) ), BSON( "b" << 1 ) ); + ASSERT( !p.scanAndOrderRequired() ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "b" << 4 ) ), BSON( "a" << 1 << "c" << 1 ) ); + ASSERT( !p2.scanAndOrderRequired() ); + QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 4 ) ), BSON( "a" << 1 << "c" << 1 ) ); + ASSERT( p3.scanAndOrderRequired() ); + } + }; + + class Optimal : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( p.optimal() ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( p2.optimal() ); + QueryPlan p3( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( p3.optimal() ); + QueryPlan p4( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( !p4.optimal() ); + QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 ) ), BSON( "b" << 1 ) ); + ASSERT( p5.optimal() ); + QueryPlan p6( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSON( "b" << 1 ) ); + ASSERT( !p6.optimal() ); + QueryPlan p7( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 << "b" << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( p7.optimal() ); + QueryPlan p8( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( p8.optimal() ); + QueryPlan p9( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( p9.optimal() ); + } + }; + + class MoreOptimal : public Base { + public: + void run() { + QueryPlan p10( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 ) ), BSONObj() ); + ASSERT( p10.optimal() ); + QueryPlan p11( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << 1 << "b" << LT << 1 ) ), BSONObj() ); + ASSERT( p11.optimal() ); + QueryPlan p12( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << LT << 1 ) ), BSONObj() ); + ASSERT( p12.optimal() ); + QueryPlan p13( nsd(), INDEXNO( "a" << 1 << "b" << 1 << "c" << 1 ), FBS( BSON( "a" << LT << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( p13.optimal() ); + } + }; + + class KeyMatch : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( !p.exactKeyMatch() ); + QueryPlan p2( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( !p2.exactKeyMatch() ); + QueryPlan p3( nsd(), INDEXNO( "b" << 1 << "a" << 1 ), FBS( BSON( "b" << "z" ) ), BSON( "a" << 1 ) ); + ASSERT( !p3.exactKeyMatch() ); + QueryPlan p4( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << "y" << "b" << "z" ) ), BSON( "a" << 1 ) ); + ASSERT( !p4.exactKeyMatch() ); + QueryPlan p5( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << "y" << "b" << "z" ) ), BSONObj() ); + ASSERT( !p5.exactKeyMatch() ); + QueryPlan p6( nsd(), INDEXNO( "b" << 1 << "a" << 1 << "c" << 1 ), FBS( BSON( "c" << LT << "y" << "b" << GT << "z" ) ), BSONObj() ); + ASSERT( !p6.exactKeyMatch() ); + QueryPlan p7( nsd(), INDEXNO( "b" << 1 ), FBS( BSONObj() ), BSON( "a" << 1 ) ); + ASSERT( !p7.exactKeyMatch() ); + QueryPlan p8( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << "y" << "a" << "z" ) ), BSONObj() ); + ASSERT( p8.exactKeyMatch() ); + QueryPlan p9( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "z" ) ), BSON( "a" << 1 ) ); + ASSERT( p9.exactKeyMatch() ); + } + }; + + class MoreKeyMatch : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "r" << "b" << NE << "q" ) ), BSON( "a" << 1 ) ); + ASSERT( !p.exactKeyMatch() ); + } + }; + + class ExactKeyQueryTypes : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << "b" ) ), BSONObj() ); + ASSERT( p.exactKeyMatch() ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << 4 ) ), BSONObj() ); + ASSERT( !p2.exactKeyMatch() ); + QueryPlan p3( nsd(), INDEXNO( "a" << 1 ), FBS( BSON( "a" << BSON( "c" << "d" ) ) ), BSONObj() ); + ASSERT( !p3.exactKeyMatch() ); + BSONObjBuilder b; + b.appendRegex( "a", "^ddd" ); + QueryPlan p4( nsd(), INDEXNO( "a" << 1 ), FBS( b.obj() ), BSONObj() ); + ASSERT( !p4.exactKeyMatch() ); + QueryPlan p5( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "a" << "z" << "b" << 4 ) ), BSONObj() ); + ASSERT( !p5.exactKeyMatch() ); + } + }; + + class Unhelpful : public Base { + public: + void run() { + QueryPlan p( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 ) ), BSONObj() ); + ASSERT( !p.range( "a" ).nontrivial() ); + ASSERT( p.unhelpful() ); + QueryPlan p2( nsd(), INDEXNO( "a" << 1 << "b" << 1 ), FBS( BSON( "b" << 1 << "c" << 1 ) ), BSON( "a" << 1 ) ); + ASSERT( !p2.scanAndOrderRequired() ); + ASSERT( !p2.range( "a" ).nontrivial() ); + ASSERT( !p2.unhelpful() ); + QueryPlan p3( nsd(), INDEXNO( "b" << 1 ), FBS( BSON( "b" << 1 << "c" << 1 ) ), BSONObj() ); + ASSERT( p3.range( "b" ).nontrivial() ); + ASSERT( !p3.unhelpful() ); + QueryPlan p4( nsd(), INDEXNO( "b" << 1 << "c" << 1 ), FBS( BSON( "c" << 1 << "d" << 1 ) ), BSONObj() ); + ASSERT( !p4.range( "b" ).nontrivial() ); + ASSERT( p4.unhelpful() ); + } + }; + + } // namespace QueryPlanTests + + namespace QueryPlanSetTests { + class Base { + public: + Base() { + setClient( ns() ); + string err; + userCreateNS( ns(), BSONObj(), err, false ); + } + ~Base() { + if ( !nsd() ) + return; + NamespaceDetailsTransient::_get( ns() ).clearQueryCache(); + string s( ns() ); + dropNS( s ); + } + static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { + // see query.h for the protocol we are using here. + BufBuilder b; + int opts = queryOptions; + b.append(opts); + b.append(ns.c_str()); + b.append(nToSkip); + b.append(nToReturn); + query.appendSelfToBufBuilder(b); + if ( fieldsToReturn ) + fieldsToReturn->appendSelfToBufBuilder(b); + toSend.setData(dbQuery, b.buf(), b.len()); + } + protected: + static const char *ns() { return "unittests.QueryPlanSetTests"; } + static NamespaceDetails *nsd() { return nsdetails( ns() ); } + private: + dblock lk_; + }; + + class NoIndexes : public Base { + public: + void run() { + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class Optimal : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSONObj() ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class NoOptimal : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 3, s.nPlans() ); + } + }; + + class NoSpec : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + QueryPlanSet s( ns(), BSONObj(), BSONObj() ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class HintSpec : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + BSONObj b = BSON( "hint" << BSON( "a" << 1 ) ); + BSONElement e = b.firstElement(); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), &e ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class HintName : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + BSONObj b = BSON( "hint" << "a_1" ); + BSONElement e = b.firstElement(); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), &e ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class NaturalHint : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + BSONObj b = BSON( "hint" << BSON( "$natural" << 1 ) ); + BSONElement e = b.firstElement(); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), &e ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class NaturalSort : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); + QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "$natural" << 1 ) ); + ASSERT_EQUALS( 1, s.nPlans() ); + } + }; + + class BadHint : public Base { + public: + void run() { + BSONObj b = BSON( "hint" << "a_1" ); + BSONElement e = b.firstElement(); + ASSERT_EXCEPTION( QueryPlanSet s( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), &e ), + AssertionException ); + } + }; + + class Count : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + string err; + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) ); + BSONObj one = BSON( "a" << 1 ); + BSONObj fourA = BSON( "a" << 4 ); + BSONObj fourB = BSON( "a" << 4 ); + theDataFileMgr.insert( ns(), one ); + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) ); + theDataFileMgr.insert( ns(), fourA ); + ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) ); + theDataFileMgr.insert( ns(), fourB ); + ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err ) ); + ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err ) ); + ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err ) ); + // missing ns + ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err ) ); + // impossible match + ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err ) ); + } + }; + + class QueryMissingNs : public Base { + public: + void run() { + Message m; + assembleRequest( "unittests.missingNS", BSONObj(), 0, 0, 0, 0, m ); + stringstream ss; + + DbMessage d(m); + QueryMessage q(d); + ASSERT_EQUALS( 0, runQuery( m, q)->nReturned ); + } + }; + + class UnhelpfulIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + QueryPlanSet s( ns(), BSON( "a" << 1 << "c" << 2 ), BSONObj() ); + ASSERT_EQUALS( 2, s.nPlans() ); + } + }; + + class SingleException : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 3, s.nPlans() ); + bool threw = false; + auto_ptr< TestOp > t( new TestOp( true, threw ) ); + boost::shared_ptr< TestOp > done = s.runOp( *t ); + ASSERT( threw ); + ASSERT( done->complete() ); + ASSERT( done->exceptionMessage().empty() ); + ASSERT( !done->error() ); + } + private: + class TestOp : public QueryOp { + public: + TestOp( bool iThrow, bool &threw ) : iThrow_( iThrow ), threw_( threw ), i_(), youThrow_( false ) {} + virtual void init() {} + virtual void next() { + if ( iThrow_ ) + threw_ = true; + massert( 10408 , "throw", !iThrow_ ); + if ( ++i_ > 10 ) + setComplete(); + } + virtual QueryOp *clone() const { + QueryOp *op = new TestOp( youThrow_, threw_ ); + youThrow_ = !youThrow_; + return op; + } + virtual bool mayRecordPlan() const { return true; } + private: + bool iThrow_; + bool &threw_; + int i_; + mutable bool youThrow_; + }; + }; + + class AllException : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( 3, s.nPlans() ); + auto_ptr< TestOp > t( new TestOp() ); + boost::shared_ptr< TestOp > done = s.runOp( *t ); + ASSERT( !done->complete() ); + ASSERT_EQUALS( "throw", done->exceptionMessage() ); + ASSERT( done->error() ); + } + private: + class TestOp : public QueryOp { + public: + virtual void init() {} + virtual void next() { + massert( 10409 , "throw", false ); + } + virtual QueryOp *clone() const { + return new TestOp(); + } + virtual bool mayRecordPlan() const { return true; } + }; + }; + + class SaveGoodIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); + nPlans( 3 ); + runQuery(); + nPlans( 1 ); + nPlans( 1 ); + Helpers::ensureIndex( ns(), BSON( "c" << 1 ), false, "c_1" ); + nPlans( 3 ); + runQuery(); + nPlans( 1 ); + + { + DBDirectClient client; + for( int i = 0; i < 34; ++i ) { + client.insert( ns(), BSON( "i" << i ) ); + client.update( ns(), QUERY( "i" << i ), BSON( "i" << i + 1 ) ); + client.remove( ns(), BSON( "i" << i + 1 ) ); + } + } + nPlans( 3 ); + + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + NoRecordTestOp original; + s.runOp( original ); + nPlans( 3 ); + + BSONObj hint = fromjson( "{hint:{$natural:1}}" ); + BSONElement hintElt = hint.firstElement(); + QueryPlanSet s2( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ), &hintElt ); + TestOp newOriginal; + s2.runOp( newOriginal ); + nPlans( 3 ); + + QueryPlanSet s3( ns(), BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) ); + TestOp newerOriginal; + s3.runOp( newerOriginal ); + nPlans( 3 ); + + runQuery(); + nPlans( 1 ); + } + private: + void nPlans( int n ) { + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ASSERT_EQUALS( n, s.nPlans() ); + } + void runQuery() { + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + TestOp original; + s.runOp( original ); + } + class TestOp : public QueryOp { + public: + virtual void init() {} + virtual void next() { + setComplete(); + } + virtual QueryOp *clone() const { + return new TestOp(); + } + virtual bool mayRecordPlan() const { return true; } + }; + class NoRecordTestOp : public TestOp { + virtual bool mayRecordPlan() const { return false; } + virtual QueryOp *clone() const { return new NoRecordTestOp(); } + }; + }; + + class TryAllPlansOnErr : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + + QueryPlanSet s( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + ScanOnlyTestOp op; + s.runOp( op ); + ASSERT( fromjson( "{$natural:1}" ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( s.fbs().pattern( BSON( "b" << 1 ) ) ) ) == 0 ); + ASSERT_EQUALS( 1, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( s.fbs().pattern( BSON( "b" << 1 ) ) ) ); + + QueryPlanSet s2( ns(), BSON( "a" << 4 ), BSON( "b" << 1 ) ); + TestOp op2; + ASSERT( s2.runOp( op2 )->complete() ); + } + private: + class TestOp : public QueryOp { + public: + virtual void init() {} + virtual void next() { + if ( qp().indexKey().firstElement().fieldName() == string( "$natural" ) ) + massert( 10410 , "throw", false ); + setComplete(); + } + virtual QueryOp *clone() const { + return new TestOp(); + } + virtual bool mayRecordPlan() const { return true; } + }; + class ScanOnlyTestOp : public TestOp { + virtual void next() { + if ( qp().indexKey().firstElement().fieldName() == string( "$natural" ) ) + setComplete(); + massert( 10411 , "throw", false ); + } + virtual QueryOp *clone() const { + return new ScanOnlyTestOp(); + } + }; + }; + + class FindOne : public Base { + public: + void run() { + BSONObj one = BSON( "a" << 1 ); + theDataFileMgr.insert( ns(), one ); + BSONObj result; + ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result ) ); + ASSERT_EXCEPTION( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ), AssertionException ); + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ) ); + } + }; + + class Delete : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + for( int i = 0; i < 200; ++i ) { + BSONObj two = BSON( "a" << 2 ); + theDataFileMgr.insert( ns(), two ); + } + BSONObj one = BSON( "a" << 1 ); + theDataFileMgr.insert( ns(), one ); + deleteObjects( ns(), BSON( "a" << 1 ), false ); + ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "a" << 1 ) ).pattern() ) ) == 0 ); + ASSERT_EQUALS( 2, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( FieldRangeSet( ns(), BSON( "a" << 1 ) ).pattern() ) ); + } + }; + + class DeleteOneScan : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "_id" << 1 ), false, "_id_1" ); + BSONObj one = BSON( "_id" << 3 << "a" << 1 ); + BSONObj two = BSON( "_id" << 2 << "a" << 1 ); + BSONObj three = BSON( "_id" << 1 << "a" << -1 ); + theDataFileMgr.insert( ns(), one ); + theDataFileMgr.insert( ns(), two ); + theDataFileMgr.insert( ns(), three ); + deleteObjects( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ), true ); + for( auto_ptr< Cursor > c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) + ASSERT( 3 != c->current().getIntField( "_id" ) ); + } + }; + + class DeleteOneIndex : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a" ); + BSONObj one = BSON( "a" << 2 << "_id" << 0 ); + BSONObj two = BSON( "a" << 1 << "_id" << 1 ); + BSONObj three = BSON( "a" << 0 << "_id" << 2 ); + theDataFileMgr.insert( ns(), one ); + theDataFileMgr.insert( ns(), two ); + theDataFileMgr.insert( ns(), three ); + deleteObjects( ns(), BSON( "a" << GTE << 0 << "_id" << GT << 0 ), true ); + for( auto_ptr< Cursor > c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) + ASSERT( 2 != c->current().getIntField( "_id" ) ); + } + }; + + class TryOtherPlansBeforeFinish : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + for( int i = 0; i < 100; ++i ) { + for( int j = 0; j < 2; ++j ) { + BSONObj temp = BSON( "a" << 100 - i - 1 << "b" << i ); + theDataFileMgr.insert( ns(), temp ); + } + } + Message m; + // Need to return at least 2 records to cause plan to be recorded. + assembleRequest( ns(), QUERY( "b" << 0 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m ); + stringstream ss; + { + DbMessage d(m); + QueryMessage q(d); + runQuery( m, q); + } + ASSERT( BSON( "$natural" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) ) == 0 ); + + Message m2; + assembleRequest( ns(), QUERY( "b" << 99 << "a" << GTE << 0 ).obj, 2, 0, 0, 0, m2 ); + { + DbMessage d(m2); + QueryMessage q(d); + runQuery( m2, q); + } + ASSERT( BSON( "a" << 1 ).woCompare( NamespaceDetailsTransient::_get( ns() ).indexForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) ) == 0 ); + ASSERT_EQUALS( 2, NamespaceDetailsTransient::_get( ns() ).nScannedForPattern( FieldRangeSet( ns(), BSON( "b" << 0 << "a" << GTE << 0 ) ).pattern() ) ); + } + }; + + class InQueryIntervals : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << i ); + theDataFileMgr.insert( ns(), temp ); + } + BSONObj hint = fromjson( "{$hint:{a:1}}" ); + BSONElement hintElt = hint.firstElement(); + QueryPlanSet s( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSONObj(), &hintElt ); + QueryPlan qp( nsd(), 1, s.fbs(), BSONObj() ); + auto_ptr< Cursor > c = qp.newCursor(); + double expected[] = { 2, 3, 6, 9 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); + } + ASSERT( !c->ok() ); + + // now check reverse + { + QueryPlanSet s( ns(), fromjson( "{a:{$in:[2,3,6,9,11]}}" ), BSON( "a" << -1 ), &hintElt ); + QueryPlan qp( nsd(), 1, s.fbs(), BSON( "a" << -1 ) ); + auto_ptr< Cursor > c = qp.newCursor(); + double expected[] = { 9, 6, 3, 2 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); + } + ASSERT( !c->ok() ); + } + } + }; + + class EqualityThenIn : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << 5 << "b" << i ); + theDataFileMgr.insert( ns(), temp ); + } + BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" ); + BSONElement hintElt = hint.firstElement(); + QueryPlanSet s( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ), BSONObj(), &hintElt ); + QueryPlan qp( nsd(), 1, s.fbs(), BSONObj() ); + auto_ptr< Cursor > c = qp.newCursor(); + double expected[] = { 2, 3, 6, 9 }; + for( int i = 0; i < 4; ++i, c->advance() ) { + ASSERT_EQUALS( expected[ i ], c->current().getField( "b" ).number() ); + } + ASSERT( !c->ok() ); + } + }; + + class NotEqualityThenIn : public Base { + public: + void run() { + Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); + for( int i = 0; i < 10; ++i ) { + BSONObj temp = BSON( "a" << 5 << "b" << i ); + theDataFileMgr.insert( ns(), temp ); + } + BSONObj hint = fromjson( "{$hint:{a:1,b:1}}" ); + BSONElement hintElt = hint.firstElement(); + QueryPlanSet s( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ), BSONObj(), &hintElt ); + QueryPlan qp( nsd(), 1, s.fbs(), BSONObj() ); + auto_ptr< Cursor > c = qp.newCursor(); + for( int i = 2; i < 10; ++i, c->advance() ) { + ASSERT_EQUALS( i, c->current().getField( "b" ).number() ); + } + ASSERT( !c->ok() ); + } + }; + + } // namespace QueryPlanSetTests + + class All : public Suite { + public: + All() : Suite( "queryoptimizer" ){} + + void setupTests(){ + add< FieldRangeTests::Empty >(); + add< FieldRangeTests::Eq >(); + add< FieldRangeTests::DupEq >(); + add< FieldRangeTests::Lt >(); + add< FieldRangeTests::Lte >(); + add< FieldRangeTests::Gt >(); + add< FieldRangeTests::Gte >(); + add< FieldRangeTests::TwoLt >(); + add< FieldRangeTests::TwoGt >(); + add< FieldRangeTests::EqGte >(); + add< FieldRangeTests::EqGteInvalid >(); + add< FieldRangeTests::Regex >(); + add< FieldRangeTests::RegexObj >(); + add< FieldRangeTests::UnhelpfulRegex >(); + add< FieldRangeTests::In >(); + add< FieldRangeTests::Equality >(); + add< FieldRangeTests::SimplifiedQuery >(); + add< FieldRangeTests::QueryPatternTest >(); + add< FieldRangeTests::NoWhere >(); + add< FieldRangeTests::Numeric >(); + add< FieldRangeTests::InLowerBound >(); + add< FieldRangeTests::InUpperBound >(); + add< FieldRangeTests::MultiBound >(); + add< QueryPlanTests::NoIndex >(); + add< QueryPlanTests::SimpleOrder >(); + add< QueryPlanTests::MoreIndexThanNeeded >(); + add< QueryPlanTests::IndexSigns >(); + add< QueryPlanTests::IndexReverse >(); + add< QueryPlanTests::NoOrder >(); + add< QueryPlanTests::EqualWithOrder >(); + add< QueryPlanTests::Optimal >(); + add< QueryPlanTests::MoreOptimal >(); + add< QueryPlanTests::KeyMatch >(); + add< QueryPlanTests::MoreKeyMatch >(); + add< QueryPlanTests::ExactKeyQueryTypes >(); + add< QueryPlanTests::Unhelpful >(); + add< QueryPlanSetTests::NoIndexes >(); + add< QueryPlanSetTests::Optimal >(); + add< QueryPlanSetTests::NoOptimal >(); + add< QueryPlanSetTests::NoSpec >(); + add< QueryPlanSetTests::HintSpec >(); + add< QueryPlanSetTests::HintName >(); + add< QueryPlanSetTests::NaturalHint >(); + add< QueryPlanSetTests::NaturalSort >(); + add< QueryPlanSetTests::BadHint >(); + add< QueryPlanSetTests::Count >(); + add< QueryPlanSetTests::QueryMissingNs >(); + add< QueryPlanSetTests::UnhelpfulIndex >(); + add< QueryPlanSetTests::SingleException >(); + add< QueryPlanSetTests::AllException >(); + add< QueryPlanSetTests::SaveGoodIndex >(); + add< QueryPlanSetTests::TryAllPlansOnErr >(); + add< QueryPlanSetTests::FindOne >(); + add< QueryPlanSetTests::Delete >(); + add< QueryPlanSetTests::DeleteOneScan >(); + add< QueryPlanSetTests::DeleteOneIndex >(); + add< QueryPlanSetTests::TryOtherPlansBeforeFinish >(); + add< QueryPlanSetTests::InQueryIntervals >(); + add< QueryPlanSetTests::EqualityThenIn >(); + add< QueryPlanSetTests::NotEqualityThenIn >(); + } + } myall; + +} // namespace QueryOptimizerTests + diff --git a/dbtests/querytests.cpp b/dbtests/querytests.cpp new file mode 100644 index 0000000..4681bf0 --- /dev/null +++ b/dbtests/querytests.cpp @@ -0,0 +1,919 @@ +// querytests.cpp : query.{h,cpp} unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/query.h" + +#include "../db/db.h" +#include "../db/instance.h" +#include "../db/json.h" +#include "../db/lasterror.h" + +#include "dbtests.h" + +namespace QueryTests { + + class Base { + dblock lk; + public: + Base() { + dblock lk; + setClient( ns() ); + addIndex( fromjson( "{\"a\":1}" ) ); + } + ~Base() { + try { + auto_ptr< Cursor > c = theDataFileMgr.findAll( ns() ); + vector< DiskLoc > toDelete; + for(; c->ok(); c->advance() ) + toDelete.push_back( c->currLoc() ); + for( vector< DiskLoc >::iterator i = toDelete.begin(); i != toDelete.end(); ++i ) + theDataFileMgr.deleteRecord( ns(), i->rec(), *i, false ); + } catch ( ... ) { + FAIL( "Exception while cleaning up records" ); + } + } + protected: + static const char *ns() { + return "unittests.querytests"; + } + static void addIndex( const BSONObj &key ) { + BSONObjBuilder b; + b.append( "name", "index" ); + b.append( "ns", ns() ); + b.append( "key", key ); + BSONObj o = b.done(); + stringstream indexNs; + indexNs << "unittests.system.indexes"; + theDataFileMgr.insert( indexNs.str().c_str(), o.objdata(), o.objsize() ); + } + static void insert( const char *s ) { + insert( fromjson( s ) ); + } + static void insert( const BSONObj &o ) { + theDataFileMgr.insert( ns(), o.objdata(), o.objsize() ); + } + }; + + class CountBasic : public Base { + public: + void run() { + insert( "{\"a\":\"b\"}" ); + BSONObj cmd = fromjson( "{\"query\":{}}" ); + string err; + ASSERT_EQUALS( 1, runCount( ns(), cmd, err ) ); + } + }; + + class CountQuery : public Base { + public: + void run() { + insert( "{\"a\":\"b\"}" ); + insert( "{\"a\":\"b\",\"x\":\"y\"}" ); + insert( "{\"a\":\"c\"}" ); + BSONObj cmd = fromjson( "{\"query\":{\"a\":\"b\"}}" ); + string err; + ASSERT_EQUALS( 2, runCount( ns(), cmd, err ) ); + } + }; + + class CountFields : public Base { + public: + void run() { + insert( "{\"a\":\"b\"}" ); + insert( "{\"c\":\"d\"}" ); + BSONObj cmd = fromjson( "{\"query\":{},\"fields\":{\"a\":1}}" ); + string err; + ASSERT_EQUALS( 2, runCount( ns(), cmd, err ) ); + } + }; + + class CountQueryFields : public Base { + public: + void run() { + insert( "{\"a\":\"b\"}" ); + insert( "{\"a\":\"c\"}" ); + insert( "{\"d\":\"e\"}" ); + BSONObj cmd = fromjson( "{\"query\":{\"a\":\"b\"},\"fields\":{\"a\":1}}" ); + string err; + ASSERT_EQUALS( 1, runCount( ns(), cmd, err ) ); + } + }; + + class CountIndexedRegex : public Base { + public: + void run() { + insert( "{\"a\":\"b\"}" ); + insert( "{\"a\":\"c\"}" ); + BSONObj cmd = fromjson( "{\"query\":{\"a\":/^b/}}" ); + string err; + ASSERT_EQUALS( 1, runCount( ns(), cmd, err ) ); + } + }; + + class ClientBase { + public: + // NOTE: Not bothering to backup the old error record. + ClientBase() { + mongo::lastError.reset( new LastError() ); + } + ~ClientBase() { + mongo::lastError.release(); + } + protected: + static void insert( const char *ns, BSONObj o ) { + client_.insert( ns, o ); + } + static void update( const char *ns, BSONObj q, BSONObj o, bool upsert = 0 ) { + client_.update( ns, Query( q ), o, upsert ); + } + static bool error() { + return !client_.getPrevError().getField( "err" ).isNull(); + } + DBDirectClient &client() const { return client_; } + + static DBDirectClient client_; + }; + DBDirectClient ClientBase::client_; + + class BoundedKey : public ClientBase { + public: + void run() { + const char *ns = "unittests.querytests.BoundedKey"; + insert( ns, BSON( "a" << 1 ) ); + BSONObjBuilder a; + a.appendMaxKey( "$lt" ); + BSONObj limit = a.done(); + ASSERT( !client().findOne( ns, QUERY( "a" << limit ) ).isEmpty() ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + ASSERT( !client().findOne( ns, QUERY( "a" << limit ).hint( BSON( "a" << 1 ) ) ).isEmpty() ); + } + }; + + class GetMore : public ClientBase { + public: + ~GetMore() { + client().dropCollection( "unittests.querytests.GetMore" ); + } + void run() { + const char *ns = "unittests.querytests.GetMore"; + insert( ns, BSON( "a" << 1 ) ); + insert( ns, BSON( "a" << 2 ) ); + insert( ns, BSON( "a" << 3 ) ); + auto_ptr< DBClientCursor > cursor = client().query( ns, BSONObj(), 2 ); + long long cursorId = cursor->getCursorId(); + cursor->decouple(); + cursor.reset(); + cursor = client().getMore( ns, cursorId ); + ASSERT( cursor->more() ); + ASSERT_EQUALS( 3, cursor->next().getIntField( "a" ) ); + } + }; + + class ReturnOneOfManyAndTail : public ClientBase { + public: + ~ReturnOneOfManyAndTail() { + client().dropCollection( "unittests.querytests.ReturnOneOfManyAndTail" ); + } + void run() { + const char *ns = "unittests.querytests.ReturnOneOfManyAndTail"; + insert( ns, BSON( "a" << 0 ) ); + insert( ns, BSON( "a" << 1 ) ); + insert( ns, BSON( "a" << 2 ) ); + auto_ptr< DBClientCursor > c = client().query( ns, QUERY( "a" << GT << 0 ).hint( BSON( "$natural" << 1 ) ), 1, 0, 0, QueryOption_CursorTailable ); + // If only one result requested, a cursor is not saved. + ASSERT_EQUALS( 0, c->getCursorId() ); + ASSERT( c->more() ); + ASSERT_EQUALS( 1, c->next().getIntField( "a" ) ); + } + }; + + class TailNotAtEnd : public ClientBase { + public: + ~TailNotAtEnd() { + client().dropCollection( "unittests.querytests.TailNotAtEnd" ); + } + void run() { + const char *ns = "unittests.querytests.TailNotAtEnd"; + insert( ns, BSON( "a" << 0 ) ); + insert( ns, BSON( "a" << 1 ) ); + insert( ns, BSON( "a" << 2 ) ); + auto_ptr< DBClientCursor > c = client().query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); + ASSERT( 0 != c->getCursorId() ); + while( c->more() ) + c->next(); + ASSERT( 0 != c->getCursorId() ); + insert( ns, BSON( "a" << 3 ) ); + insert( ns, BSON( "a" << 4 ) ); + insert( ns, BSON( "a" << 5 ) ); + insert( ns, BSON( "a" << 6 ) ); + ASSERT( c->more() ); + ASSERT_EQUALS( 3, c->next().getIntField( "a" ) ); + } + }; + + class EmptyTail : public ClientBase { + public: + ~EmptyTail() { + client().dropCollection( "unittests.querytests.EmptyTail" ); + } + void run() { + const char *ns = "unittests.querytests.EmptyTail"; + ASSERT_EQUALS( 0, client().query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable )->getCursorId() ); + insert( ns, BSON( "a" << 0 ) ); + ASSERT( 0 != client().query( ns, QUERY( "a" << 1 ).hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable )->getCursorId() ); + } + }; + + class TailableDelete : public ClientBase { + public: + ~TailableDelete() { + client().dropCollection( "unittests.querytests.TailableDelete" ); + } + void run() { + const char *ns = "unittests.querytests.TailableDelete"; + insert( ns, BSON( "a" << 0 ) ); + insert( ns, BSON( "a" << 1 ) ); + auto_ptr< DBClientCursor > c = client().query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); + c->next(); + c->next(); + ASSERT( !c->more() ); + client().remove( ns, QUERY( "a" << 1 ) ); + insert( ns, BSON( "a" << 2 ) ); + ASSERT( !c->more() ); + ASSERT_EQUALS( 0, c->getCursorId() ); + } + }; + + class TailableInsertDelete : public ClientBase { + public: + ~TailableInsertDelete() { + client().dropCollection( "unittests.querytests.TailableInsertDelete" ); + } + void run() { + const char *ns = "unittests.querytests.TailableInsertDelete"; + insert( ns, BSON( "a" << 0 ) ); + insert( ns, BSON( "a" << 1 ) ); + auto_ptr< DBClientCursor > c = client().query( ns, Query().hint( BSON( "$natural" << 1 ) ), 2, 0, 0, QueryOption_CursorTailable ); + c->next(); + c->next(); + ASSERT( !c->more() ); + insert( ns, BSON( "a" << 2 ) ); + client().remove( ns, QUERY( "a" << 1 ) ); + ASSERT( c->more() ); + ASSERT_EQUALS( 2, c->next().getIntField( "a" ) ); + ASSERT( !c->more() ); + } + }; + + class OplogReplayMode : public ClientBase { + public: + ~OplogReplayMode() { + client().dropCollection( "unittests.querytests.OplogReplayMode" ); + } + void run() { + const char *ns = "unittests.querytests.OplogReplayMode"; + insert( ns, BSON( "a" << 3 ) ); + insert( ns, BSON( "a" << 0 ) ); + insert( ns, BSON( "a" << 1 ) ); + insert( ns, BSON( "a" << 2 ) ); + auto_ptr< DBClientCursor > c = client().query( ns, QUERY( "a" << GT << 1 ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_OplogReplay ); + ASSERT( c->more() ); + ASSERT_EQUALS( 2, c->next().getIntField( "a" ) ); + ASSERT( !c->more() ); + } + }; + + class BasicCount : public ClientBase { + public: + ~BasicCount() { + client().dropCollection( "unittests.querytests.BasicCount" ); + } + void run() { + const char *ns = "unittests.querytests.BasicCount"; + client().ensureIndex( ns, BSON( "a" << 1 ) ); + count( 0 ); + insert( ns, BSON( "a" << 3 ) ); + count( 0 ); + insert( ns, BSON( "a" << 4 ) ); + count( 1 ); + insert( ns, BSON( "a" << 5 ) ); + count( 1 ); + insert( ns, BSON( "a" << 4 ) ); + count( 2 ); + } + private: + void count( unsigned long long c ) const { + ASSERT_EQUALS( c, client().count( "unittests.querytests.BasicCount", BSON( "a" << 4 ) ) ); + } + }; + + class ArrayId : public ClientBase { + public: + ~ArrayId() { + client().dropCollection( "unittests.querytests.ArrayId" ); + } + void run() { + const char *ns = "unittests.querytests.ArrayId"; + client().ensureIndex( ns, BSON( "_id" << 1 ) ); + ASSERT( !error() ); + client().insert( ns, fromjson( "{'_id':[1,2]}" ) ); + ASSERT( error() ); + } + }; + + class UnderscoreNs : public ClientBase { + public: + ~UnderscoreNs() { + client().dropCollection( "unittests.querytests._UnderscoreNs" ); + } + void run() { + ASSERT( !error() ); + const char *ns = "unittests.querytests._UnderscoreNs"; + ASSERT( client().findOne( ns, "{}" ).isEmpty() ); + client().insert( ns, BSON( "a" << 1 ) ); + ASSERT_EQUALS( 1, client().findOne( ns, "{}" ).getIntField( "a" ) ); + ASSERT( !error() ); + } + }; + + class EmptyFieldSpec : public ClientBase { + public: + ~EmptyFieldSpec() { + client().dropCollection( "unittests.querytests.EmptyFieldSpec" ); + } + void run() { + const char *ns = "unittests.querytests.EmptyFieldSpec"; + client().insert( ns, BSON( "a" << 1 ) ); + ASSERT( !client().findOne( ns, "" ).isEmpty() ); + BSONObj empty; + ASSERT( !client().findOne( ns, "", &empty ).isEmpty() ); + } + }; + + class MultiNe : public ClientBase { + public: + ~MultiNe() { + client().dropCollection( "unittests.querytests.Ne" ); + } + void run() { + const char *ns = "unittests.querytests.Ne"; + client().insert( ns, fromjson( "{a:[1,2]}" ) ); + ASSERT( client().findOne( ns, fromjson( "{a:{$ne:1}}" ) ).isEmpty() ); + BSONObj spec = fromjson( "{a:{$ne:1,$ne:2}}" ); + ASSERT( client().findOne( ns, spec ).isEmpty() ); + } + }; + + class EmbeddedNe : public ClientBase { + public: + ~EmbeddedNe() { + client().dropCollection( "unittests.querytests.NestedNe" ); + } + void run() { + const char *ns = "unittests.querytests.NestedNe"; + client().insert( ns, fromjson( "{a:[{b:1},{b:2}]}" ) ); + ASSERT( client().findOne( ns, fromjson( "{'a.b':{$ne:1}}" ) ).isEmpty() ); + } + }; + + class AutoResetIndexCache : public ClientBase { + public: + ~AutoResetIndexCache() { + client().dropCollection( "unittests.querytests.AutoResetIndexCache" ); + } + static const char *ns() { return "unittests.querytests.AutoResetIndexCache"; } + static const char *idxNs() { return "unittests.system.indexes"; } + void index() const { ASSERT( !client().findOne( idxNs(), BSON( "name" << NE << "_id_" ) ).isEmpty() ); } + void noIndex() const { ASSERT( client().findOne( idxNs(), BSON( "name" << NE << "_id_" ) ).isEmpty() ); } + void checkIndex() { + client().ensureIndex( ns(), BSON( "a" << 1 ) ); + index(); + } + void run() { + client().dropDatabase( "unittests" ); + noIndex(); + checkIndex(); + client().dropCollection( ns() ); + noIndex(); + checkIndex(); + client().dropDatabase( "unittests" ); + noIndex(); + checkIndex(); + } + }; + + class UniqueIndex : public ClientBase { + public: + ~UniqueIndex() { + client().dropCollection( "unittests.querytests.UniqueIndex" ); + } + void run() { + const char *ns = "unittests.querytests.UniqueIndex"; + client().ensureIndex( ns, BSON( "a" << 1 ), true ); + client().insert( ns, BSON( "a" << 4 << "b" << 2 ) ); + client().insert( ns, BSON( "a" << 4 << "b" << 3 ) ); + ASSERT_EQUALS( 1U, client().count( ns, BSONObj() ) ); + client().dropCollection( ns ); + client().ensureIndex( ns, BSON( "b" << 1 ), true ); + client().insert( ns, BSON( "a" << 4 << "b" << 2 ) ); + client().insert( ns, BSON( "a" << 4 << "b" << 3 ) ); + ASSERT_EQUALS( 2U, client().count( ns, BSONObj() ) ); + } + }; + + class UniqueIndexPreexistingData : public ClientBase { + public: + ~UniqueIndexPreexistingData() { + client().dropCollection( "unittests.querytests.UniqueIndexPreexistingData" ); + } + void run() { + const char *ns = "unittests.querytests.UniqueIndexPreexistingData"; + client().insert( ns, BSON( "a" << 4 << "b" << 2 ) ); + client().insert( ns, BSON( "a" << 4 << "b" << 3 ) ); + client().ensureIndex( ns, BSON( "a" << 1 ), true ); + ASSERT_EQUALS( 0U, client().count( "unittests.system.indexes", BSON( "ns" << ns << "name" << NE << "_id_" ) ) ); + } + }; + + class SubobjectInArray : public ClientBase { + public: + ~SubobjectInArray() { + client().dropCollection( "unittests.querytests.SubobjectInArray" ); + } + void run() { + const char *ns = "unittests.querytests.SubobjectInArray"; + client().insert( ns, fromjson( "{a:[{b:{c:1}}]}" ) ); + ASSERT( !client().findOne( ns, BSON( "a.b.c" << 1 ) ).isEmpty() ); + ASSERT( !client().findOne( ns, fromjson( "{'a.c':null}" ) ).isEmpty() ); + } + }; + + class Size : public ClientBase { + public: + ~Size() { + client().dropCollection( "unittests.querytests.Size" ); + } + void run() { + const char *ns = "unittests.querytests.Size"; + client().insert( ns, fromjson( "{a:[1,2,3]}" ) ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + ASSERT( client().query( ns, QUERY( "a" << mongo::SIZE << 3 ).hint( BSON( "a" << 1 ) ) )->more() ); + } + }; + + class FullArray : public ClientBase { + public: + ~FullArray() { + client().dropCollection( "unittests.querytests.IndexedArray" ); + } + void run() { + const char *ns = "unittests.querytests.IndexedArray"; + client().insert( ns, fromjson( "{a:[1,2,3]}" ) ); + ASSERT( client().query( ns, Query( "{a:[1,2,3]}" ) )->more() ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + ASSERT( client().query( ns, Query( "{a:{$in:[1,[1,2,3]]}}" ).hint( BSON( "a" << 1 ) ) )->more() ); + ASSERT( client().query( ns, Query( "{a:[1,2,3]}" ).hint( BSON( "a" << 1 ) ) )->more() ); // SERVER-146 + } + }; + + class InsideArray : public ClientBase { + public: + ~InsideArray() { + client().dropCollection( "unittests.querytests.InsideArray" ); + } + void run() { + const char *ns = "unittests.querytests.InsideArray"; + client().insert( ns, fromjson( "{a:[[1],2]}" ) ); + check( "$natural" ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + check( "a" ); // SERVER-146 + } + private: + void check( const string &hintField ) { + const char *ns = "unittests.querytests.InsideArray"; + ASSERT( client().query( ns, Query( "{a:[[1],2]}" ).hint( BSON( hintField << 1 ) ) )->more() ); + ASSERT( client().query( ns, Query( "{a:[1]}" ).hint( BSON( hintField << 1 ) ) )->more() ); + ASSERT( client().query( ns, Query( "{a:2}" ).hint( BSON( hintField << 1 ) ) )->more() ); + ASSERT( !client().query( ns, Query( "{a:1}" ).hint( BSON( hintField << 1 ) ) )->more() ); + } + }; + + class IndexInsideArrayCorrect : public ClientBase { + public: + ~IndexInsideArrayCorrect() { + client().dropCollection( "unittests.querytests.IndexInsideArrayCorrect" ); + } + void run() { + const char *ns = "unittests.querytests.IndexInsideArrayCorrect"; + client().insert( ns, fromjson( "{'_id':1,a:[1]}" ) ); + client().insert( ns, fromjson( "{'_id':2,a:[[1]]}" ) ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + ASSERT_EQUALS( 1, client().query( ns, Query( "{a:[1]}" ).hint( BSON( "a" << 1 ) ) )->next().getIntField( "_id" ) ); + } + }; + + class SubobjArr : public ClientBase { + public: + ~SubobjArr() { + client().dropCollection( "unittests.querytests.SubobjArr" ); + } + void run() { + const char *ns = "unittests.querytests.SubobjArr"; + client().insert( ns, fromjson( "{a:[{b:[1]}]}" ) ); + check( "$natural" ); + client().ensureIndex( ns, BSON( "a" << 1 ) ); + check( "a" ); + } + private: + void check( const string &hintField ) { + const char *ns = "unittests.querytests.SubobjArr"; + ASSERT( client().query( ns, Query( "{'a.b':1}" ).hint( BSON( hintField << 1 ) ) )->more() ); + ASSERT( !client().query( ns, Query( "{'a.b':[1]}" ).hint( BSON( hintField << 1 ) ) )->more() ); + } + }; + + class MinMax : public ClientBase { + public: + MinMax() : ns( "unittests.querytests.MinMax" ) {} + ~MinMax() { + client().dropCollection( "unittests.querytests.MinMax" ); + } + void run() { + client().ensureIndex( ns, BSON( "a" << 1 << "b" << 1 ) ); + client().insert( ns, BSON( "a" << 1 << "b" << 1 ) ); + client().insert( ns, BSON( "a" << 1 << "b" << 2 ) ); + client().insert( ns, BSON( "a" << 2 << "b" << 1 ) ); + client().insert( ns, BSON( "a" << 2 << "b" << 2 ) ); + + ASSERT_EQUALS( 4, count( client().query( ns, BSONObj() ) ) ); + BSONObj hints[] = { BSONObj(), BSON( "a" << 1 << "b" << 1 ) }; + for( int i = 0; i < 2; ++i ) { + check( 0, 0, 3, 3, 4, hints[ i ] ); + check( 1, 1, 2, 2, 3, hints[ i ] ); + check( 1, 2, 2, 2, 2, hints[ i ] ); + check( 1, 2, 2, 1, 1, hints[ i ] ); + + auto_ptr< DBClientCursor > c = query( 1, 2, 2, 2, hints[ i ] ); + BSONObj obj = c->next(); + ASSERT_EQUALS( 1, obj.getIntField( "a" ) ); + ASSERT_EQUALS( 2, obj.getIntField( "b" ) ); + obj = c->next(); + ASSERT_EQUALS( 2, obj.getIntField( "a" ) ); + ASSERT_EQUALS( 1, obj.getIntField( "b" ) ); + ASSERT( !c->more() ); + } + } + private: + auto_ptr< DBClientCursor > query( int minA, int minB, int maxA, int maxB, const BSONObj &hint ) { + Query q; + q = q.minKey( BSON( "a" << minA << "b" << minB ) ).maxKey( BSON( "a" << maxA << "b" << maxB ) ); + if ( !hint.isEmpty() ) + q.hint( hint ); + return client().query( ns, q ); + } + void check( int minA, int minB, int maxA, int maxB, int expectedCount, const BSONObj &hint = empty_ ) { + ASSERT_EQUALS( expectedCount, count( query( minA, minB, maxA, maxB, hint ) ) ); + } + int count( auto_ptr< DBClientCursor > c ) { + int ret = 0; + while( c->more() ) { + ++ret; + c->next(); + } + return ret; + } + const char *ns; + static BSONObj empty_; + }; + BSONObj MinMax::empty_; + + class DirectLocking : public ClientBase { + public: + void run() { + dblock lk; + setClient( "unittests.DirectLocking" ); + client().remove( "a.b", BSONObj() ); + ASSERT_EQUALS( "unittests", cc().database()->name ); + } + const char *ns; + }; + + class FastCountIn : public ClientBase { + public: + ~FastCountIn() { + client().dropCollection( "unittests.querytests.FastCountIn" ); + } + void run() { + const char *ns = "unittests.querytests.FastCountIn"; + client().insert( ns, BSON( "i" << "a" ) ); + client().ensureIndex( ns, BSON( "i" << 1 ) ); + ASSERT_EQUALS( 1U, client().count( ns, fromjson( "{i:{$in:['a']}}" ) ) ); + } + }; + + class EmbeddedArray : public ClientBase { + public: + ~EmbeddedArray() { + client().dropCollection( "unittests.querytests.EmbeddedArray" ); + } + void run() { + const char *ns = "unittests.querytests.EmbeddedArray"; + client().insert( ns, fromjson( "{foo:{bar:['spam']}}" ) ); + client().insert( ns, fromjson( "{foo:{bar:['spam','eggs']}}" ) ); + client().insert( ns, fromjson( "{bar:['spam']}" ) ); + client().insert( ns, fromjson( "{bar:['spam','eggs']}" ) ); + ASSERT_EQUALS( 2U, client().count( ns, BSON( "bar" << "spam" ) ) ); + ASSERT_EQUALS( 2U, client().count( ns, BSON( "foo.bar" << "spam" ) ) ); + } + }; + + class DifferentNumbers : public ClientBase { + public: + ~DifferentNumbers(){ + client().dropCollection( "unittests.querytests.DifferentNumbers" ); + } + void t( const char * ns ){ + auto_ptr< DBClientCursor > cursor = client().query( ns, Query().sort( "7" ) ); + while ( cursor->more() ){ + BSONObj o = cursor->next(); + cout << " foo " << o << endl; + } + + } + void run() { + const char *ns = "unittests.querytests.DifferentNumbers"; + { BSONObjBuilder b; b.append( "7" , (int)4 ); client().insert( ns , b.obj() ); } + { BSONObjBuilder b; b.append( "7" , (long long)2 ); client().insert( ns , b.obj() ); } + { BSONObjBuilder b; b.appendNull( "7" ); client().insert( ns , b.obj() ); } + { BSONObjBuilder b; b.append( "7" , "b" ); client().insert( ns , b.obj() ); } + { BSONObjBuilder b; b.appendNull( "8" ); client().insert( ns , b.obj() ); } + { BSONObjBuilder b; b.append( "7" , (double)3.7 ); client().insert( ns , b.obj() ); } + + t(ns); + client().ensureIndex( ns , BSON( "7" << 1 ) ); + t(ns); + } + }; + + class CollectionBase : public ClientBase { + public: + + CollectionBase( string leaf ){ + _ns = "unittests.querytests."; + _ns += leaf; + } + + virtual ~CollectionBase(){ + client().dropCollection( ns() ); + } + + int count(){ + return (int) client().count( ns() ); + } + + const char * ns(){ + return _ns.c_str(); + } + + private: + string _ns; + }; + + class SymbolStringSame : public CollectionBase { + public: + SymbolStringSame() : CollectionBase( "symbolstringsame" ){} + + void run(){ + { BSONObjBuilder b; b.appendSymbol( "x" , "eliot" ); b.append( "z" , 17 ); client().insert( ns() , b.obj() ); } + ASSERT_EQUALS( 17 , client().findOne( ns() , BSONObj() )["z"].number() ); + { + BSONObjBuilder b; + b.appendSymbol( "x" , "eliot" ); + ASSERT_EQUALS( 17 , client().findOne( ns() , b.obj() )["z"].number() ); + } + ASSERT_EQUALS( 17 , client().findOne( ns() , BSON( "x" << "eliot" ) )["z"].number() ); + client().ensureIndex( ns() , BSON( "x" << 1 ) ); + ASSERT_EQUALS( 17 , client().findOne( ns() , BSON( "x" << "eliot" ) )["z"].number() ); + } + }; + + class TailableCappedRaceCondition : public CollectionBase { + public: + + TailableCappedRaceCondition() : CollectionBase( "tailablecappedrace" ){ + client().dropCollection( ns() ); + _n = 0; + } + void run(){ + string err; + + writelock lk(""); + setClient( "unittests" ); + + ASSERT( userCreateNS( ns() , fromjson( "{ capped : true , size : 2000 }" ) , err , false ) ); + for ( int i=0; i<100; i++ ){ + insertNext(); + ASSERT( count() < 45 ); + } + + int a = count(); + + auto_ptr< DBClientCursor > c = client().query( ns() , QUERY( "i" << GT << 0 ).hint( BSON( "$natural" << 1 ) ), 0, 0, 0, QueryOption_CursorTailable ); + int n=0; + while ( c->more() ){ + BSONObj z = c->next(); + n++; + } + + ASSERT_EQUALS( a , n ); + + insertNext(); + ASSERT( c->more() ); + + for ( int i=0; i<50; i++ ){ + insertNext(); + } + + while ( c->more() ){ c->next(); } + ASSERT( c->isDead() ); + } + + void insertNext(){ + insert( ns() , BSON( "i" << _n++ ) ); + } + + int _n; + }; + + class HelperTest : public CollectionBase { + public: + + HelperTest() : CollectionBase( "helpertest" ){ + } + + void run(){ + writelock lk(""); + setClient( "unittests" ); + + for ( int i=0; i<50; i++ ){ + insert( ns() , BSON( "_id" << i << "x" << i * 2 ) ); + } + + ASSERT_EQUALS( 50 , count() ); + + BSONObj res; + ASSERT( Helpers::findOne( ns() , BSON( "_id" << 20 ) , res , true ) ); + ASSERT_EQUALS( 40 , res["x"].numberInt() ); + + ASSERT( Helpers::findById( cc(), ns() , BSON( "_id" << 20 ) , res ) ); + ASSERT_EQUALS( 40 , res["x"].numberInt() ); + + ASSERT( ! Helpers::findById( cc(), ns() , BSON( "_id" << 200 ) , res ) ); + + unsigned long long slow , fast; + + int n = 10000; + { + Timer t; + for ( int i=0; i<n; i++ ){ + ASSERT( Helpers::findOne( ns() , BSON( "_id" << 20 ) , res , true ) ); + } + slow = t.micros(); + } + { + Timer t; + for ( int i=0; i<n; i++ ){ + ASSERT( Helpers::findById( cc(), ns() , BSON( "_id" << 20 ) , res ) ); + } + fast = t.micros(); + } + + cout << "HelperTest slow:" << slow << " fast:" << fast << endl; + + { + auto_ptr<CursorIterator> i = Helpers::find( ns() ); + int n = 0; + while ( i->hasNext() ){ + BSONObj o = i->next(); + n++; + } + ASSERT_EQUALS( 50 , n ); + + i = Helpers::find( ns() , BSON( "_id" << 20 ) ); + n = 0; + while ( i->hasNext() ){ + BSONObj o = i->next(); + n++; + } + ASSERT_EQUALS( 1 , n ); + } + + } + }; + + class HelperByIdTest : public CollectionBase { + public: + + HelperByIdTest() : CollectionBase( "helpertestbyid" ){ + } + + void run(){ + writelock lk(""); + setClient( "unittests" ); + + for ( int i=0; i<1000; i++ ){ + insert( ns() , BSON( "_id" << i << "x" << i * 2 ) ); + } + for ( int i=0; i<1000; i+=2 ){ + client_.remove( ns() , BSON( "_id" << i ) ); + } + + BSONObj res; + for ( int i=0; i<1000; i++ ){ + bool found = Helpers::findById( cc(), ns() , BSON( "_id" << i ) , res ); + ASSERT_EQUALS( i % 2 , int(found) ); + } + + } + }; + + class ClientCursorTest : public CollectionBase{ + ClientCursorTest() : CollectionBase( "clientcursortest" ){ + } + + void run(){ + writelock lk(""); + setClient( "unittests" ); + + for ( int i=0; i<1000; i++ ){ + insert( ns() , BSON( "_id" << i << "x" << i * 2 ) ); + } + + + } + }; + + class All : public Suite { + public: + All() : Suite( "query" ) { + } + + void setupTests(){ + add< CountBasic >(); + add< CountQuery >(); + add< CountFields >(); + add< CountQueryFields >(); + add< CountIndexedRegex >(); + add< BoundedKey >(); + add< GetMore >(); + add< ReturnOneOfManyAndTail >(); + add< TailNotAtEnd >(); + add< EmptyTail >(); + add< TailableDelete >(); + add< TailableInsertDelete >(); + add< OplogReplayMode >(); + add< ArrayId >(); + add< UnderscoreNs >(); + add< EmptyFieldSpec >(); + add< MultiNe >(); + add< EmbeddedNe >(); + add< AutoResetIndexCache >(); + add< UniqueIndex >(); + add< UniqueIndexPreexistingData >(); + add< SubobjectInArray >(); + add< Size >(); + add< FullArray >(); + add< InsideArray >(); + add< IndexInsideArrayCorrect >(); + add< SubobjArr >(); + add< MinMax >(); + add< DirectLocking >(); + add< FastCountIn >(); + add< EmbeddedArray >(); + add< DifferentNumbers >(); + add< SymbolStringSame >(); + add< TailableCappedRaceCondition >(); + add< HelperTest >(); + add< HelperByIdTest >(); + } + } myall; + +} // namespace QueryTests + diff --git a/dbtests/repltests.cpp b/dbtests/repltests.cpp new file mode 100644 index 0000000..d4d97c1 --- /dev/null +++ b/dbtests/repltests.cpp @@ -0,0 +1,1050 @@ +// repltests.cpp : Unit tests for replication +// + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/repl.h" + +#include "../db/db.h" +#include "../db/instance.h" +#include "../db/json.h" + +#include "dbtests.h" + +namespace mongo { + void createOplog(); +} + +namespace ReplTests { + + BSONObj f( const char *s ) { + return fromjson( s ); + } + + class Base { + public: + Base() { + master = true; + createOplog(); + dblock lk; + setClient( ns() ); + ensureHaveIdIndex( ns() ); + } + ~Base() { + try { + master = false; + deleteAll( ns() ); + deleteAll( cllNS() ); + } catch ( ... ) { + FAIL( "Exception while cleaning up test" ); + } + } + protected: + static const char *ns() { + return "unittests.repltests"; + } + static const char *cllNS() { + return "local.oplog.$main"; + } + DBDirectClient *client() const { return &client_; } + BSONObj one( const BSONObj &query = BSONObj() ) const { + return client()->findOne( ns(), query ); + } + void checkOne( const BSONObj &o ) const { + check( o, one( o ) ); + } + void checkAll( const BSONObj &o ) const { + auto_ptr< DBClientCursor > c = client()->query( ns(), o ); + assert( c->more() ); + while( c->more() ) { + check( o, c->next() ); + } + } + void check( const BSONObj &expected, const BSONObj &got ) const { + if ( expected.woCompare( got ) ) { + out() << "expected: " << expected.toString() + << ", got: " << got.toString() << endl; + } + ASSERT_EQUALS( expected , got ); + } + BSONObj oneOp() const { + return client()->findOne( cllNS(), BSONObj() ); + } + int count() const { + int count = 0; + dblock lk; + setClient( ns() ); + auto_ptr< Cursor > c = theDataFileMgr.findAll( ns() ); + for(; c->ok(); c->advance(), ++count ) { +// cout << "obj: " << c->current().toString() << endl; + } + return count; + } + static int opCount() { + dblock lk; + setClient( cllNS() ); + int count = 0; + for( auto_ptr< Cursor > c = theDataFileMgr.findAll( cllNS() ); c->ok(); c->advance() ) + ++count; + return count; + } + static void applyAllOperations() { + class Applier : public ReplSource { + public: + static void apply( const BSONObj &op ) { + ReplSource::applyOperation( op ); + } + }; + dblock lk; + setClient( cllNS() ); + vector< BSONObj > ops; + for( auto_ptr< Cursor > c = theDataFileMgr.findAll( cllNS() ); c->ok(); c->advance() ) + ops.push_back( c->current() ); + setClient( ns() ); + for( vector< BSONObj >::iterator i = ops.begin(); i != ops.end(); ++i ) + Applier::apply( *i ); + } + static void printAll( const char *ns ) { + dblock lk; + setClient( ns ); + auto_ptr< Cursor > c = theDataFileMgr.findAll( ns ); + vector< DiskLoc > toDelete; + out() << "all for " << ns << endl; + for(; c->ok(); c->advance() ) { + out() << c->current().toString() << endl; + } + } + // These deletes don't get logged. + static void deleteAll( const char *ns ) { + dblock lk; + setClient( ns ); + auto_ptr< Cursor > c = theDataFileMgr.findAll( ns ); + vector< DiskLoc > toDelete; + for(; c->ok(); c->advance() ) { + toDelete.push_back( c->currLoc() ); + } + for( vector< DiskLoc >::iterator i = toDelete.begin(); i != toDelete.end(); ++i ) { + theDataFileMgr.deleteRecord( ns, i->rec(), *i, true ); + } + } + static void insert( const BSONObj &o, bool god = false ) { + dblock lk; + setClient( ns() ); + theDataFileMgr.insert( ns(), o.objdata(), o.objsize(), god ); + } + static BSONObj wid( const char *json ) { + class BSONObjBuilder b; + OID id; + id.init(); + b.appendOID( "_id", &id ); + b.appendElements( fromjson( json ) ); + return b.obj(); + } + private: + static DBDirectClient client_; + }; + DBDirectClient Base::client_; + + class LogBasic : public Base { + public: + void run() { + ASSERT_EQUALS( 1, opCount() ); + client()->insert( ns(), fromjson( "{\"a\":\"b\"}" ) ); + ASSERT_EQUALS( 2, opCount() ); + } + }; + + namespace Idempotence { + + class Base : public ReplTests::Base { + public: + virtual ~Base() {} + void run() { + reset(); + doIt(); + int nOps = opCount(); + check(); + applyAllOperations(); + check(); + ASSERT_EQUALS( nOps, opCount() ); + + reset(); + applyAllOperations(); + check(); + ASSERT_EQUALS( nOps, opCount() ); + applyAllOperations(); + check(); + ASSERT_EQUALS( nOps, opCount() ); + } + protected: + virtual void doIt() const = 0; + virtual void check() const = 0; + virtual void reset() const = 0; + }; + + class InsertTimestamp : public Base { + public: + void doIt() const { + BSONObjBuilder b; + b.append( "a", 1 ); + b.appendTimestamp( "t" ); + client()->insert( ns(), b.done() ); + date_ = client()->findOne( ns(), QUERY( "a" << 1 ) ).getField( "t" ).date(); + } + void check() const { + BSONObj o = client()->findOne( ns(), QUERY( "a" << 1 ) ); + ASSERT( 0 != o.getField( "t" ).date() ); + ASSERT_EQUALS( date_, o.getField( "t" ).date() ); + } + void reset() const { + deleteAll( ns() ); + } + private: + mutable Date_t date_; + }; + + class InsertAutoId : public Base { + public: + InsertAutoId() : o_( fromjson( "{\"a\":\"b\"}" ) ) {} + void doIt() const { + client()->insert( ns(), o_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + } + void reset() const { + deleteAll( ns() ); + } + protected: + BSONObj o_; + }; + + class InsertWithId : public InsertAutoId { + public: + InsertWithId() { + o_ = fromjson( "{\"_id\":ObjectId(\"0f0f0f0f0f0f0f0f0f0f0f0f\"),\"a\":\"b\"}" ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( o_ ); + } + }; + + class InsertTwo : public Base { + public: + InsertTwo() : + o_( fromjson( "{'_id':1,a:'b'}" ) ), + t_( fromjson( "{'_id':2,c:'d'}" ) ) {} + void doIt() const { + vector< BSONObj > v; + v.push_back( o_ ); + v.push_back( t_ ); + client()->insert( ns(), v ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + checkOne( o_ ); + checkOne( t_ ); + } + void reset() const { + deleteAll( ns() ); + } + private: + BSONObj o_; + BSONObj t_; + }; + + class InsertTwoIdentical : public Base { + public: + InsertTwoIdentical() : o_( fromjson( "{\"a\":\"b\"}" ) ) {} + void doIt() const { + client()->insert( ns(), o_ ); + client()->insert( ns(), o_ ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + } + void reset() const { + deleteAll( ns() ); + } + private: + BSONObj o_; + }; + + class UpdateTimestamp : public Base { + public: + void doIt() const { + BSONObjBuilder b; + b.append( "_id", 1 ); + b.appendTimestamp( "t" ); + client()->update( ns(), BSON( "_id" << 1 ), b.done() ); + date_ = client()->findOne( ns(), QUERY( "_id" << 1 ) ).getField( "t" ).date(); + } + void check() const { + BSONObj o = client()->findOne( ns(), QUERY( "_id" << 1 ) ); + ASSERT( 0 != o.getField( "t" ).date() ); + ASSERT_EQUALS( date_, o.getField( "t" ).date() ); + } + void reset() const { + deleteAll( ns() ); + insert( BSON( "_id" << 1 ) ); + } + private: + mutable Date_t date_; + }; + + class UpdateSameField : public Base { + public: + UpdateSameField() : + q_( fromjson( "{a:'b'}" ) ), + o1_( wid( "{a:'b'}" ) ), + o2_( wid( "{a:'b'}" ) ), + u_( fromjson( "{a:'c'}" ) ){} + void doIt() const { + client()->update( ns(), q_, u_ ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + ASSERT( !client()->findOne( ns(), q_ ).isEmpty() ); + ASSERT( !client()->findOne( ns(), u_ ).isEmpty() ); + } + void reset() const { + deleteAll( ns() ); + insert( o1_ ); + insert( o2_ ); + } + private: + BSONObj q_, o1_, o2_, u_; + }; + + class UpdateSameFieldWithId : public Base { + public: + UpdateSameFieldWithId() : + o_( fromjson( "{'_id':1,a:'b'}" ) ), + q_( fromjson( "{a:'b'}" ) ), + u_( fromjson( "{'_id':1,a:'c'}" ) ){} + void doIt() const { + client()->update( ns(), q_, u_ ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + ASSERT( !client()->findOne( ns(), q_ ).isEmpty() ); + ASSERT( !client()->findOne( ns(), u_ ).isEmpty() ); + } + void reset() const { + deleteAll( ns() ); + insert( o_ ); + insert( fromjson( "{'_id':2,a:'b'}" ) ); + } + private: + BSONObj o_, q_, u_; + }; + + class UpdateSameFieldExplicitId : public Base { + public: + UpdateSameFieldExplicitId() : + o_( fromjson( "{'_id':1,a:'b'}" ) ), + u_( fromjson( "{'_id':1,a:'c'}" ) ){} + void doIt() const { + client()->update( ns(), o_, u_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( u_ ); + } + void reset() const { + deleteAll( ns() ); + insert( o_ ); + } + protected: + BSONObj o_, u_; + }; + + class UpdateId : public UpdateSameFieldExplicitId { + public: + UpdateId() { + o_ = fromjson( "{'_id':1}" ); + u_ = fromjson( "{'_id':2}" ); + } + }; + + class UpdateDifferentFieldExplicitId : public Base { + public: + UpdateDifferentFieldExplicitId() : + o_( fromjson( "{'_id':1,a:'b'}" ) ), + q_( fromjson( "{'_id':1}" ) ), + u_( fromjson( "{'_id':1,a:'c'}" ) ){} + void doIt() const { + client()->update( ns(), q_, u_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( u_ ); + } + void reset() const { + deleteAll( ns() ); + insert( o_ ); + } + protected: + BSONObj o_, q_, u_; + }; + + class UpsertUpdateNoMods : public UpdateDifferentFieldExplicitId { + void doIt() const { + client()->update( ns(), q_, u_, true ); + } + }; + + class UpsertInsertNoMods : public InsertAutoId { + void doIt() const { + client()->update( ns(), fromjson( "{a:'c'}" ), o_, true ); + } + }; + + class UpdateSet : public Base { + public: + UpdateSet() : + o_( fromjson( "{'_id':1,a:5}" ) ), + q_( fromjson( "{a:5}" ) ), + u_( fromjson( "{$set:{a:7}}" ) ), + ou_( fromjson( "{'_id':1,a:7}" ) ) {} + void doIt() const { + client()->update( ns(), q_, u_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( ou_ ); + } + void reset() const { + deleteAll( ns() ); + insert( o_ ); + } + protected: + BSONObj o_, q_, u_, ou_; + }; + + class UpdateInc : public Base { + public: + UpdateInc() : + o_( fromjson( "{'_id':1,a:5}" ) ), + q_( fromjson( "{a:5}" ) ), + u_( fromjson( "{$inc:{a:3}}" ) ), + ou_( fromjson( "{'_id':1,a:8}" ) ) {} + void doIt() const { + client()->update( ns(), q_, u_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( ou_ ); + } + void reset() const { + deleteAll( ns() ); + insert( o_ ); + } + protected: + BSONObj o_, q_, u_, ou_; + }; + + class UpsertInsertIdMod : public Base { + public: + UpsertInsertIdMod() : + q_( fromjson( "{'_id':5,a:4}" ) ), + u_( fromjson( "{$inc:{a:3}}" ) ), + ou_( fromjson( "{'_id':5,a:7}" ) ) {} + void doIt() const { + client()->update( ns(), q_, u_, true ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( ou_ ); + } + void reset() const { + deleteAll( ns() ); + } + protected: + BSONObj q_, u_, ou_; + }; + + class UpsertInsertSet : public Base { + public: + UpsertInsertSet() : + q_( fromjson( "{a:5}" ) ), + u_( fromjson( "{$set:{a:7}}" ) ), + ou_( fromjson( "{a:7}" ) ) {} + void doIt() const { + client()->update( ns(), q_, u_, true ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + ASSERT( !client()->findOne( ns(), ou_ ).isEmpty() ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':7,a:7}" ) ); + } + protected: + BSONObj o_, q_, u_, ou_; + }; + + class UpsertInsertInc : public Base { + public: + UpsertInsertInc() : + q_( fromjson( "{a:5}" ) ), + u_( fromjson( "{$inc:{a:3}}" ) ), + ou_( fromjson( "{a:8}" ) ) {} + void doIt() const { + client()->update( ns(), q_, u_, true ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + ASSERT( !client()->findOne( ns(), ou_ ).isEmpty() ); + } + void reset() const { + deleteAll( ns() ); + } + protected: + BSONObj o_, q_, u_, ou_; + }; + + class MultiInc : public Base { + public: + + string s() const { + stringstream ss; + auto_ptr<DBClientCursor> cc = client()->query( ns() , Query().sort( BSON( "_id" << 1 ) ) ); + bool first = true; + while ( cc->more() ){ + if ( first ) first = false; + else ss << ","; + + BSONObj o = cc->next(); + ss << o["x"].numberInt(); + } + return ss.str(); + } + + void doIt() const { + client()->insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); + client()->insert( ns(), BSON( "_id" << 2 << "x" << 5 ) ); + + ASSERT_EQUALS( "1,5" , s() ); + + client()->update( ns() , BSON( "_id" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "2,5" , s() ); + + client()->update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "3,5" , s() ); + + client()->update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) , false , true ); + check(); + } + + void check() const { + ASSERT_EQUALS( "4,6" , s() ); + } + + void reset() const { + deleteAll( ns() ); + } + }; + + class UpdateWithoutPreexistingId : public Base { + public: + UpdateWithoutPreexistingId() : + o_( fromjson( "{a:5}" ) ), + u_( fromjson( "{a:5}" ) ), + ot_( fromjson( "{b:4}" ) ) {} + void doIt() const { + client()->update( ns(), o_, u_ ); + } + void check() const { + ASSERT_EQUALS( 2, count() ); + checkOne( u_ ); + checkOne( ot_ ); + } + void reset() const { + deleteAll( ns() ); + insert( ot_, true ); + insert( o_, true ); + } + protected: + BSONObj o_, u_, ot_; + }; + + class Remove : public Base { + public: + Remove() : + o1_( f( "{\"_id\":\"010101010101010101010101\",\"a\":\"b\"}" ) ), + o2_( f( "{\"_id\":\"010101010101010101010102\",\"a\":\"b\"}" ) ), + q_( f( "{\"a\":\"b\"}" ) ) {} + void doIt() const { + client()->remove( ns(), q_ ); + } + void check() const { + ASSERT_EQUALS( 0, count() ); + } + void reset() const { + deleteAll( ns() ); + insert( o1_ ); + insert( o2_ ); + } + protected: + BSONObj o1_, o2_, q_; + }; + + class RemoveOne : public Remove { + void doIt() const { + client()->remove( ns(), q_, true ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + } + }; + + class FailingUpdate : public Base { + public: + FailingUpdate() : + o_( fromjson( "{'_id':1,a:'b'}" ) ), + u_( fromjson( "{'_id':1,c:'d'}" ) ) {} + void doIt() const { + client()->update( ns(), o_, u_ ); + client()->insert( ns(), o_ ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( o_ ); + } + void reset() const { + deleteAll( ns() ); + } + protected: + BSONObj o_, u_; + }; + + class SetNumToStr : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$set" << BSON( "a" << "bcd" ) ) ); + } + void check() const { + ASSERT_EQUALS( 1, count() ); + checkOne( BSON( "_id" << 0 << "a" << "bcd" ) ); + } + void reset() const { + deleteAll( ns() ); + insert( BSON( "_id" << 0 << "a" << 4.0 ) ); + } + }; + + class Push : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$push" << BSON( "a" << 5.0 ) ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4]}" ) ); + } + }; + + class PushUpsert : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$push" << BSON( "a" << 5.0 ) ), true ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4]}" ) ); + } + }; + + class MultiPush : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$push" << BSON( "a" << 5.0 ) << "$push" << BSON( "b.c" << 6.0 ) ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5],b:{c:[6]}}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4]}" ) ); + } + }; + + class EmptyPush : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$push" << BSON( "a" << 5.0 ) ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0}" ) ); + } + }; + + class PushAll : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pushAll:{a:[5.0,6.0]}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5,6]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4]}" ) ); + } + }; + + class PushAllUpsert : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pushAll:{a:[5.0,6.0]}}" ), true ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5,6]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4]}" ) ); + } + }; + + class EmptyPushAll : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pushAll:{a:[5.0,6.0]}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[5,6]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0}" ) ); + } + }; + + class Pull : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$pull" << BSON( "a" << 4.0 ) ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4,5]}" ) ); + } + }; + + class PullNothing : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), BSON( "$pull" << BSON( "a" << 6.0 ) ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4,5]}" ) ); + } + }; + + class PullAll : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pullAll:{a:[4,5]}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[6]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4,5,6]}" ) ); + } + }; + + class Pop : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pop:{a:1}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[4,5]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4,5,6]}" ) ); + } + }; + + class PopReverse : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$pop:{a:-1}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( fromjson( "{'_id':0,a:[5,6]}" ), one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:[4,5,6]}" ) ); + } + }; + + class BitOp : public Base { + public: + void doIt() const { + client()->update( ns(), BSON( "_id" << 0 ), fromjson( "{$bit:{a:{and:2,or:8}}}" ) ); + } + using ReplTests::Base::check; + void check() const { + ASSERT_EQUALS( 1, count() ); + check( BSON( "_id" << 0 << "a" << ( ( 3 & 2 ) | 8 ) ) , one( fromjson( "{'_id':0}" ) ) ); + } + void reset() const { + deleteAll( ns() ); + insert( fromjson( "{'_id':0,a:3}" ) ); + } + }; + + + + } // namespace Idempotence + + class DeleteOpIsIdBased : public Base { + public: + void run() { + insert( BSON( "_id" << 0 << "a" << 10 ) ); + insert( BSON( "_id" << 1 << "a" << 11 ) ); + insert( BSON( "_id" << 3 << "a" << 10 ) ); + client()->remove( ns(), BSON( "a" << 10 ) ); + ASSERT_EQUALS( 1U, client()->count( ns(), BSONObj() ) ); + insert( BSON( "_id" << 0 << "a" << 11 ) ); + insert( BSON( "_id" << 2 << "a" << 10 ) ); + insert( BSON( "_id" << 3 << "a" << 10 ) ); + + applyAllOperations(); + ASSERT_EQUALS( 2U, client()->count( ns(), BSONObj() ) ); + ASSERT( !one( BSON( "_id" << 1 ) ).isEmpty() ); + ASSERT( !one( BSON( "_id" << 2 ) ).isEmpty() ); + } + }; + + class DbIdsTest { + public: + void run() { + setClient( "unittests.repltest.DbIdsTest" ); + + s_.reset( new DbIds( "local.temp.DbIdsTest" ) ); + s_->reset(); + check( false, false, false ); + + s_->set( "a", BSON( "_id" << 4 ), true ); + check( true, false, false ); + s_->set( "a", BSON( "_id" << 4 ), false ); + check( false, false, false ); + + s_->set( "b", BSON( "_id" << 4 ), true ); + check( false, true, false ); + s_->set( "b", BSON( "_id" << 4 ), false ); + check( false, false, false ); + + s_->set( "a", BSON( "_id" << 5 ), true ); + check( false, false, true ); + s_->set( "a", BSON( "_id" << 5 ), false ); + check( false, false, false ); + + s_->set( "a", BSON( "_id" << 4 ), true ); + s_->set( "b", BSON( "_id" << 4 ), true ); + s_->set( "a", BSON( "_id" << 5 ), true ); + check( true, true, true ); + + s_->reset(); + check( false, false, false ); + + s_->set( "a", BSON( "_id" << 4 ), true ); + s_->set( "a", BSON( "_id" << 4 ), true ); + check( true, false, false ); + s_->set( "a", BSON( "_id" << 4 ), false ); + check( false, false, false ); + } + private: + void check( bool one, bool two, bool three ) { + ASSERT_EQUALS( one, s_->get( "a", BSON( "_id" << 4 ) ) ); + ASSERT_EQUALS( two, s_->get( "b", BSON( "_id" << 4 ) ) ); + ASSERT_EQUALS( three, s_->get( "a", BSON( "_id" << 5 ) ) ); + } + dblock lk_; + auto_ptr< DbIds > s_; + }; + + class MemIdsTest { + public: + void run() { + int n = sizeof( BSONObj ) + BSON( "_id" << 4 ).objsize(); + + s_.reset(); + ASSERT_EQUALS( 0, s_.roughSize() ); + ASSERT( !s_.get( "a", BSON( "_id" << 4 ) ) ); + ASSERT( !s_.get( "b", BSON( "_id" << 4 ) ) ); + s_.set( "a", BSON( "_id" << 4 ), true ); + ASSERT_EQUALS( n, s_.roughSize() ); + ASSERT( s_.get( "a", BSON( "_id" << 4 ) ) ); + ASSERT( !s_.get( "b", BSON( "_id" << 4 ) ) ); + s_.set( "a", BSON( "_id" << 4 ), false ); + ASSERT_EQUALS( 0, s_.roughSize() ); + ASSERT( !s_.get( "a", BSON( "_id" << 4 ) ) ); + + s_.set( "a", BSON( "_id" << 4 ), true ); + s_.set( "b", BSON( "_id" << 4 ), true ); + s_.set( "b", BSON( "_id" << 100 ), true ); + s_.set( "b", BSON( "_id" << 101 ), true ); + ASSERT_EQUALS( n * 4, s_.roughSize() ); + } + private: + MemIds s_; + }; + + class IdTrackerTest { + public: + void run() { + setClient( "unittests.repltests.IdTrackerTest" ); + + ASSERT( s_.inMem() ); + s_.reset( 4 * sizeof( BSONObj ) - 1 ); + s_.haveId( "a", BSON( "_id" << 0 ), true ); + s_.haveId( "a", BSON( "_id" << 1 ), true ); + s_.haveId( "b", BSON( "_id" << 0 ), true ); + s_.haveModId( "b", BSON( "_id" << 0 ), true ); + ASSERT( s_.inMem() ); + check(); + s_.mayUpgradeStorage(); + ASSERT( !s_.inMem() ); + check(); + + s_.haveId( "a", BSON( "_id" << 1 ), false ); + ASSERT( !s_.haveId( "a", BSON( "_id" << 1 ) ) ); + s_.haveId( "a", BSON( "_id" << 1 ), true ); + check(); + ASSERT( !s_.inMem() ); + + s_.reset(); + ASSERT( s_.inMem() ); + } + private: + void check() { + ASSERT( s_.haveId( "a", BSON( "_id" << 0 ) ) ); + ASSERT( s_.haveId( "a", BSON( "_id" << 1 ) ) ); + ASSERT( s_.haveId( "b", BSON( "_id" << 0 ) ) ); + ASSERT( s_.haveModId( "b", BSON( "_id" << 0 ) ) ); + } + dblock lk_; + IdTracker s_; + }; + + class All : public Suite { + public: + All() : Suite( "repl" ){ + } + + void setupTests(){ + add< LogBasic >(); + add< Idempotence::InsertTimestamp >(); + add< Idempotence::InsertAutoId >(); + add< Idempotence::InsertWithId >(); + add< Idempotence::InsertTwo >(); + add< Idempotence::InsertTwoIdentical >(); + add< Idempotence::UpdateTimestamp >(); + add< Idempotence::UpdateSameField >(); + add< Idempotence::UpdateSameFieldWithId >(); + add< Idempotence::UpdateSameFieldExplicitId >(); + add< Idempotence::UpdateId >(); + add< Idempotence::UpdateDifferentFieldExplicitId >(); + add< Idempotence::UpsertUpdateNoMods >(); + add< Idempotence::UpsertInsertNoMods >(); + add< Idempotence::UpdateSet >(); + add< Idempotence::UpdateInc >(); + add< Idempotence::UpsertInsertIdMod >(); + add< Idempotence::UpsertInsertSet >(); + add< Idempotence::UpsertInsertInc >(); + add< Idempotence::MultiInc >(); + // Don't worry about this until someone wants this functionality. +// add< Idempotence::UpdateWithoutPreexistingId >(); + add< Idempotence::Remove >(); + add< Idempotence::RemoveOne >(); + add< Idempotence::FailingUpdate >(); + add< Idempotence::SetNumToStr >(); + add< Idempotence::Push >(); + add< Idempotence::PushUpsert >(); + add< Idempotence::MultiPush >(); + add< Idempotence::EmptyPush >(); + add< Idempotence::PushAll >(); + add< Idempotence::PushAllUpsert >(); + add< Idempotence::EmptyPushAll >(); + add< Idempotence::Pull >(); + add< Idempotence::PullNothing >(); + add< Idempotence::PullAll >(); + add< Idempotence::Pop >(); + add< Idempotence::PopReverse >(); + add< Idempotence::BitOp >(); + add< DeleteOpIsIdBased >(); + add< DbIdsTest >(); + add< MemIdsTest >(); + add< IdTrackerTest >(); + } + } myall; + +} // namespace ReplTests + diff --git a/dbtests/sharding.cpp b/dbtests/sharding.cpp new file mode 100644 index 0000000..c7c072a --- /dev/null +++ b/dbtests/sharding.cpp @@ -0,0 +1,56 @@ +// sharding.cpp : some unit tests for sharding internals + +/** + * Copyright (C) 2009 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#include "dbtests.h" + +#include "../client/parallel.h" + +namespace ShardingTests { + + namespace serverandquerytests { + class test1 { + public: + void run(){ + ServerAndQuery a( "foo:1" , BSON( "a" << GT << 0 << LTE << 100 ) ); + ServerAndQuery b( "foo:1" , BSON( "a" << GT << 200 << LTE << 1000 ) ); + + ASSERT( a < b ); + ASSERT( ! ( b < a ) ); + + set<ServerAndQuery> s; + s.insert( a ); + s.insert( b ); + + ASSERT_EQUALS( (unsigned int)2 , s.size() ); + } + }; + } + + class All : public Suite { + public: + All() : Suite( "sharding" ){ + } + + void setupTests(){ + add< serverandquerytests::test1 >(); + } + } myall; + +} diff --git a/dbtests/socktests.cpp b/dbtests/socktests.cpp new file mode 100644 index 0000000..c263f2e --- /dev/null +++ b/dbtests/socktests.cpp @@ -0,0 +1,44 @@ +// socktests.cpp : sock.{h,cpp} unit tests. +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../util/sock.h" + +#include "dbtests.h" + +namespace SockTests { + + class HostByName { + public: + void run() { + ASSERT_EQUALS( "127.0.0.1", hostbyname( "localhost" ) ); + ASSERT_EQUALS( "127.0.0.1", hostbyname( "127.0.0.1" ) ); + } + }; + + class All : public Suite { + public: + All() : Suite( "sock" ){} + void setupTests(){ + add< HostByName >(); + } + } myall; + +} // namespace SockTests + diff --git a/dbtests/test.vcproj b/dbtests/test.vcproj new file mode 100644 index 0000000..4582ef2 --- /dev/null +++ b/dbtests/test.vcproj @@ -0,0 +1,1931 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="test"
+ ProjectGUID="{215B2D68-0A70-4D10-8E75-B33010C62A91}"
+ RootNamespace="dbtests"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ UseOfATL="0"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib Psapi.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ IgnoreAllDefaultLibraries="false"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="OLDJS;STATIC_JS_API;XP_WIN;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="release_nojni|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0";"c:\program files\java\jdk\include";"c:\program files\java\jdk\include\win32""
+ PreprocessorDefinitions="NOJNI;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug Recstore|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ UseOfMFC="0"
+ UseOfATL="0"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="..\..\js\src;"..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="_RECSTORE;OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ IgnoreAllDefaultLibraries="false"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="misc and third party"
+ >
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\config\auto_link.hpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\db.rc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\js\js\Debug\js.lib"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\js\js\Release\js.lib"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ ExcludedFromBuild="true"
+ >
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcrecpp.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcrecpp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\SConstruct"
+ >
+ </File>
+ <File
+ RelativePath="..\targetver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\version.hpp"
+ >
+ </File>
+ <Filter
+ Name="pcre"
+ >
+ <File
+ RelativePath="..\pcre-7.4\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre.h"
+ >
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_chartables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_compile.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_config.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_dfa_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_fullinfo.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_get.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_globals.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_info.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_maketables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_newline.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_ord2utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_refcount.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_scanner.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_stringpiece.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_study.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_tables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_try_flipped.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_ucp_searchfuncs.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_valid_utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_version.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcre_xclass.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\pcre-7.4\pcreposix.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="storage related"
+ >
+ <File
+ RelativePath="..\db\rec.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\reccache.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\reccache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\reci.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\recstore.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\storage.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\storage.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\client\connpool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\connpool.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\model.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\quorum.cpp"
+ >
+ </File>
+ <Filter
+ Name="btree related"
+ >
+ <File
+ RelativePath="..\db\btree.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\btree.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\btreecursor.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="db"
+ >
+ <File
+ RelativePath="..\db\clientcursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\cmdline.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\commands.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\concurrency.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\curop.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\cursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\database.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\db.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbhelpers.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbinfo.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbmessage.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\extsort.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\introspect.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\jsobj.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\json.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\matcher.h"
+ >
+ </File>
+ <File
+ RelativePath="..\grid\message.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\minilex.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\namespace.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\pdfile.h"
+ >
+ </File>
+ <File
+ RelativePath="..\grid\protocol.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\query.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\queryoptimizer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\queryutil.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\repl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\replset.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\resource.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\scanandorder.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\security.h"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.h"
+ >
+ </File>
+ <Filter
+ Name="cpp"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath="..\db\client.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\clientcursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\cloner.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\cursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\database.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbcommands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbeval.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbhelpers.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbstats.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\extsort.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\index.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\instance.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\introspect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\jsobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\json.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\lasterror.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\matcher.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap_win.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\namespace.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\nonce.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\pdfile.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\query.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\queryoptimizer.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\repl.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\security.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\security_commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\db\tests.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\top.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\update.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="util"
+ >
+ <File
+ RelativePath="..\util\builder.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\file.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\goodies.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\hashtab.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\lasterror.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\log.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\lruishmap.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.hpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\miniwebserver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\sock.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\unittest.h"
+ >
+ </File>
+ <Filter
+ Name="cpp"
+ >
+ <File
+ RelativePath="..\util\assert_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\base64.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\httpclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ PrecompiledHeaderThrough=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\util\md5main.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\message.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\miniwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\mmap.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\ntservice.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\processinfo_win32.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\sock.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\thread_pool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\util.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="shard"
+ >
+ <File
+ RelativePath="..\s\d_logic.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="scripting"
+ >
+ <File
+ RelativePath="..\scripting\engine.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\scripting\engine_spidermonkey.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\shell\mongo_vstudio.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="dbtests"
+ >
+ <File
+ RelativePath=".\basictests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\btreetests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\clienttests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\cursortests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dbtests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\framework.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\jsobjtests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\jsontests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\jstests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\matchertests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\namespacetests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\pairingtests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\pdfiletests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\queryoptimizertests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\querytests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\repltests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\socktests.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\threadedtests.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ DisableSpecificWarnings="4180"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\updatetests.cpp"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/dbtests/test.vcxproj b/dbtests/test.vcxproj new file mode 100644 index 0000000..efbf2d0 --- /dev/null +++ b/dbtests/test.vcxproj @@ -0,0 +1,586 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug Recstore|Win32">
+ <Configuration>Debug Recstore</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="release_nojni|Win32">
+ <Configuration>release_nojni</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{215B2D68-0A70-4D10-8E75-B33010C62A91}</ProjectGuid>
+ <RootNamespace>dbtests</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <UseOfAtl>false</UseOfAtl>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <UseOfAtl>false</UseOfAtl>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.21006.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">true</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>OLDJS;STATIC_JS_API;XP_WIN;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\program files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;c:\program files\java\jdk\include;c:\program files\java\jdk\include\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>NOJNI;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\program files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\js\src;..\pcre-7.4;c:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_RECSTORE;OLDJS;STATIC_JS_API;XP_WIN;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="..\pcre-7.4\pcrecpp.h" />
+ <ClInclude Include="..\targetver.h" />
+ <ClInclude Include="..\pcre-7.4\config.h" />
+ <ClInclude Include="..\pcre-7.4\pcre.h" />
+ <ClInclude Include="..\db\rec.h" />
+ <ClInclude Include="..\db\reccache.h" />
+ <ClInclude Include="..\db\reci.h" />
+ <ClInclude Include="..\db\recstore.h" />
+ <ClInclude Include="..\db\storage.h" />
+ <ClInclude Include="..\client\connpool.h" />
+ <ClInclude Include="..\client\dbclient.h" />
+ <ClInclude Include="..\client\model.h" />
+ <ClInclude Include="..\db\btree.h" />
+ <ClInclude Include="..\db\clientcursor.h" />
+ <ClInclude Include="..\db\cmdline.h" />
+ <ClInclude Include="..\db\commands.h" />
+ <ClInclude Include="..\db\concurrency.h" />
+ <ClInclude Include="..\db\curop.h" />
+ <ClInclude Include="..\db\cursor.h" />
+ <ClInclude Include="..\db\database.h" />
+ <ClInclude Include="..\db\db.h" />
+ <ClInclude Include="..\db\dbhelpers.h" />
+ <ClInclude Include="..\db\dbinfo.h" />
+ <ClInclude Include="..\db\dbmessage.h" />
+ <ClInclude Include="..\db\introspect.h" />
+ <ClInclude Include="..\db\jsobj.h" />
+ <ClInclude Include="..\db\json.h" />
+ <ClInclude Include="..\db\matcher.h" />
+ <ClInclude Include="..\grid\message.h" />
+ <ClInclude Include="..\db\minilex.h" />
+ <ClInclude Include="..\db\namespace.h" />
+ <ClInclude Include="..\db\pdfile.h" />
+ <ClInclude Include="..\grid\protocol.h" />
+ <ClInclude Include="..\db\query.h" />
+ <ClInclude Include="..\db\queryoptimizer.h" />
+ <ClInclude Include="..\db\repl.h" />
+ <ClInclude Include="..\db\replset.h" />
+ <ClInclude Include="..\db\resource.h" />
+ <ClInclude Include="..\db\scanandorder.h" />
+ <ClInclude Include="..\db\security.h" />
+ <ClInclude Include="..\stdafx.h" />
+ <ClInclude Include="..\util\builder.h" />
+ <ClInclude Include="..\util\file.h" />
+ <ClInclude Include="..\util\goodies.h" />
+ <ClInclude Include="..\util\hashtab.h" />
+ <ClInclude Include="..\db\lasterror.h" />
+ <ClInclude Include="..\util\log.h" />
+ <ClInclude Include="..\util\lruishmap.h" />
+ <ClInclude Include="..\util\md5.h" />
+ <ClInclude Include="..\util\md5.hpp" />
+ <ClInclude Include="..\util\miniwebserver.h" />
+ <ClInclude Include="..\util\mmap.h" />
+ <ClInclude Include="..\util\sock.h" />
+ <ClInclude Include="..\util\unittest.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\db\db.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <CustomBuild Include="..\..\js\js\Release\js.lib">
+ <FileType>Document</FileType>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+ </CustomBuild>
+ <CustomBuild Include="..\..\js\js\Debug\js.lib">
+ <FileType>Document</FileType>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+ </CustomBuild>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\pcre-7.4\pcrecpp.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_chartables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_compile.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_config.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_dfa_exec.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_exec.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_fullinfo.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_get.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_globals.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_info.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_maketables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_newline.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_ord2utf8.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_refcount.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_scanner.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_stringpiece.cc">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_study.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_tables.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_try_flipped.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_ucp_searchfuncs.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_valid_utf8.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_version.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcre_xclass.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\pcre-7.4\pcreposix.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\db\reccache.cpp" />
+ <ClCompile Include="..\db\storage.cpp" />
+ <ClCompile Include="..\client\connpool.cpp" />
+ <ClCompile Include="..\client\dbclient.cpp" />
+ <ClCompile Include="..\db\btree.cpp" />
+ <ClCompile Include="..\db\btreecursor.cpp" />
+ <ClCompile Include="..\db\queryutil.cpp" />
+ <ClCompile Include="..\db\client.cpp" />
+ <ClCompile Include="..\db\clientcursor.cpp" />
+ <ClCompile Include="..\db\cloner.cpp" />
+ <ClCompile Include="..\db\commands.cpp" />
+ <ClCompile Include="..\db\cursor.cpp" />
+ <ClCompile Include="..\db\dbcommands.cpp" />
+ <ClCompile Include="..\db\dbeval.cpp" />
+ <ClCompile Include="..\db\dbhelpers.cpp" />
+ <ClCompile Include="..\db\dbinfo.cpp" />
+ <ClCompile Include="..\db\dbwebserver.cpp" />
+ <ClCompile Include="..\db\extsort.cpp" />
+ <ClCompile Include="..\db\instance.cpp" />
+ <ClCompile Include="..\db\introspect.cpp" />
+ <ClCompile Include="..\db\jsobj.cpp" />
+ <ClCompile Include="..\db\json.cpp" />
+ <ClCompile Include="..\db\lasterror.cpp" />
+ <ClCompile Include="..\db\matcher.cpp" />
+ <ClCompile Include="..\util\mmap_win.cpp" />
+ <ClCompile Include="..\db\namespace.cpp" />
+ <ClCompile Include="..\db\nonce.cpp" />
+ <ClCompile Include="..\db\pdfile.cpp" />
+ <ClCompile Include="..\db\query.cpp" />
+ <ClCompile Include="..\db\queryoptimizer.cpp" />
+ <ClCompile Include="..\db\repl.cpp" />
+ <ClCompile Include="..\db\security.cpp" />
+ <ClCompile Include="..\db\security_commands.cpp" />
+ <ClCompile Include="..\stdafx.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\db\tests.cpp" />
+ <ClCompile Include="..\db\update.cpp" />
+ <ClCompile Include="..\util\assert_util.cpp" />
+ <ClCompile Include="..\util\background.cpp" />
+ <ClCompile Include="..\util\base64.cpp" />
+ <ClCompile Include="..\util\httpclient.cpp" />
+ <ClCompile Include="..\util\md5.c">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeaderFile>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='release_nojni|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\util\md5main.cpp" />
+ <ClCompile Include="..\util\message.cpp" />
+ <ClCompile Include="..\util\miniwebserver.cpp" />
+ <ClCompile Include="..\util\mmap.cpp" />
+ <ClCompile Include="..\util\ntservice.cpp" />
+ <ClCompile Include="..\util\processinfo_none.cpp" />
+ <ClCompile Include="..\util\sock.cpp" />
+ <ClCompile Include="..\util\util.cpp" />
+ <ClCompile Include="..\s\d_logic.cpp" />
+ <ClCompile Include="..\scripting\engine.cpp" />
+ <ClCompile Include="..\scripting\engine_spidermonkey.cpp" />
+ <ClCompile Include="..\shell\mongo_vstudio.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ </PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="basictests.cpp" />
+ <ClCompile Include="btreetests.cpp" />
+ <ClCompile Include="clienttests.cpp" />
+ <ClCompile Include="cursortests.cpp" />
+ <ClCompile Include="dbtests.cpp" />
+ <ClCompile Include="framework.cpp" />
+ <ClCompile Include="jsobjtests.cpp" />
+ <ClCompile Include="jsontests.cpp" />
+ <ClCompile Include="jstests.cpp" />
+ <ClCompile Include="matchertests.cpp" />
+ <ClCompile Include="namespacetests.cpp" />
+ <ClCompile Include="pairingtests.cpp" />
+ <ClCompile Include="pdfiletests.cpp" />
+ <ClCompile Include="queryoptimizertests.cpp" />
+ <ClCompile Include="querytests.cpp" />
+ <ClCompile Include="repltests.cpp" />
+ <ClCompile Include="socktests.cpp" />
+ <ClCompile Include="updatetests.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\SConstruct" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/dbtests/threadedtests.cpp b/dbtests/threadedtests.cpp new file mode 100644 index 0000000..f3ebe39 --- /dev/null +++ b/dbtests/threadedtests.cpp @@ -0,0 +1,135 @@ +// threadedtests.cpp - Tests for threaded code +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../util/mvar.h" +#include "../util/thread_pool.h" +#include <boost/thread.hpp> +#include <boost/bind.hpp> + +#include "dbtests.h" + +namespace ThreadedTests { + + template <int nthreads_param=10> + class ThreadedTest{ + public: + virtual void setup() {} //optional + virtual void subthread() = 0; + virtual void validate() = 0; + + static const int nthreads = nthreads_param; + + void run(){ + setup(); + + launch_subthreads(nthreads); + + validate(); + } + + virtual ~ThreadedTest() {}; // not necessary, but makes compilers happy + + private: + void launch_subthreads(int remaining){ + if (!remaining) return; + + boost::thread athread(boost::bind(&ThreadedTest::subthread, this)); + + launch_subthreads(remaining - 1); + + athread.join(); + } + }; + + // Tested with up to 30k threads + class IsWrappingIntAtomic : public ThreadedTest<> { + static const int iterations = 1000000; + WrappingInt target; + + void subthread(){ + for(int i=0; i < iterations; i++){ + //target.x++; // verified to fail with this version + target.atomicIncrement(); + } + } + void validate(){ + ASSERT_EQUALS(target.x , unsigned(nthreads * iterations)); + } + }; + + class MVarTest : public ThreadedTest<> { + static const int iterations = 10000; + MVar<int> target; + + public: + MVarTest() : target(0) {} + void subthread(){ + for(int i=0; i < iterations; i++){ + int val = target.take(); +#if BOOST_VERSION >= 103500 + //increase chances of catching failure + boost::this_thread::yield(); +#endif + target.put(val+1); + } + } + void validate(){ + ASSERT_EQUALS(target.take() , nthreads * iterations); + } + }; + + class ThreadPoolTest{ + static const int iterations = 10000; + static const int nThreads = 8; + + WrappingInt counter; + void increment(int n){ + for (int i=0; i<n; i++){ + counter.atomicIncrement(); + } + } + + public: + void run(){ + ThreadPool tp(nThreads); + + for (int i=0; i < iterations; i++){ + tp.schedule(&WrappingInt::atomicIncrement, &counter); + tp.schedule(&ThreadPoolTest::increment, this, 2); + } + + tp.join(); + + ASSERT(counter == (unsigned)(iterations * 3)); + } + }; + + class All : public Suite { + public: + All() : Suite( "threading" ){ + } + + void setupTests(){ + add< IsWrappingIntAtomic >(); + add< MVarTest >(); + add< ThreadPoolTest >(); + } + } myall; +} diff --git a/dbtests/updatetests.cpp b/dbtests/updatetests.cpp new file mode 100644 index 0000000..a9c4b1e --- /dev/null +++ b/dbtests/updatetests.cpp @@ -0,0 +1,770 @@ +// updatetests.cpp : unit tests relating to update requests +// + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../db/query.h" + +#include "../db/db.h" +#include "../db/instance.h" +#include "../db/json.h" +#include "../db/lasterror.h" +#include "../db/update.h" + +#include "dbtests.h" + +namespace UpdateTests { + + class ClientBase { + public: + // NOTE: Not bothering to backup the old error record. + ClientBase() { + mongo::lastError.reset( new LastError() ); + } + ~ClientBase() { + mongo::lastError.release(); + } + protected: + static void insert( const char *ns, BSONObj o ) { + client_.insert( ns, o ); + } + static void update( const char *ns, BSONObj q, BSONObj o, bool upsert = 0 ) { + client_.update( ns, Query( q ), o, upsert ); + } + static bool error() { + return !client_.getPrevError().getField( "err" ).isNull(); + } + DBDirectClient &client() const { return client_; } + private: + static DBDirectClient client_; + }; + DBDirectClient ClientBase::client_; + + class Fail : public ClientBase { + public: + virtual ~Fail() {} + void run() { + prep(); + ASSERT( !error() ); + doIt(); + ASSERT( error() ); + } + protected: + const char *ns() { return "unittests.UpdateTests_Fail"; } + virtual void prep() { + insert( ns(), fromjson( "{a:1}" ) ); + } + virtual void doIt() = 0; + }; + + class ModId : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{$set:{'_id':4}}" ) ); + } + }; + + class ModNonmodMix : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{$set:{a:4},z:3}" ) ); + } + }; + + class InvalidMod : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{$awk:{a:4}}" ) ); + } + }; + + class ModNotFirst : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{z:3,$set:{a:4}}" ) ); + } + }; + + class ModDuplicateFieldSpec : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{$set:{a:4},$inc:{a:1}}" ) ); + } + }; + + class IncNonNumber : public Fail { + void doIt() { + update( ns(), BSONObj(), fromjson( "{$inc:{a:'d'}}" ) ); + } + }; + + class PushAllNonArray : public Fail { + void doIt() { + insert( ns(), fromjson( "{a:[1]}" ) ); + update( ns(), BSONObj(), fromjson( "{$pushAll:{a:'d'}}" ) ); + } + }; + + class PullAllNonArray : public Fail { + void doIt() { + insert( ns(), fromjson( "{a:[1]}" ) ); + update( ns(), BSONObj(), fromjson( "{$pullAll:{a:'d'}}" ) ); + } + }; + + class IncTargetNonNumber : public Fail { + void doIt() { + insert( ns(), BSON( "a" << "a" ) ); + update( ns(), BSON( "a" << "a" ), fromjson( "{$inc:{a:1}}" ) ); + } + }; + + class SetBase : public ClientBase { + public: + ~SetBase() { + client().dropCollection( ns() ); + } + protected: + const char *ns() { return "unittests.updatetests.SetBase"; } + }; + + class SetNum : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << 1 ) ); + client().update( ns(), BSON( "a" << 1 ), BSON( "$set" << BSON( "a" << 4 ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a" << 4 ) ).isEmpty() ); + } + }; + + class SetString : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << "b" ) ); + client().update( ns(), BSON( "a" << "b" ), BSON( "$set" << BSON( "a" << "c" ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a" << "c" ) ).isEmpty() ); + } + }; + + class SetStringDifferentLength : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << "b" ) ); + client().update( ns(), BSON( "a" << "b" ), BSON( "$set" << BSON( "a" << "cd" ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a" << "cd" ) ).isEmpty() ); + } + }; + + class SetStringToNum : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << "b" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 5 ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a" << 5 ) ).isEmpty() ); + } + }; + + class SetStringToNumInPlace : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << "bcd" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 5.0 ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a" << 5.0 ) ).isEmpty() ); + } + }; + + class ModDotted : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{a:{b:4}}" ) ); + client().update( ns(), Query(), BSON( "$inc" << BSON( "a.b" << 10 ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a.b" << 14 ) ).isEmpty() ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 55 ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a.b" << 55 ) ).isEmpty() ); + } + }; + + class SetInPlaceDotted : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{a:{b:'cdef'}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "llll" ) ) ); + ASSERT( !client().findOne( ns(), BSON( "a.b" << "llll" ) ).isEmpty() ); + } + }; + + class SetRecreateDotted : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:'cdef'}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); + ASSERT( client().findOne( ns(), BSON( "a.b" << "lllll" ) ).woCompare( fromjson( "{'_id':0,a:{b:'lllll'}}" ) ) == 0 ); + } + }; + + class SetMissingDotted : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), BSONObj(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); + ASSERT( client().findOne( ns(), BSON( "a.b" << "lllll" ) ).woCompare( fromjson( "{'_id':0,a:{b:'lllll'}}" ) ) == 0 ); + } + }; + + class SetAdjacentDotted : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{c:4}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); + ASSERT_EQUALS( client().findOne( ns(), BSON( "a.b" << "lllll" ) ) , fromjson( "{'_id':0,a:{b:'lllll',c:4}}" ) ); + } + }; + + class IncMissing : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$inc" << BSON( "f" << 3.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:3}" ) ) == 0 ); + } + }; + + class MultiInc : public SetBase { + public: + + string s(){ + stringstream ss; + auto_ptr<DBClientCursor> cc = client().query( ns() , Query().sort( BSON( "_id" << 1 ) ) ); + bool first = true; + while ( cc->more() ){ + if ( first ) first = false; + else ss << ","; + + BSONObj o = cc->next(); + ss << o["x"].numberInt(); + } + return ss.str(); + } + + void run(){ + client().insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); + client().insert( ns(), BSON( "_id" << 2 << "x" << 5 ) ); + + ASSERT_EQUALS( "1,5" , s() ); + + client().update( ns() , BSON( "_id" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "2,5" , s() ); + + client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "3,5" , s() ); + + client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) , false , true ); + ASSERT_EQUALS( "4,6" , s() ); + + } + }; + + class UnorderedNewSet : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "f.g.h" << 3.0 << "f.g.a" << 2.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:{g:{a:2,h:3}}}" ) ) == 0 ); + } + }; + + class UnorderedNewSetAdjacent : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), BSONObj(), BSON( "$set" << BSON( "f.g.h.b" << 3.0 << "f.g.a.b" << 2.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:{g:{a:{b:2},h:{b:3}}}}" ) ) == 0 ); + } + }; + + class ArrayEmbeddedSet : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,z:[4,'b']}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "z.0" << "a" ) ) ); + ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,z:['a','b']}" ) ); + } + }; + + class AttemptEmbedInExistingNum : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:1}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 1 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:1}" ) ) == 0 ); + } + }; + + class AttemptEmbedConflictsWithOtherSet : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 2 << "a.b" << 1 ) ) ); + ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0}" ) ); + } + }; + + class ModMasksEmbeddedConflict : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:2}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 2 << "a.b" << 1 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:2}}" ) ) == 0 ); + } + }; + + class ModOverwritesExistingObject : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:2}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << BSON( "c" << 2 ) ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{c:2}}" ) ) == 0 ); + } + }; + + class InvalidEmbeddedSet : public Fail { + public: + virtual void doIt() { + client().update( ns(), Query(), BSON( "$set" << BSON( "a." << 1 ) ) ); + } + }; + + class UpsertMissingEmbedded : public SetBase { + public: + void run() { + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 1 ) ), true ); + ASSERT( !client().findOne( ns(), QUERY( "a.b" << 1 ) ).isEmpty() ); + } + }; + + class Push : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); + ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,5]}" ) ); + } + }; + + class PushInvalidEltType : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:1}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:1}" ) ) == 0 ); + } + }; + + class PushConflictsWithOtherMod : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 1 ) <<"$push" << BSON( "a" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[1]}" ) ) == 0 ); + } + }; + + class PushFromNothing : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); + ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[5]}" ) ); + } + }; + + class PushFromEmpty : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[5]}" ) ) == 0 ); + } + }; + + class PushInsideNothing : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a.b" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:[5]}}" ) ) == 0 ); + } + }; + + class CantPushInsideOtherMod : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a" << BSONObj() ) << "$push" << BSON( "a.b" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0}" ) ) == 0 ); + } + }; + + class CantPushTwice : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 4 ) << "$push" << BSON( "a" << 5 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[]}" ) ) == 0 ); + } + }; + + class SetEncapsulationConflictsWithExistingType : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b.c" << 4.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); + } + }; + + class CantPushToParent : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); + client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 4.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); + } + }; + + class CantIncParent : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); + client().update( ns(), Query(), BSON( "$inc" << BSON( "a" << 4.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); + } + }; + + class DontDropEmpty : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:{}}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.c" << 4.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:{},c:4}}" ) ) == 0 ); + } + }; + + class InsertInEmpty : public SetBase { + public: + void run() { + client().insert( ns(), fromjson( "{'_id':0,a:{b:{}}}" ) ); + client().update( ns(), Query(), BSON( "$set" << BSON( "a.b.f" << 4.0 ) ) ); + ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:{f:4}}}" ) ) == 0 ); + } + }; + + class IndexParentOfMod : public SetBase { + public: + void run() { + client().ensureIndex( ns(), BSON( "a" << 1 ) ); + client().insert( ns(), fromjson( "{'_id':0}" ) ); + client().update( ns(), Query(), fromjson( "{$set:{'a.b':4}}" ) ); + ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), Query() ) ); + ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), fromjson( "{'a.b':4}" ) ) ); // make sure the index works + } + }; + + class IndexModSet : public SetBase { + public: + void run() { + client().ensureIndex( ns(), BSON( "a.b" << 1 ) ); + client().insert( ns(), fromjson( "{'_id':0,a:{b:3}}" ) ); + client().update( ns(), Query(), fromjson( "{$set:{'a.b':4}}" ) ); + ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), Query() ) ); + ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), fromjson( "{'a.b':4}" ) ) ); // make sure the index works + } + }; + + + class PreserveIdWithIndex : public SetBase { // Not using $set, but base class is still useful + public: + void run() { + client().insert( ns(), BSON( "_id" << 55 << "i" << 5 ) ); + client().update( ns(), BSON( "i" << 5 ), BSON( "i" << 6 ) ); + ASSERT( !client().findOne( ns(), Query( BSON( "_id" << 55 ) ).hint + ( "{\"_id\":ObjectId(\"000000000000000000000000\")}" ) ).isEmpty() ); + } + }; + + class CheckNoMods : public SetBase { + public: + void run() { + client().update( ns(), BSONObj(), BSON( "i" << 5 << "$set" << BSON( "q" << 3 ) ), true ); + ASSERT( error() ); + } + }; + + class UpdateMissingToNull : public SetBase { + public: + void run() { + client().insert( ns(), BSON( "a" << 5 ) ); + client().update( ns(), BSON( "a" << 5 ), fromjson( "{$set:{b:null}}" ) ); + ASSERT_EQUALS( jstNULL, client().findOne( ns(), QUERY( "a" << 5 ) ).getField( "b" ).type() ); + } + }; + + namespace ModSetTests { + + class internal1 { + public: + void run(){ + BSONObj b = BSON( "$inc" << BSON( "x" << 1 << "a.b" << 1 ) ); + ModSet m; + m.getMods( b ); + + ASSERT( m.haveModForField( "x" ) ); + ASSERT( m.haveModForField( "a.b" ) ); + ASSERT( ! m.haveModForField( "y" ) ); + ASSERT( ! m.haveModForField( "a.c" ) ); + ASSERT( ! m.haveModForField( "a" ) ); + + ASSERT( m.haveConflictingMod( "x" ) ); + ASSERT( m.haveConflictingMod( "a" ) ); + ASSERT( m.haveConflictingMod( "a.b" ) ); + ASSERT( ! m.haveConflictingMod( "a.bc" ) ); + ASSERT( ! m.haveConflictingMod( "a.c" ) ); + ASSERT( ! m.haveConflictingMod( "a.a" ) ); + } + }; + + class Base { + public: + + virtual ~Base(){} + + + void test( BSONObj morig , BSONObj in , BSONObj wanted ){ + BSONObj m = morig.copy(); + ModSet set; + set.getMods( m ); + + BSONObj out = set.createNewFromMods( in ); + ASSERT_EQUALS( wanted , out ); + } + }; + + class inc1 : public Base { + public: + void run(){ + BSONObj m = BSON( "$inc" << BSON( "x" << 1 ) ); + test( m , BSON( "x" << 5 ) , BSON( "x" << 6 ) ); + test( m , BSON( "a" << 5 ) , BSON( "a" << 5 << "x" << 1 ) ); + test( m , BSON( "z" << 5 ) , BSON( "x" << 1 << "z" << 5 ) ); + } + }; + + class inc2 : public Base { + public: + void run(){ + BSONObj m = BSON( "$inc" << BSON( "a.b" << 1 ) ); + test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 ) ) ); + test( m , BSON( "a" << BSON( "b" << 2 ) ) , BSON( "a" << BSON( "b" << 3 ) ) ); + + m = BSON( "$inc" << BSON( "a.b" << 1 << "a.c" << 1 ) ); + test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 << "c" << 1 ) ) ); + + + } + }; + + class set1 : public Base { + public: + void run(){ + test( BSON( "$set" << BSON( "x" << 17 ) ) , BSONObj() , BSON( "x" << 17 ) ); + test( BSON( "$set" << BSON( "x" << 17 ) ) , BSON( "x" << 5 ) , BSON( "x" << 17 ) ); + + test( BSON( "$set" << BSON( "x.a" << 17 ) ) , BSON( "z" << 5 ) , BSON( "x" << BSON( "a" << 17 )<< "z" << 5 ) ); + } + }; + + class push1 : public Base { + public: + void run(){ + test( BSON( "$push" << BSON( "a" << 5 ) ) , fromjson( "{a:[1]}" ) , fromjson( "{a:[1,5]}" ) ); + } + }; + + }; + + namespace basic { + class Base : public ClientBase { + virtual const char * ns() = 0; + virtual void dotest() = 0; + + protected: + + void test( const char* initial , const char* mod , const char* after ){ + test( fromjson( initial ) , fromjson( mod ) , fromjson( after ) ); + } + + + void test( const BSONObj& initial , const BSONObj& mod , const BSONObj& after ){ + client().dropCollection( ns() ); + client().insert( ns() , initial ); + client().update( ns() , BSONObj() , mod ); + ASSERT_EQUALS( after , client().findOne( ns(), BSONObj() )); + client().dropCollection( ns() ); + } + + public: + + Base(){} + virtual ~Base(){ + } + + void run(){ + client().dropCollection( ns() ); + + dotest(); + + client().dropCollection( ns() ); + } + }; + + class SingleTest : public Base { + virtual BSONObj initial() = 0; + virtual BSONObj mod() = 0; + virtual BSONObj after() = 0; + + void dotest(){ + test( initial() , mod() , after() ); + } + + }; + + class inc1 : public SingleTest { + virtual BSONObj initial(){ + return BSON( "_id" << 1 << "x" << 1 ); + } + virtual BSONObj mod(){ + return BSON( "$inc" << BSON( "x" << 2 ) ); + } + virtual BSONObj after(){ + return BSON( "_id" << 1 << "x" << 3 ); + } + virtual const char * ns(){ + return "unittests.inc1"; + } + + }; + + class bit1 : public Base { + const char * ns(){ + return "unittests.bit1"; + } + void dotest(){ + test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "and" << 2 ) ) ) , BSON( "_id" << 1 << "x" << ( 3 & 2 ) ) ); + test( BSON( "_id" << 1 << "x" << 1 ) , BSON( "$bit" << BSON( "x" << BSON( "or" << 4 ) ) ) , BSON( "_id" << 1 << "x" << ( 1 | 4 ) ) ); + test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "and" << 2 << "or" << 8 ) ) ) , BSON( "_id" << 1 << "x" << ( ( 3 & 2 ) | 8 ) ) ); + test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "or" << 2 << "and" << 8 ) ) ) , BSON( "_id" << 1 << "x" << ( ( 3 | 2 ) & 8 ) ) ); + + } + }; + + class unset : public Base { + const char * ns(){ + return "unittests.unset"; + } + void dotest(){ + test( "{_id:1,x:1}" , "{$unset:{x:1}}" , "{_id:1}" ); + } + }; + + class setswitchint : public Base { + const char * ns(){ + return "unittests.int1"; + } + void dotest(){ + test( BSON( "_id" << 1 << "x" << 1 ) , BSON( "$set" << BSON( "x" << 5.6 ) ) , BSON( "_id" << 1 << "x" << 5.6 ) ); + test( BSON( "_id" << 1 << "x" << 5.6 ) , BSON( "$set" << BSON( "x" << 1 ) ) , BSON( "_id" << 1 << "x" << 1 ) ); + } + }; + + + }; + + class All : public Suite { + public: + All() : Suite( "update" ) { + } + void setupTests(){ + add< ModId >(); + add< ModNonmodMix >(); + add< InvalidMod >(); + add< ModNotFirst >(); + add< ModDuplicateFieldSpec >(); + add< IncNonNumber >(); + add< PushAllNonArray >(); + add< PullAllNonArray >(); + add< IncTargetNonNumber >(); + add< SetNum >(); + add< SetString >(); + add< SetStringDifferentLength >(); + add< SetStringToNum >(); + add< SetStringToNumInPlace >(); + add< ModDotted >(); + add< SetInPlaceDotted >(); + add< SetRecreateDotted >(); + add< SetMissingDotted >(); + add< SetAdjacentDotted >(); + add< IncMissing >(); + add< MultiInc >(); + add< UnorderedNewSet >(); + add< UnorderedNewSetAdjacent >(); + add< ArrayEmbeddedSet >(); + add< AttemptEmbedInExistingNum >(); + add< AttemptEmbedConflictsWithOtherSet >(); + add< ModMasksEmbeddedConflict >(); + add< ModOverwritesExistingObject >(); + add< InvalidEmbeddedSet >(); + add< UpsertMissingEmbedded >(); + add< Push >(); + add< PushInvalidEltType >(); + add< PushConflictsWithOtherMod >(); + add< PushFromNothing >(); + add< PushFromEmpty >(); + add< PushInsideNothing >(); + add< CantPushInsideOtherMod >(); + add< CantPushTwice >(); + add< SetEncapsulationConflictsWithExistingType >(); + add< CantPushToParent >(); + add< CantIncParent >(); + add< DontDropEmpty >(); + add< InsertInEmpty >(); + add< IndexParentOfMod >(); + add< IndexModSet >(); + add< PreserveIdWithIndex >(); + add< CheckNoMods >(); + add< UpdateMissingToNull >(); + + add< ModSetTests::internal1 >(); + add< ModSetTests::inc1 >(); + add< ModSetTests::inc2 >(); + add< ModSetTests::set1 >(); + add< ModSetTests::push1 >(); + + add< basic::inc1 >(); + add< basic::bit1 >(); + add< basic::unset >(); + add< basic::setswitchint >(); + } + } myall; + +} // namespace UpdateTests + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..3c6963c --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +mongodb (1.3.1) unstable; urgency=low + + * Initial release + + -- Kristina Chodorow <kristina@10gen.com> Tue, 07 Apr 2009 10:18:58 -0400 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..6616a8b --- /dev/null +++ b/debian/control @@ -0,0 +1,29 @@ +Source: mongodb +Section: devel +Priority: optional +Maintainer: Kristina Chodorow <kristina@10gen.com> +Build-Depends: debhelper (>= 7), libboost-dev, libpcre3, libpcre3-dev, scons, xulrunner-1.9-dev | xulrunner-1.9.1-dev, libboost-thread-dev, libboost-filesystem-dev, libboost-program-options-dev, libboost-date-time-dev +Standards-Version: 3.8.0 +Homepage: http://www.mongodb.org + +Package: mongodb +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, xulrunner-1.9-dev +Description: An object/document-oriented database + MongoDB is a high-performance, open source, schema-free + document-oriented data store that's easy to deploy, manage + and use. It's network accessible, written in C++ and offers + the following features : + . + * Collection oriented storage - easy storage of object- + style data + * Full index support, including on inner objects + * Query profiling + * Replication and fail-over support + * Efficient storage of binary data including large + objects (e.g. videos) + * Auto-sharding for cloud-level scalability (Q209) + . + High performance, scalability, and reasonable depth of + functionality are the goals for the project. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..478c6f9 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,23 @@ +This package was debianized by Kristina Chodorow <kristina@10gen.com> on +Tue, 07 Apr 2009 10:18:58 -0400. + +It was downloaded from http://www.mongodb.org + +Upstream Authors: + + Eliot Horowitz + Dwight Merriman + Aaron Staple + Michael Dirolf + Kristina Chodorow + +Copyright: + + 2009 10gen + +License: + + AGPL + +The Debian packaging is (C) 2009, Kristina Chodorow <kristina@10gen.com> and +is licensed under the AGPL, see `http://www.fsf.org/licensing/licenses/agpl-3.0.html'. diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..a7b6e78 --- /dev/null +++ b/debian/dirs @@ -0,0 +1,3 @@ +usr/bin +usr/sbin +var/lib/mongodb diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..2e28959 --- /dev/null +++ b/debian/files @@ -0,0 +1 @@ +mongodb_0.9.7_amd64.deb devel optional diff --git a/debian/init.d b/debian/init.d new file mode 100644 index 0000000..b4eedf0 --- /dev/null +++ b/debian/init.d @@ -0,0 +1,294 @@ +#!/bin/sh +# +# init.d script with LSB support. +# +# Copyright (c) 2007 Javier Fernandez-Sanguino <jfs@debian.org> +# +# This is free software; you may 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, +# or (at your option) any later version. +# +# This 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 with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: mongodb +# Required-Start: $network $local_fs +# Required-Stop: +# Should-Start: $named +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: An object/document-oriented database +# Description: MongoDB is a high-performance, open source, schema-free +# document-oriented data store that's easy to deploy, manage +# and use. It's network accessible, written in C++ and offers +# the following features: +# +# * Collection oriented storage - easy storage of object- +# style data +# * Full index support, including on inner objects +# * Query profiling +# * Replication and fail-over support +# * Efficient storage of binary data including large +# objects (e.g. videos) +# * Auto-sharding for cloud-level scalability (Q209) +# +# High performance, scalability, and reasonable depth of +# functionality are the goals for the project. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/mongod +DATA=/var/lib/mongodb +NAME=MongoDB +DESC=database + +if test ! -x $DAEMON; then + echo "Could not find $DAEMON" + exit 0 +fi + +if test ! -x $DATA; then + mkdir $DATA || exit 0 +fi + +. /lib/lsb/init-functions + +LOGDIR=/var/log/mongodb +PIDFILE=/var/run/$NAME.pid +DIETIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +LOGFILE=$LOGDIR/$NAME.log # Server logfile +DAEMON_OPTS="--dbpath $DATA --logpath $LOGFILE run" + + +# Include mongodb defaults if available +if [ -f /etc/default/$NAME ] ; then + . /etc/default/$NAME +fi + +DAEMONUSER=mongodb +# Check that the user exists (if we set a user) +# Does the user exist? +if [ -n "$DAEMONUSER" ] ; then + if getent passwd | grep -q "^$DAEMONUSER:"; then + # Obtain the uid and gid + DAEMONUID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $3}'` + DAEMONGID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $4}'` + else + log_failure_msg "The user $DAEMONUSER, required to run $NAME does not exist." + exit 1 + fi +fi + +set -e + + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { +# Start the process using the wrapper + if [ -z "$DAEMONUSER" ] ; then + start-stop-daemon --background --start --quiet --pidfile $PIDFILE \ + --make-pidfile --exec $DAEMON -- $DAEMON_OPTS + errcode=$? + else +# if we are using a daemonuser then change the user id + start-stop-daemon --background --start --quiet --pidfile $PIDFILE \ + --make-pidfile --chuid $DAEMONUSER \ + --exec $DAEMON -- $DAEMON_OPTS + errcode=$? + fi + return $errcode +} + +stop_server() { +# Stop the process using the wrapper + if [ -z "$DAEMONUSER" ] ; then + start-stop-daemon --stop --quiet --pidfile $PIDFILE + rm $PIDFILE + errcode=$? + else +# if we are using a daemonuser then look for process that match + start-stop-daemon --stop --quiet --pidfile $PIDFILE \ + --user $DAEMONUSER \ + --exec $DAEMON + errcode=$? + fi + + return $errcode +} + +reload_server() { + [ ! -f "$PIDFILE" ] && return 1 + pid=pidofproc $PIDFILE # This is the daemon's pid + # Send a SIGHUP + kill -USR1 $pid + return $? +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DIETIME"s + if running ; then + kill -9 $pid + sleep "$DIETIME"s + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $DESC $NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server ; then + # NOTE: Some servers might die some time after they start, + # this code will detect this issue if STARTTIME is set + # to a reasonable value + [ -n "$STARTTIME" ] && sleep $STARTTIME # Wait some time + if running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # It is not running after we did start + log_end_msg 1 + fi + else + # Either we could not start it + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $DESC" "$NAME" + if running ; then + # Only stop the server if we see it running + errcode=0 + stop_server || errcode=$? + log_end_msg $errcode + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $DESC" "$NAME" + errcode=0 + force_stop || errcode=$? + log_end_msg $errcode + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + errcode=0 + stop_server || errcode=$? + # Wait some sensible amount, some server need this + [ -n "$DIETIME" ] && sleep $DIETIME + start_server || errcode=$? + [ -n "$STARTTIME" ] && sleep $STARTTIME + running || errcode=$? + log_end_msg $errcode + ;; + status) + + log_daemon_msg "Checking status of $DESC" "$NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" + log_warning_msg "cannot re-read the config file (use restart)." + ;; + # And this if it cann + #reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + # log_daemon_msg "Reloading $DESC configuration files" "$NAME" + # if running ; then + # reload_server + # if ! running ; then + # Process died after we tried to reload + # log_progress_msg "died on reload" + # log_end_msg 1 + # exit 1 + # fi + # else + # log_progress_msg "server is not running" + # log_end_msg 1 + # exit 1 + # fi + #;; + + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/mongo.1 b/debian/mongo.1 new file mode 100644 index 0000000..2eeb674 --- /dev/null +++ b/debian/mongo.1 @@ -0,0 +1,62 @@ +.\" Documentation for the MongoDB shell +.TH MONGO "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongo \- the Mongo command\-line tool +.SH "SYNOPSIS" +\fBmongo [\fIOPTIONS\fR] [\fIDB_ADDRESS\fR] [\fIFILE+\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongo\fR +is a JavaScript shell (with GNU +readline +capabilities). It supports interactive and non\-interactive use. When used interactively, JavaScript can be used to query the database or perform any other function normally available with SpiderMonkey. Database output is displayed in JSON format. +.PP +If JavaScript files are specified on the command line, the shell will run non\-interactively, running each one in sequence and then exiting. +.SH "EXAMPLES" +.TP +.B mongo +start the shell, connecting to the server at localhost:27017 and using the test database +.TP +.B mongod foo +start the shell using the foo database at localhost:27017 +.TP +.B mongod 192.169.0.5/foo +start the shell using the foo database at 192.169.0.5:27017 +.TP +.B mongod 192.169.0.5:9999/foo +start the shell using the foo database at 192.169.0.5:9999 +.TP +.B mongod script1.js script2.js script3.js +run three scripts and exit +.SH "OPTIONS" +.TP +.B \-\-shell +run the shell after executing files +.TP +.B \-\-help +show usage information +.TP +.B \-\-host HOST +server to connect to (default HOST=localhost) +.TP +.B \-\-port PORT +port to connect to (default PORT=27017) +.TP +.B \-\-nodb +do not connect to mongod +.TP +.B \-\-eval SCRIPT +evaluate JavaScript +.TP +.B \-u USERNAME +specify user to log in as +.TP +.B \-pPASSWORD +specify password of user (notice there is no space) +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongodump.1 b/debian/mongodump.1 new file mode 100644 index 0000000..5cb33ce --- /dev/null +++ b/debian/mongodump.1 @@ -0,0 +1,36 @@ +.\" Documentation for the MongoDB dump tool +.TH MONGODUMP "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongodump \- the Mongo dump tool +.SH "SYNOPSIS" +\fBmongodump [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongodump\fR +is a tool to output a binary representation of a database. It is mostly used for doing hot backups of a database. +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-h, \-\-host HOST +server to connect to (default HOST=localhost) +.TP +.B \-d, \-\-db DATABASE +database to use +.TP +.B \-c, \-\-c COLLECTION +collection to use +.TP +.B \-o, \-\-out FILE +output file, if not specified, stdout is used +.TP +.B \-\-dbpath PATH +directly access mongod data files in this path, instead of connecting to a mongod instance +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongoexport.1 b/debian/mongoexport.1 new file mode 100644 index 0000000..1996b36 --- /dev/null +++ b/debian/mongoexport.1 @@ -0,0 +1,51 @@ +.\" Documentation for the MongoDB shell +.TH MONGOEXPORT "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongoexport \- the Mongo export tool +.SH "SYNOPSIS" +\fBmongoexport [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongoexport\fR +is a tool to export a MongoDB collection to either JSON or CSV. The query can be filtered or a list of fields to output can be given. +.PP +If the output is CSV, the fields must be specified in order. +.SH "EXAMPLES" +.TP +.B mongoexport -d test -c test1 --csv -f "name,num" +export documents from test.test1 in CSV format +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-h, \-\-host HOST +server to connect to (default HOST=localhost) +.TP +.B \-d, \-\-db DATABASE +database to use +.TP +.B \-c, \-\-c COLLECTION +collection to use +.TP +.B \-q, \-\-query QUERY +query filter +.TP +.B \-f, \-\-fields FIELDS +comma\-separated list of field names +.TP +.B \-\-csv +export to CSV instead of JSON +.TP +.B \-o, \-\-out FILE +output file, if not specified, stdout is used +.TP +.B \-\-dbpath PATH +directly access mongod data files in this path, instead of connecting to a mongod instance +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongofiles.1 b/debian/mongofiles.1 new file mode 100644 index 0000000..4d7c0c5 --- /dev/null +++ b/debian/mongofiles.1 @@ -0,0 +1,52 @@ +.\" Documentation for the MongoDB dump tool +.TH MONGOFILES "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongofiles \- a simple GridFS interface +.SH "SYNOPSIS" +\fBmongofiles [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongofiles\fR +is used to list, get, and insert files in the database. +.SH "EXAMPLES" +.TP +.B mongofiles list +lists files in test.fs.files +.TP +.B mongofiles put README.txt +inserts the file README.txt into the collection test.fs.files +.TP +.B mongofiles get photo.jpg +retrieves photo.jpg from test.fs.files and saves it locally +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-h, \-\-host HOST +mongo host to which to connect +.TP +.B \-d, \-\-db DB +database to use (default DB=test) +.TP +.B \-c, \-\-collection COLLECTION (default COLLECTION=fs.files) +collection to use +.TP +.B \-\-command [list\||\|search\||\|put\||\|get] +execute a command +.TP +.B \-\-file FILE +filename for get or put +.TP +.B list +list all files. takes an optional filename. the file has to start with the filename +.TP +.B search +search all files for something that contains the string +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongoimportjson.1 b/debian/mongoimportjson.1 new file mode 100644 index 0000000..5f3f450 --- /dev/null +++ b/debian/mongoimportjson.1 @@ -0,0 +1,45 @@ +.\" Documentation for the MongoDB shell +.TH MONGOIMPORTJSON "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongoimportjson \- the Mongo import tool +.SH "SYNOPSIS" +\fBmongoimportjson [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongoimportjson\fR +is a tool to import JSON documents into MongoDB. This utility takes a single file that contains one JSON string per line and inserts it. A databaase and collection must be specified. +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-h, \-\-host HOST +server to connect to (default HOST=localhost) +.TP +.B \-d, \-\-db DATABASE +database to use +.TP +.B \-c, \-\-c COLLECTION +collection to use +.TP +.B \-\-file FILE +file from which to import +.TP +.B \-\-dbpath PATH +directly access mongod data files in this path, instead of connecting to a mongod instance +.TP +.B \-\-idbefore +create id index before importing +.TP +.B \-\-id +create id index after importing (recommended) +.TP +.B \-\-drop +drop collection before importing +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongorestore.1 b/debian/mongorestore.1 new file mode 100644 index 0000000..5f207b0 --- /dev/null +++ b/debian/mongorestore.1 @@ -0,0 +1,36 @@ +.\" Documentation for the MongoDB dump tool +.TH MONGORESTORE "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongorestore \- the Mongo restoration tool +.SH "SYNOPSIS" +\fBmongorestore [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongorestore\fR +is a tool to use the output from mongodump to restore a database. +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-h, \-\-host HOST +server to connect to (default HOST=localhost) +.TP +.B \-d, \-\-db DATABASE +database to use +.TP +.B \-c, \-\-c COLLECTION +collection to use +.TP +.B \-\-dir PATH +directory from which to restore +.TP +.B \-\-dbpath PATH +directly access mongod data files in this path, instead of connecting to a mongod instance +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/mongos.1 b/debian/mongos.1 new file mode 100644 index 0000000..74d01c6 --- /dev/null +++ b/debian/mongos.1 @@ -0,0 +1,39 @@ +.\" Documentation for the MongoDB dump tool +.TH MONGOS "1" "June 2009" "10gen" "Mongo Database" +.SH "NAME" +mongos \- the Mongo sharding server +.SH "SYNOPSIS" +\fBmongos [\fIOPTIONS\fR]\fR +.SH "DESCRIPTION" +.PP +\fBmongos\fR +is used to setup, configure, and get information about sharded databases. +.SH "EXAMPLES" +.PP +.B ./mongod --port 9999 --dbpath /data/db/a # first server +.PP +.B ./mongod --port 9998 --dbpath /data/db/b # second server +.PP +.B ./mongos --configdb localhost:9999 # mongos +.PP +starts three servers to set up sharding +.SH "OPTIONS" +.TP +.B \-\-help +show usage information +.TP +.B \-\-port N +port on which to listen +.TP +.B \-\-configdb DATABASE+ +one or more databases to use as the configuration databases +.TP +.B \-v+ +verbosity +.SH "COPYRIGHT" +.PP +Copyright 2007\-2009 10gen +.SH "SEE ALSO" +For more information, please refer to the MongoDB wiki, available at http://www.mongodb.org. +.SH "AUTHOR" +Kristina Chodorow diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..3745b99 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,55 @@ +#!/bin/sh +# postinst script for mongodb +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postinst> `configure' <most-recently-configured-version> +# * <old-postinst> `abort-upgrade' <new version> +# * <conflictor's-postinst> `abort-remove' `in-favour' <package> +# <new-version> +# * <postinst> `abort-remove' +# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' +# <failed-install-package> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + configure) + # create a mongodb group and user + if ! grep -q mongodb /etc/passwd; then + adduser --system mongodb + addgroup --system mongodb + adduser mongodb mongodb + fi + + # create db + mkdir -p /var/lib/mongodb + chown mongodb:mongodb /var/lib/mongodb + + # create logdir + mkdir -p /var/log/mongodb + chown mongodb:mongodb /var/log/mongodb + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..4bbb708 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,39 @@ +#!/bin/sh +# postrm script for mongodb +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/preinst b/debian/preinst new file mode 100644 index 0000000..c2d5362 --- /dev/null +++ b/debian/preinst @@ -0,0 +1,37 @@ +#!/bin/sh +# preinst script for mongodb +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <new-preinst> `install' +# * <new-preinst> `install' <old-version> +# * <new-preinst> `upgrade' <old-version> +# * <old-preinst> `abort-upgrade' <new-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 0000000..9507ade --- /dev/null +++ b/debian/prerm @@ -0,0 +1,41 @@ +#!/bin/sh +# prerm script for mongodb +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <prerm> `remove' +# * <old-prerm> `upgrade' <new-version> +# * <new-prerm> `failed-upgrade' <old-version> +# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> +# * <deconfigured's-prerm> `deconfigure' `in-favour' +# <package-being-installed> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +echo "arg: $1" + +case "$1" in + remove|upgrade|deconfigure) + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..c258723 --- /dev/null +++ b/debian/rules @@ -0,0 +1,95 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + scons + #docbook-to-man debian/mongodb.sgml > mongodb.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + scons -c + rm -f config.log + rm -f mongo + rm -f mongod + rm -f mongoimportjson + rm -f mongoexport + rm -f mongorestore + rm -f mongodump + rm -f mongofiles + rm -f .sconsign.dblite + rm -f libmongoclient.a + rm -rf client/*.o + rm -rf tools/*.o + rm -rf shell/*.o + rm -rf .sconf_temp + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + scons --prefix=$(CURDIR)/debian/mongodb/usr install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime + dh_installinit +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/ubuntu/mongodb.conf b/debian/ubuntu/mongodb.conf new file mode 100644 index 0000000..90a5e44 --- /dev/null +++ b/debian/ubuntu/mongodb.conf @@ -0,0 +1,13 @@ +# Ubuntu upstart file at /etc/init/mongodb.conf +# Presumes installation of mongodb is in /usr/local/mongodb/ + +pre-start script + mkdir -p /var/lib/mongodb/ + mkdir -p /var/log/mongodb/ +end script + +start on runlevel [345] + +exec /usr/local/mongodb/bin/mongod --config /usr/local/mongodb/mongodb_settings.conf + +respawn
\ No newline at end of file diff --git a/debian/ubuntu/mongodb_settings.conf b/debian/ubuntu/mongodb_settings.conf new file mode 100644 index 0000000..dbb83cb --- /dev/null +++ b/debian/ubuntu/mongodb_settings.conf @@ -0,0 +1,6 @@ +# This is an example config file for MongoDB to be located at /usr/local/mongodb/mongodb_settings.conf +# and used by /etc/init/mongodb.conf + +logappend = true +logpath = /var/log/mongodb/mongod.log +dbpath = /var/lib/mongodb/
\ No newline at end of file diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..08ce42b --- /dev/null +++ b/debian/watch @@ -0,0 +1,10 @@ +# Example watch control file for uscan +# Rename this file to "watch" and then you can run the "uscan" command +# to check for upstream updates and more. +# See uscan(1) for format + +# Compulsory line, this is a version 3 file +version=3 + +# examine a Webserver directory +http://downloads.mongodb.org/linux/mongodb-linux-(.*)\.tar\.gz diff --git a/distsrc/GNU-AGPL-3.0 b/distsrc/GNU-AGPL-3.0 new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/distsrc/GNU-AGPL-3.0 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + 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 +them 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 Affero General Public License as published by + the Free Software Foundation, either version 3 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/distsrc/README b/distsrc/README new file mode 100644 index 0000000..745608d --- /dev/null +++ b/distsrc/README @@ -0,0 +1,34 @@ + +MongoDB +======= + +Welcome to MongoDB! + +Package Contents +---------------- + + bin/mongod - MongoDB server + bin/mongo - MongoDB client + + bin/mongodump - MongoDB dump tool - for backups, snapshots, etc.. + bin/mongorestore - MongoDB restore a dump + bin/mongoexport - Export a single collection to test (json,csv) + bin/mongoimportjson - Import a json file into a collection + + bin/mongofiles - Utility for putting and getting files from MongoDB gridfs + + +Useful Resources +---------------- + + MongoDB Website + + * http://www.mongodb.org/ + +Documentation + + * http://www.mongodb.org/display/DOCS/Documentation + + MongoDB Maillists & IRC + + * http://www.mongodb.org/display/DOCS/Community diff --git a/distsrc/THIRD-PARTY-NOTICES b/distsrc/THIRD-PARTY-NOTICES new file mode 100644 index 0000000..76307c9 --- /dev/null +++ b/distsrc/THIRD-PARTY-NOTICES @@ -0,0 +1,166 @@ +MongoDB uses third-party libraries or other resources that may +be distributed under licenses different than the MongoDB software. + +In the event that we accidentally failed to list a required notice, +please bring it to our attention through any of the ways detailed here : + + mongodb-dev@googlegroups.com + +The attached notices are provided for information only. + + +1) License Notice for Boost +--------------------------- + +http://www.boost.org/LICENSE_1_0.txt + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +2) License Notice for V8 +------------------------- + +http://code.google.com/p/v8/source/browse/trunk/LICENSE + +This license applies to all parts of V8 that are not externally +maintained libraries. The externally maintained libraries used by V8 +are: + + - Jscre, located under third_party/jscre. This code is copyrighted + by the University of Cambridge and Apple Inc. and released under a + 2-clause BSD license. + + - Dtoa, located under third_party/dtoa. This code is copyrighted by + David M. Gay and released under an MIT license. + + - Strongtalk assembler, the basis of the files assembler-arm-inl.h, + assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, + assembler-ia32.cc, assembler-ia32.h, assembler.cc and assembler.h. + This code is copyrighted by Sun Microsystems Inc. and released + under a 3-clause BSD license. + +These libraries have their own licenses; we recommend you read them, +as their terms may differ from the terms below. + +Copyright 2006-2008, Google Inc. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +3) License Notice for PCRE +-------------------------- + +http://www.pcre.org/licence.txt + +PCRE LICENCE +------------ + +PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + +Release 7 of PCRE is distributed under the terms of the "BSD" licence, as +specified below. The documentation for PCRE, supplied in the "doc" +directory, is distributed under the same terms as the software itself. + +The basic library functions are written in C and are freestanding. Also +included in the distribution is a set of C++ wrapper functions. + + +THE BASIC LIBRARY FUNCTIONS +--------------------------- + +Written by: Philip Hazel +Email local part: ph10 +Email domain: cam.ac.uk + +University of Cambridge Computing Service, +Cambridge, England. + +Copyright (c) 1997-2008 University of Cambridge +All rights reserved. + + +THE C++ WRAPPER FUNCTIONS +------------------------- + +Contributed by: Google Inc. + +Copyright (c) 2007-2008, Google Inc. +All rights reserved. + + +THE "BSD" LICENCE +----------------- + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the name of Google + Inc. nor the names of their contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +End diff --git a/docs/building.debian.etch.ec2.md b/docs/building.debian.etch.ec2.md new file mode 100644 index 0000000..4a96a79 --- /dev/null +++ b/docs/building.debian.etch.ec2.md @@ -0,0 +1,21 @@ + +ami-f2f6159b + + apt-get update + + apt-get install git-core "g++-4.1" + apt-get install python-setuptools libpcre3-dev + apt-get install libboost-filesystem-dev libboost-dev libboost-thread-dev libboost-program-options-dev libboost-date-time-dev + +see: http://www.mongodb.org/display/DOCS/Building+Spider+Monkey + + ln -s /usr/bin/g++-4.1 /usr/bin/g++ + ln -s /usr/bin/gcc-4.1 /usr/bin/gcc + + easy_install scons + + git clone git://github.com/mongodb/mongo.git + + cd mongo + scons all + diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 0000000..3f18557 --- /dev/null +++ b/docs/building.md @@ -0,0 +1,65 @@ + +Building MongoDB +================ + +Scons +---------------- + + For detail information about building, please see: + http://www.mongodb.org/display/DOCS/Building + + If you want to build everything (mongod, mongo, tools, etc): + + $ scons . + + If you only want to build the database: + + $ scons + + To install + + $ scons --prefix=/opt/mongo install + + Please note that prebuilt binaries are available on mongodb.org and may be the easier way to get started. + +scons targets +------------- +* mongod +* mongos +* mongo +* mongoclient + +*general notes +--------------- + COMPILER VERSIONS + + Mongo has been tested with GCC 4.x and Visual Studio 2008. Older versions + of GCC may not be happy. + +windows +--------------- + + See also http://www.mongodb.org/display/DOCS/Building+for+Windows + + Build requirements: + - vc++ express or visual studio + - python 2.5 (for scons - 2.6 might be needed for some regression tests) + - scons + - boost 1.35 (or higher) + - windows sdk - tested with v6.0 v6.0a + + Or download a prebuilt binary for Windows at www.mongodb.org. + +ubuntu +-------------- + + scons libboost-dev libpcre++-dev xulrunner-1.9.1-dev + +FreeBSD + + Install the following ports: + + - devel/boost + - devel/libexecinfo + - devel/pcre + - lang/spidermonkey diff --git a/doxygenConfig b/doxygenConfig new file mode 100644 index 0000000..6580365 --- /dev/null +++ b/doxygenConfig @@ -0,0 +1,316 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = MongoDB +PROJECT_NUMBER = 1.3.1 +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# *** +# ERH - this controls whether all classes in files are documented or just the ones with tags +# *** +EXTRACT_ALL = NO + +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = client db/jsobj.h db/json.h +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +HTML_DYNAMIC_SECTIONS = NO +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NONE +TREEVIEW_WIDTH = 250 +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_FONTNAME = FreeSans +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO @@ -0,0 +1,108 @@ +# $Id$ +# +# SCons builder for gcc's precompiled headers +# Copyright (C) 2006 Tim Blechmann +# +# 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; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# $Revision$ +# $LastChangedRevision$ +# $LastChangedDate$ +# $LastChangedBy$ + +import SCons.Action +import SCons.Builder +import SCons.Scanner.C +import SCons.Util +import SCons.Script + +SCons.Script.EnsureSConsVersion(0,96,92) + +GchAction = SCons.Action.Action('$GCHCOM', '$GCHCOMSTR') +GchShAction = SCons.Action.Action('$GCHSHCOM', '$GCHSHCOMSTR') + +def gen_suffix(env, sources): + return sources[0].get_suffix() + env['GCHSUFFIX'] + + +GchShBuilder = SCons.Builder.Builder(action = GchShAction, + source_scanner = SCons.Scanner.C.CScanner(), + suffix = gen_suffix) + +GchBuilder = SCons.Builder.Builder(action = GchAction, + source_scanner = SCons.Scanner.C.CScanner(), + suffix = gen_suffix) + +def static_pch_emitter(target,source,env): + SCons.Defaults.StaticObjectEmitter( target, source, env ) + + scanner = SCons.Scanner.C.CScanner() + path = scanner.path(env) + deps = scanner(source[0], env, path) + + if env.has_key('Gch') and env['Gch']: + if env['Gch'].path.strip('.gch') in [x.path for x in deps]: + env.Depends(target, env['Gch']) + + return (target, source) + +def shared_pch_emitter(target,source,env): + SCons.Defaults.SharedObjectEmitter( target, source, env ) + + scanner = SCons.Scanner.C.CScanner() + path = scanner.path(env) + deps = scanner(source[0], env, path) + + if env.has_key('GchSh') and env['GchSh']: + if env['GchSh'].path.strip('.gch') in [x.path for x in deps]: + env.Depends(target, env['GchSh']) + return (target, source) + +def generate(env): + """ + Add builders and construction variables for the Gch builder. + """ + env.Append(BUILDERS = { + 'gch': env.Builder( + action = GchAction, + target_factory = env.fs.File, + ), + 'gchsh': env.Builder( + action = GchShAction, + target_factory = env.fs.File, + ), + }) + + try: + bld = env['BUILDERS']['Gch'] + bldsh = env['BUILDERS']['GchSh'] + except KeyError: + bld = GchBuilder + bldsh = GchShBuilder + env['BUILDERS']['Gch'] = bld + env['BUILDERS']['GchSh'] = bldsh + + env['GCHCOM'] = '$CXX -o $TARGET -x c++-header -c $CXXFLAGS $_CCCOMCOM $SOURCE' + env['GCHSHCOM'] = '$CXX -o $TARGET -x c++-header -c $SHCXXFLAGS $_CCCOMCOM $SOURCE' + env['GCHSUFFIX'] = '.gch' + + for suffix in SCons.Util.Split('.c .C .cc .cxx .cpp .c++'): + env['BUILDERS']['StaticObject'].add_emitter( suffix, static_pch_emitter ) + env['BUILDERS']['SharedObject'].add_emitter( suffix, shared_pch_emitter ) + + +def exists(env): + return env.Detect('g++') diff --git a/jars/babble.jar b/jars/babble.jar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/jars/babble.jar diff --git a/jars/ecj-3.4.jar b/jars/ecj-3.4.jar Binary files differnew file mode 100644 index 0000000..878a32c --- /dev/null +++ b/jars/ecj-3.4.jar diff --git a/jars/fast-md5.jar b/jars/fast-md5.jar Binary files differnew file mode 100644 index 0000000..487fd85 --- /dev/null +++ b/jars/fast-md5.jar diff --git a/jars/mongojs-core.jar b/jars/mongojs-core.jar Binary files differnew file mode 100644 index 0000000..50b9270 --- /dev/null +++ b/jars/mongojs-core.jar diff --git a/jars/mongojs-db.jar b/jars/mongojs-db.jar Binary files differnew file mode 100644 index 0000000..11e979a --- /dev/null +++ b/jars/mongojs-db.jar diff --git a/jars/mongojs-ext.jar b/jars/mongojs-ext.jar Binary files differnew file mode 100644 index 0000000..c736b7f --- /dev/null +++ b/jars/mongojs-ext.jar diff --git a/jars/mongojs-js.jar b/jars/mongojs-js.jar Binary files differnew file mode 100644 index 0000000..2b9b128 --- /dev/null +++ b/jars/mongojs-js.jar diff --git a/jstests/_lodeRunner.js b/jstests/_lodeRunner.js new file mode 100644 index 0000000..6e23dbb --- /dev/null +++ b/jstests/_lodeRunner.js @@ -0,0 +1,4 @@ +// Start mongod and run jstests/_runner.js + +db = startMongod( "--port", "27018", "--dbpath", "/data/db/jstests" ).getDB( "test" ); +load( "jstests/_runner.js" ); diff --git a/jstests/_runner.js b/jstests/_runner.js new file mode 100644 index 0000000..f0ce49d --- /dev/null +++ b/jstests/_runner.js @@ -0,0 +1,24 @@ +// +// simple runner to run toplevel tests in jstests +// +var files = listFiles("jstests"); + +files.forEach( + function(x) { + + if ( /_runner/.test(x.name) || + /_lodeRunner/.test(x.name) || + ! /\.js$/.test(x.name ) ){ + print(" >>>>>>>>>>>>>>> skipping " + x.name); + return; + } + + + print(" *******************************************"); + print(" Test : " + x.name + " ..."); + print(" " + Date.timeFunc( function() { load(x.name); }, 1) + "ms"); + + } +); + + diff --git a/jstests/_runner_leak.js b/jstests/_runner_leak.js new file mode 100644 index 0000000..18d7fb2 --- /dev/null +++ b/jstests/_runner_leak.js @@ -0,0 +1,44 @@ +// +// simple runner to run toplevel tests in jstests +// +var files = listFiles("jstests"); + +var dummyDb = db.getSisterDB( "dummyDBdummydummy" ); + +dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ); +prev = dummyDb.serverStatus(); + +print( "START : " + tojson( prev ) ); + +files.forEach( + function(x) { + + if ( /_runner/.test(x.name) || + /_lodeRunner/.test(x.name) || + ! /\.js$/.test(x.name ) ){ + print(" >>>>>>>>>>>>>>> skipping " + x.name); + return; + } + + + print(" *******************************************"); + print(" Test : " + x.name + " ..."); + print(" " + Date.timeFunc( function() { load(x.name); }, 1) + "ms"); + + assert( dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ).ok == 1 , "closeAllDatabases failed" ); + var now = dummyDb.serverStatus(); + var leaked = now.mem.virtual - prev.mem.virtual; + if ( leaked > 0 ){ + print( " LEAK : " + prev.mem.virtual + " -->> " + now.mem.virtual ); + printjson( now ); + if ( leaked > 20 ) + throw -1; + } + prev = now; + } +); + + + +dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ); +print( "END : " + tojson( dummyDb.serverStatus() ) ); diff --git a/jstests/_runner_leak_nojni.js b/jstests/_runner_leak_nojni.js new file mode 100644 index 0000000..fe2c6b2 --- /dev/null +++ b/jstests/_runner_leak_nojni.js @@ -0,0 +1,42 @@ +// +// simple runner to run toplevel tests in jstests +// +var files = listFiles("jstests"); + +var dummyDb = db.getSisterDB( "dummyDBdummydummy" ); + +dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ); +prev = dummyDb.runCommand( "meminfo" ); + +print( "START : " + tojson( prev ) ); + +files.forEach( + function(x) { + + if ( /_runner/.test(x.name) || + /_lodeRunner/.test(x.name) || + /jni/.test(x.name) || + /eval/.test(x.name) || + /where/.test(x.name) || + ! /\.js$/.test(x.name ) ){ + print(" >>>>>>>>>>>>>>> skipping " + x.name); + return; + } + + + print(" *******************************************"); + print(" Test : " + x.name + " ..."); + print(" " + Date.timeFunc( function() { load(x.name); }, 1) + "ms"); + + assert( dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ).ok == 1 , "closeAllDatabases failed" ); + var now = dummyDb.runCommand( "meminfo" ); + if ( now.virtual > prev.virtual ) + print( " LEAK : " + prev.virtual + " -->> " + now.virtual ); + prev = now; + } +); + + + +dummyDb.getSisterDB( "admin" ).runCommand( "closeAllDatabases" ); +print( "END : " + tojson( dummyDb.runCommand( "meminfo" ) ) ); diff --git a/jstests/_runner_sharding.js b/jstests/_runner_sharding.js new file mode 100644 index 0000000..761b9df --- /dev/null +++ b/jstests/_runner_sharding.js @@ -0,0 +1,35 @@ +// +// simple runner to run toplevel tests in jstests +// +var files = listFiles("jstests/sharding"); + +var num = 0; + +files.forEach( + function(x) { + + if ( /_runner/.test(x.name) || + /_lodeRunner/.test(x.name) || + ! /\.js$/.test(x.name ) ){ + print(" >>>>>>>>>>>>>>> skipping " + x.name); + return; + } + + if ( num++ > 0 ){ + sleep( 1000 ); // let things fully come down + } + + print(" *******************************************"); + print(" Test : " + x.name + " ..."); + try { + print(" " + Date.timeFunc( function() { load(x.name); }, 1) + "ms"); + } + catch ( e ){ + print( " ERROR on " + x.name + "!! " + e ); + throw e; + } + + } +); + + diff --git a/jstests/all.js b/jstests/all.js new file mode 100644 index 0000000..3d642ee --- /dev/null +++ b/jstests/all.js @@ -0,0 +1,45 @@ +t = db.jstests_all; +t.drop(); + +doTest = function() { + + t.save( { a:[ 1,2,3 ] } ); + t.save( { a:[ 1,2,4 ] } ); + t.save( { a:[ 1,8,5 ] } ); + t.save( { a:[ 1,8,6 ] } ); + t.save( { a:[ 1,9,7 ] } ); + + assert.eq( 5, t.find( { a: { $all: [ 1 ] } } ).count() ); + assert.eq( 2, t.find( { a: { $all: [ 1, 2 ] } } ).count() ); + assert.eq( 2, t.find( { a: { $all: [ 1, 8 ] } } ).count() ); + assert.eq( 1, t.find( { a: { $all: [ 1, 3 ] } } ).count() ); + assert.eq( 2, t.find( { a: { $all: [ 2 ] } } ).count() ); + assert.eq( 1, t.find( { a: { $all: [ 2, 3 ] } } ).count() ); + assert.eq( 2, t.find( { a: { $all: [ 2, 1 ] } } ).count() ); + + t.save( { a: [ 2, 2 ] } ); + assert.eq( 3, t.find( { a: { $all: [ 2, 2 ] } } ).count() ); + + t.save( { a: [ [ 2 ] ] } ); + assert.eq( 3, t.find( { a: { $all: [ 2 ] } } ).count() ); + + t.save( { a: [ { b: [ 10, 11 ] }, 11 ] } ); + assert.eq( 1, t.find( { 'a.b': { $all: [ 10 ] } } ).count() ); + assert.eq( 1, t.find( { a: { $all: [ 11 ] } } ).count() ); + + t.save( { a: { b: [ 20, 30 ] } } ); + assert.eq( 1, t.find( { 'a.b': { $all: [ 20 ] } } ).count() ); + assert.eq( 1, t.find( { 'a.b': { $all: [ 20, 30 ] } } ).count() ); + + + assert.eq( 5 , t.find( { a : { $all : [1] } } ).count() , "E1" ); + assert.eq( 0 , t.find( { a : { $all : [19] } } ).count() , "E2" ); + assert.eq( 0 , t.find( { a : { $all : [] } } ).count() , "E3" ); + + +} + +doTest(); +t.drop(); +t.ensureIndex( {a:1} ); +doTest(); diff --git a/jstests/all2.js b/jstests/all2.js new file mode 100644 index 0000000..64372ca --- /dev/null +++ b/jstests/all2.js @@ -0,0 +1,86 @@ + +t = db.all2; +t.drop(); + +t.save( { a : [ { x : 1 } , { x : 2 } ] } ) +t.save( { a : [ { x : 2 } , { x : 3 } ] } ) +t.save( { a : [ { x : 3 } , { x : 4 } ] } ) + +state = "no index"; + +function check( n , q , e ){ + assert.eq( n , t.find( q ).count() , tojson( q ) + " " + e + " count " + state ); + assert.eq( n , t.find( q ).itcount() , tojson( q ) + " " + e + " itcount" + state ); +} + +check( 1 , { "a.x" : { $in : [ 1 ] } } , "A" ); +check( 2 , { "a.x" : { $in : [ 2 ] } } , "B" ); + +check( 2 , { "a.x" : { $in : [ 1 , 2 ] } } , "C" ); +check( 3 , { "a.x" : { $in : [ 2 , 3 ] } } , "D" ); +check( 3 , { "a.x" : { $in : [ 1 , 3 ] } } , "E" ); + +check( 1 , { "a.x" : { $all : [ 1 , 2 ] } } , "F" ); +check( 1 , { "a.x" : { $all : [ 2 , 3 ] } } , "G" ); +check( 0 , { "a.x" : { $all : [ 1 , 3 ] } } , "H" ); + +t.ensureIndex( { "a.x" : 1 } ); +state = "index"; + +check( 1 , { "a.x" : { $in : [ 1 ] } } , "A" ); +check( 2 , { "a.x" : { $in : [ 2 ] } } , "B" ); + +check( 2 , { "a.x" : { $in : [ 1 , 2 ] } } , "C" ); +check( 3 , { "a.x" : { $in : [ 2 , 3 ] } } , "D" ); +check( 3 , { "a.x" : { $in : [ 1 , 3 ] } } , "E" ); + +check( 1 , { "a.x" : { $all : [ 1 , 2 ] } } , "F" ); +check( 1 , { "a.x" : { $all : [ 2 , 3 ] } } , "G" ); +check( 0 , { "a.x" : { $all : [ 1 , 3 ] } } , "H" ); + +// --- more + +t.drop(); + +t.save( { a : [ 1 , 2 ] } ) +t.save( { a : [ 2 , 3 ] } ) +t.save( { a : [ 3 , 4 ] } ) + +state = "more no index"; + +check( 1 , { "a" : { $in : [ 1 ] } } , "A" ); +check( 2 , { "a" : { $in : [ 2 ] } } , "B" ); + +check( 2 , { "a" : { $in : [ 1 , 2 ] } } , "C" ); +check( 3 , { "a" : { $in : [ 2 , 3 ] } } , "D" ); +check( 3 , { "a" : { $in : [ 1 , 3 ] } } , "E" ); + +check( 1 , { "a" : { $all : [ 1 , 2 ] } } , "F" ); +check( 1 , { "a" : { $all : [ 2 , 3 ] } } , "G" ); +check( 0 , { "a" : { $all : [ 1 , 3 ] } } , "H" ); + +t.ensureIndex( { "a" : 1 } ); +state = "more index"; + +check( 1 , { "a" : { $in : [ 1 ] } } , "A" ); +check( 2 , { "a" : { $in : [ 2 ] } } , "B" ); + +check( 2 , { "a" : { $in : [ 1 , 2 ] } } , "C" ); +check( 3 , { "a" : { $in : [ 2 , 3 ] } } , "D" ); +check( 3 , { "a" : { $in : [ 1 , 3 ] } } , "E" ); + +check( 1 , { "a" : { $all : [ 1 , 2 ] } } , "F" ); +check( 1 , { "a" : { $all : [ 2 , 3 ] } } , "G" ); +check( 0 , { "a" : { $all : [ 1 , 3 ] } } , "H" ); + + +// more 2 + +state = "more 2" + +t.drop(); +t.save( { name : [ "harry","jack","tom" ] } ) +check( 0 , { name : { $all : ["harry","john"] } } , "A" ); +t.ensureIndex( { name : 1 } ); +check( 0 , { name : { $all : ["harry","john"] } } , "B" ); + diff --git a/jstests/apitest_db.js b/jstests/apitest_db.js new file mode 100644 index 0000000..45e25b6 --- /dev/null +++ b/jstests/apitest_db.js @@ -0,0 +1,70 @@ +/** + * Tests for the db object enhancement + */ + +dd = function( x ){ + //print( x ); +} + +dd( "a" ); + + +dd( "b" ); + +/* + * be sure the public collection API is complete + */ +assert(db.createCollection , "createCollection" ); +assert(db.getProfilingLevel , "getProfilingLevel" ); +assert(db.setProfilingLevel , "setProfilingLevel" ); +assert(db.dbEval , "dbEval" ); +assert(db.group , "group" ); + +dd( "c" ); + +/* + * test createCollection + */ + +db.getCollection( "test" ).drop(); +db.getCollection( "system.namespaces" ).find().forEach( function(x) { assert(x.name != "test.test"); }); + +dd( "d" ); + +db.createCollection("test"); +var found = false; +db.getCollection( "system.namespaces" ).find().forEach( function(x) { if (x.name == "test.test") found = true; }); +assert(found); + +dd( "e" ); + +/* + * profile level + */ + +db.setProfilingLevel(0); +assert(db.getProfilingLevel() == 0); + +db.setProfilingLevel(1); +assert(db.getProfilingLevel() == 1); + +db.setProfilingLevel(2); +assert(db.getProfilingLevel() == 2); + +db.setProfilingLevel(0); +assert(db.getProfilingLevel() == 0); + +dd( "f" ); +asserted = false; +try { + db.setProfilingLevel(10); + assert(false); +} +catch (e) { + asserted = true; + assert(e.dbSetProfilingException); +} +assert( asserted ); + +dd( "g" ); + diff --git a/jstests/apitest_dbcollection.js b/jstests/apitest_dbcollection.js new file mode 100644 index 0000000..f6e74da --- /dev/null +++ b/jstests/apitest_dbcollection.js @@ -0,0 +1,115 @@ +/** + * Tests for the db collection + */ + + + +/* + * test drop + */ +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).find().length() == 0,1); + +db.getCollection( "test_db" ).save({a:1}); +assert(db.getCollection( "test_db" ).find().length() == 1,2); + +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).find().length() == 0,3); + +/* + * test count + */ + +assert(db.getCollection( "test_db" ).count() == 0,4); +db.getCollection( "test_db" ).save({a:1}); +assert(db.getCollection( "test_db" ).count() == 1,5); +for (i = 0; i < 100; i++) { + db.getCollection( "test_db" ).save({a:1}); +} +assert(db.getCollection( "test_db" ).count() == 101,6); +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).count() == 0,7); + +/* + * test clean (not sure... just be sure it doen't blow up, I guess + */ + + db.getCollection( "test_db" ).clean(); + + /* + * test validate + */ + +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).count() == 0,8); + +for (i = 0; i < 100; i++) { + db.getCollection( "test_db" ).save({a:1}); +} + +var v = db.getCollection( "test_db" ).validate(); +if( v.ns != "test.test_db" ) { + print("Error: wrong ns name"); + print(tojson(v)); +} +assert (v.ns == "test.test_db",9); +assert (v.ok == 1,10); + +assert(v.result.toString().match(/nrecords\?:(\d+)/)[1] == 100,11); + +/* + * test deleteIndex, deleteIndexes + */ + +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).count() == 0,12); +db.getCollection( "test_db" ).dropIndexes(); +assert(db.getCollection( "test_db" ).getIndexes().length == 0,13); + +db.getCollection( "test_db" ).save({a:10}); +assert(db.getCollection( "test_db" ).getIndexes().length == 1,14); + +db.getCollection( "test_db" ).ensureIndex({a:1}); +db.getCollection( "test_db" ).save({a:10}); + +print( tojson( db.getCollection( "test_db" ).getIndexes() ) ); +assert.eq(db.getCollection( "test_db" ).getIndexes().length , 2,15); + +db.getCollection( "test_db" ).dropIndex({a:1}); +assert(db.getCollection( "test_db" ).getIndexes().length == 1,16); + +db.getCollection( "test_db" ).save({a:10}); +db.getCollection( "test_db" ).ensureIndex({a:1}); +db.getCollection( "test_db" ).save({a:10}); + +assert(db.getCollection( "test_db" ).getIndexes().length == 2,17); + +db.getCollection( "test_db" ).dropIndex("a_1"); +assert.eq( db.getCollection( "test_db" ).getIndexes().length , 1,18); + +db.getCollection( "test_db" ).save({a:10, b:11}); +db.getCollection( "test_db" ).ensureIndex({a:1}); +db.getCollection( "test_db" ).ensureIndex({b:1}); +db.getCollection( "test_db" ).save({a:10, b:12}); + +assert(db.getCollection( "test_db" ).getIndexes().length == 3,19); + +db.getCollection( "test_db" ).dropIndex({b:1}); +assert(db.getCollection( "test_db" ).getIndexes().length == 2,20); +db.getCollection( "test_db" ).dropIndex({a:1}); +assert(db.getCollection( "test_db" ).getIndexes().length == 1,21); + +db.getCollection( "test_db" ).save({a:10, b:11}); +db.getCollection( "test_db" ).ensureIndex({a:1}); +db.getCollection( "test_db" ).ensureIndex({b:1}); +db.getCollection( "test_db" ).save({a:10, b:12}); + +assert(db.getCollection( "test_db" ).getIndexes().length == 3,22); + +db.getCollection( "test_db" ).dropIndexes(); +assert(db.getCollection( "test_db" ).getIndexes().length == 1,23); + +db.getCollection( "test_db" ).find(); + +db.getCollection( "test_db" ).drop(); +assert(db.getCollection( "test_db" ).getIndexes().length == 0,24); diff --git a/jstests/array1.js b/jstests/array1.js new file mode 100644 index 0000000..4409b7b --- /dev/null +++ b/jstests/array1.js @@ -0,0 +1,14 @@ +t = db.array1 +t.drop() + +x = { a : [ 1 , 2 ] }; + +t.save( { a : [ [1,2] ] } ); +assert.eq( 1 , t.find( x ).count() , "A" ); + +t.save( x ); +delete x._id; +assert.eq( 2 , t.find( x ).count() , "B" ); + +t.ensureIndex( { a : 1 } ); +assert.eq( 2 , t.find( x ).count() , "C" ); // TODO SERVER-146 diff --git a/jstests/array3.js b/jstests/array3.js new file mode 100644 index 0000000..3d053f9 --- /dev/null +++ b/jstests/array3.js @@ -0,0 +1,8 @@ + +assert.eq( 5 , Array.sum( [ 1 , 4 ] ), "A" ) +assert.eq( 2.5 , Array.avg( [ 1 , 4 ] ), "B" ) + +arr = [ 2 , 4 , 4 , 4 , 5 , 5 , 7 , 9 ] +assert.eq( 5 , Array.avg( arr ) , "C" ) +assert.eq( 2 , Array.stdDev( arr ) , "D" ) + diff --git a/jstests/arrayfind1.js b/jstests/arrayfind1.js new file mode 100644 index 0000000..422369e --- /dev/null +++ b/jstests/arrayfind1.js @@ -0,0 +1,38 @@ + +t = db.arrayfind1; +t.drop(); + +t.save( { a : [ { x : 1 } ] } ) +t.save( { a : [ { x : 1 , y : 2 , z : 1 } ] } ) +t.save( { a : [ { x : 1 , y : 1 , z : 3 } ] } ) + +function test( exptected , q , name ){ + assert.eq( exptected , t.find( q ).itcount() , name + " " + tojson( q ) + " itcount" ); + assert.eq( exptected , t.find( q ).count() , name + " " + tojson( q ) + " count" ); +} + +test( 3 , {} , "A1" ); +test( 1 , { "a.y" : 2 } , "A2" ); +test( 1 , { "a" : { x : 1 } } , "A3" ); +test( 3 , { "a" : { $elemMatch : { x : 1 } } } , "A4" ); // SERVER-377 + + +t.save( { a : [ { x : 2 } ] } ) +t.save( { a : [ { x : 3 } ] } ) +t.save( { a : [ { x : 4 } ] } ) + +assert.eq( 1 , t.find( { a : { $elemMatch : { x : 2 } } } ).count() , "B1" ); +assert.eq( 2 , t.find( { a : { $elemMatch : { x : { $gt : 2 } } } } ).count() , "B2" ); + +t.ensureIndex( { "a.x" : 1 } ); +assert( t.find( { "a" : { $elemMatch : { x : 1 } } } ).explain().cursor.indexOf( "BtreeC" ) == 0 , "C1" ); + +assert.eq( 1 , t.find( { a : { $elemMatch : { x : 2 } } } ).count() , "D1" ); + +t.find( { "a.x" : 1 } ).count(); +t.find( { "a.x" : { $gt : 1 } } ).count(); + +res = t.find( { "a" : { $elemMatch : { x : { $gt : 2 } } } } ).explain() +assert( res.cursor.indexOf( "BtreeC" ) == 0 , "C1" ); +assert.eq( 2 , t.find( { a : { $elemMatch : { x : { $gt : 2 } } } } ).count() , "D2" ); + diff --git a/jstests/auth1.js b/jstests/auth1.js new file mode 100644 index 0000000..f6890cc --- /dev/null +++ b/jstests/auth1.js @@ -0,0 +1,42 @@ + + +users = db.getCollection( "system.users" ); +users.remove( {} ); + +pass = "a" + Math.random(); +//print( "password [" + pass + "]" ); + +db.addUser( "eliot" , pass ); + +assert( db.auth( "eliot" , pass ) , "auth failed" ); +assert( ! db.auth( "eliot" , pass + "a" ) , "auth should have failed" ); + +pass2 = "b" + Math.random(); +db.addUser( "eliot" , pass2 ); + +assert( ! db.auth( "eliot" , pass ) , "failed to change password failed" ); +assert( db.auth( "eliot" , pass2 ) , "new password didn't take" ); + +assert( db.auth( "eliot" , pass2 ) , "what?" ); +db.removeUser( "eliot" ); +assert( ! db.auth( "eliot" , pass2 ) , "didn't remove user" ); + + +var a = db.getMongo().getDB( "admin" ); +users = a.getCollection( "system.users" ); +users.remove( {} ); +pass = "c" + Math.random(); +a.addUser( "super", pass ); +assert( a.auth( "super" , pass ) , "auth failed" ); +assert( !a.auth( "super" , pass + "a" ) , "auth should have failed" ); + +db2 = new Mongo( db.getMongo().host ).getDB( db.getName() ); + +users = db2.getCollection( "system.users" ); +users.remove( {} ); + +pass = "a" + Math.random(); + +db2.addUser( "eliot" , pass ); + +assert.commandFailed( db2.runCommand( { authenticate: 1, user: "eliot", nonce: "foo", key: "bar" } ) ); diff --git a/jstests/autoid.js b/jstests/autoid.js new file mode 100644 index 0000000..6c8062f --- /dev/null +++ b/jstests/autoid.js @@ -0,0 +1,11 @@ +f = db.jstests_autoid; +f.drop(); + +f.save( {z:1} ); +a = f.findOne( {z:1} ); +f.update( {z:1}, {z:2} ); +b = f.findOne( {z:2} ); +assert.eq( a._id.str, b._id.str ); +c = f.update( {z:2}, {z:"abcdefgabcdefgabcdefg"} ); +c = f.findOne( {} ); +assert.eq( a._id.str, c._id.str ); diff --git a/jstests/basic1.js b/jstests/basic1.js new file mode 100644 index 0000000..e5fa577 --- /dev/null +++ b/jstests/basic1.js @@ -0,0 +1,21 @@ + +t = db.getCollection( "basic1" ); +t.drop(); + +o = { a : 1 }; +t.save( o ); + +assert.eq( 1 , t.findOne().a , "first" ); +assert( o._id , "now had id" ); +assert( o._id.str , "id not a real id" ); + +o.a = 2; +t.save( o ); + +assert.eq( 2 , t.findOne().a , "second" ); + +assert(t.validate().valid); + +// not a very good test of currentOp, but tests that it at least +// is sort of there: +assert( db.currentOp().inprog != null ); diff --git a/jstests/basic2.js b/jstests/basic2.js new file mode 100644 index 0000000..aaa3de4 --- /dev/null +++ b/jstests/basic2.js @@ -0,0 +1,16 @@ + +t = db.getCollection( "basic2" ); +t.drop(); + +o = { n : 2 }; +t.save( o ); + +assert.eq( 1 , t.find().count() ); + +assert.eq( 2 , t.find( o._id ).toArray()[0].n ); +assert.eq( 2 , t.find( o._id , { n : 1 } ).toArray()[0].n ); + +t.remove( o._id ); +assert.eq( 0 , t.find().count() ); + +assert(t.validate().valid); diff --git a/jstests/basic3.js b/jstests/basic3.js new file mode 100644 index 0000000..b1ebafd --- /dev/null +++ b/jstests/basic3.js @@ -0,0 +1,24 @@ + +t = db.getCollection( "foo" ); + +t.find( { "a.b" : 1 } ).toArray(); + +ok = false; + +try{ + t.save( { "a.b" : 5 } ); + ok = false; +} +catch ( e ){ + ok = true; +} +assert( ok , ". in names aren't allowed doesn't work" ); + +try{ + t.save( { "x" : { "a.b" : 5 } } ); + ok = false; +} +catch ( e ){ + ok = true; +} +assert( ok , ". in embedded names aren't allowed doesn't work" ); diff --git a/jstests/basic4.js b/jstests/basic4.js new file mode 100644 index 0000000..0cf7a26 --- /dev/null +++ b/jstests/basic4.js @@ -0,0 +1,12 @@ +t = db.getCollection( "basic4" ); +t.drop(); + +t.save( { a : 1 , b : 1.0 } ); + +assert( t.findOne() ); +assert( t.findOne( { a : 1 } ) ); +assert( t.findOne( { a : 1.0 } ) ); +assert( t.findOne( { b : 1 } ) ); +assert( t.findOne( { b : 1.0 } ) ); + +assert( ! t.findOne( { b : 2.0 } ) ); diff --git a/jstests/basic5.js b/jstests/basic5.js new file mode 100644 index 0000000..bfa40fb --- /dev/null +++ b/jstests/basic5.js @@ -0,0 +1,6 @@ +t = db.getCollection( "basic5" ); +t.drop(); + +t.save( { a : 1 , b : [ 1 , 2 , 3 ] } ); +assert.eq( 3 , t.findOne().b.length ); + diff --git a/jstests/basic6.js b/jstests/basic6.js new file mode 100644 index 0000000..e0cd6f1 --- /dev/null +++ b/jstests/basic6.js @@ -0,0 +1,8 @@ + +t = db.basic6; + +t.findOne(); +t.a.findOne(); + +assert.eq( "test.basic6" , t.toString() ); +assert.eq( "test.basic6.a" , t.a.toString() ); diff --git a/jstests/basic7.js b/jstests/basic7.js new file mode 100644 index 0000000..7bb0d47 --- /dev/null +++ b/jstests/basic7.js @@ -0,0 +1,11 @@ + +t = db.basic7; +t.drop(); + +t.save( { a : 1 } ) +t.ensureIndex( { a : 1 } ); + +assert.eq( t.find().toArray()[0].a , 1 ); +assert.eq( t.find().arrayAccess(0).a , 1 ); +assert.eq( t.find()[0].a , 1 ); + diff --git a/jstests/basic8.js b/jstests/basic8.js new file mode 100644 index 0000000..513da0d --- /dev/null +++ b/jstests/basic8.js @@ -0,0 +1,11 @@ + +t = db.basic8; +t.drop(); + +t.save( { a : 1 } ); +o = t.findOne(); +o.b = 2; +t.save( o ); + +assert.eq( 1 , t.find().count() , "A" ); +assert.eq( 2 , t.findOne().b , "B" ); diff --git a/jstests/basic9.js b/jstests/basic9.js new file mode 100644 index 0000000..5920418 --- /dev/null +++ b/jstests/basic9.js @@ -0,0 +1,25 @@ + +t = db.getCollection( "foo" ); + +t.save( { "foo$bar" : 5 } ); + +ok = false; + +try{ + t.save( { "$foo" : 5 } ); + ok = false; +} +catch ( e ){ + ok = true; +} +assert( ok , "key names aren't allowed to start with $ doesn't work" ); + +try{ + t.save( { "x" : { "$foo" : 5 } } ); + ok = false; +} +catch ( e ){ + ok = true; +} +assert( ok , "embedded key names aren't allowed to start with $ doesn't work" ); + diff --git a/jstests/basica.js b/jstests/basica.js new file mode 100644 index 0000000..0cc364b --- /dev/null +++ b/jstests/basica.js @@ -0,0 +1,33 @@ + +t = db.basica; + + +t.drop(); + +t.save( { a : 1 , b : [ { x : 2 , y : 2 } , { x : 3 , y : 3 } ] } ); + +x = t.findOne(); +x.b["0"].x = 4; +x.b["0"].z = 4; +x.b[0].m = 9; +x.b[0]["asd"] = 11; +x.a = 2; +x.z = 11; + +tojson( x ); +t.save( x ); +assert.eq( tojson( x ) , tojson( t.findOne() ) , "FIRST" ); + +// ----- + +t.drop(); + +t.save( { a : 1 , b : [ { x : 2 , y : 2 } , { x : 3 , y : 3 } ] } ); + +x = t.findOne(); +x.b["0"].z = 4; + +//printjson( x ); +t.save( x ); +assert.eq( tojson( x ) , tojson( t.findOne() ) , "SECOND" ); + diff --git a/jstests/basicb.js b/jstests/basicb.js new file mode 100644 index 0000000..571b88c --- /dev/null +++ b/jstests/basicb.js @@ -0,0 +1,7 @@ + +t = db.basicb; +t.drop(); + +assert.throws( "t.insert( { '$a' : 5 } );" ); +t.insert( { '$a' : 5 } , true ); + diff --git a/jstests/capped.js b/jstests/capped.js new file mode 100644 index 0000000..bae7472 --- /dev/null +++ b/jstests/capped.js @@ -0,0 +1,11 @@ +db.jstests_capped.drop(); +db.createCollection("jstests_capped", {capped:true, size:30000}); +assert.eq( 0, db.system.indexes.find( {ns:"test.jstests_capped"} ).count() ); +t = db.jstests_capped; + +t.save({x:1}); +t.save({x:2}); + +assert( t.find().sort({$natural:1})[0].x == 1 ); +assert( t.find().sort({$natural:-1})[0].x == 2 ); + diff --git a/jstests/capped1.js b/jstests/capped1.js new file mode 100644 index 0000000..0bbeaa4 --- /dev/null +++ b/jstests/capped1.js @@ -0,0 +1,11 @@ + +t = db.capped1; +t.drop(); + +db.createCollection("capped1" , {capped:true, size:1024 }); +v = t.validate(); +assert( v.valid , "A : " + tojson( v ) ); // SERVER-485 + +t.save( { x : 1 } ) +assert( t.validate().valid , "B" ) + diff --git a/jstests/capped2.js b/jstests/capped2.js new file mode 100644 index 0000000..2d2f6a8 --- /dev/null +++ b/jstests/capped2.js @@ -0,0 +1,62 @@ +db.capped2.drop(); +db._dbCommand( { create: "capped2", capped: true, size: 1000, $nExtents: 11, autoIndexId: false } ); +tzz = db.capped2; + +function debug( x ) { +// print( x ); +} + +var val = new Array( 2000 ); +var c = ""; +for( i = 0; i < 2000; ++i, c += "-" ) { + val[ i ] = { a: c }; +} + +function checkIncreasing( i ) { + res = tzz.find().sort( { $natural: -1 } ); + assert( res.hasNext(), "A" ); + var j = i; + while( res.hasNext() ) { + try { + assert.eq( val[ j-- ].a, res.next().a, "B" ); + } catch( e ) { + debug( "capped2 err " + j ); + throw e; + } + } + res = tzz.find().sort( { $natural: 1 } ); + assert( res.hasNext(), "C" ); + while( res.hasNext() ) + assert.eq( val[ ++j ].a, res.next().a, "D" ); + assert.eq( j, i, "E" ); +} + +function checkDecreasing( i ) { + res = tzz.find().sort( { $natural: -1 } ); + assert( res.hasNext(), "F" ); + var j = i; + while( res.hasNext() ) { + assert.eq( val[ j++ ].a, res.next().a, "G" ); + } + res = tzz.find().sort( { $natural: 1 } ); + assert( res.hasNext(), "H" ); + while( res.hasNext() ) + assert.eq( val[ --j ].a, res.next().a, "I" ); + assert.eq( j, i, "J" ); +} + +for( i = 0 ;; ++i ) { + debug( "capped 2: " + i ); + tzz.save( val[ i ] ); + if ( tzz.count() == 0 ) { + assert( i > 100, "K" ); + break; + } + checkIncreasing( i ); +} + +for( i = 600 ; i >= 0 ; --i ) { + debug( "capped 2: " + i ); + tzz.save( val[ i ] ); + checkDecreasing( i ); +} diff --git a/jstests/capped3.js b/jstests/capped3.js new file mode 100644 index 0000000..f3b29b7 --- /dev/null +++ b/jstests/capped3.js @@ -0,0 +1,42 @@ +t = db.jstests_capped3; +t2 = db.jstests_capped3_clone; +t.drop(); +t2.drop(); +for( i = 0; i < 1000; ++i ) { + t.save( {i:i} ); +} +assert.commandWorked( db.runCommand( { cloneCollectionAsCapped:"jstests_capped3", toCollection:"jstests_capped3_clone", size:100000 } ) ); +c = t2.find(); +for( i = 0; i < 1000; ++i ) { + assert.eq( i, c.next().i ); +} +assert( !c.hasNext() ); + +t.drop(); +t2.drop(); + +for( i = 0; i < 1000; ++i ) { + t.save( {i:i} ); +} +assert.commandWorked( db.runCommand( { cloneCollectionAsCapped:"jstests_capped3", toCollection:"jstests_capped3_clone", size:1000 } ) ); +c = t2.find().sort( {$natural:-1} ); +i = 999; +while( c.hasNext() ) { + assert.eq( i--, c.next().i ); +} +assert( i < 990 ); + +t.drop(); +t2.drop(); + +for( i = 0; i < 1000; ++i ) { + t.save( {i:i} ); +} +assert.commandWorked( t.convertToCapped( 1000 ) ); +c = t.find().sort( {$natural:-1} ); +i = 999; +while( c.hasNext() ) { + assert.eq( i--, c.next().i ); +} +assert( i < 990 ); +assert( i > 900 ); diff --git a/jstests/capped4.js b/jstests/capped4.js new file mode 100644 index 0000000..14d5bd0 --- /dev/null +++ b/jstests/capped4.js @@ -0,0 +1,28 @@ +t = db.jstests_capped4; +t.drop(); + +db.createCollection( "jstests_capped4", {size:1000,capped:true} ); +t.ensureIndex( { i: 1 } ); +for( i = 0; i < 20; ++i ) { + t.save( { i : i } ); +} +c = t.find().sort( { $natural: -1 } ).limit( 2 ); +c.next(); +c.next(); +d = t.find().sort( { i: -1 } ).limit( 2 ); +d.next(); +d.next(); + +for( i = 20; t.findOne( { i:19 } ); ++i ) { + t.save( { i : i } ); +} +//assert( !t.findOne( { i : 19 } ), "A" ); +assert( !c.hasNext(), "B" ); +assert( !d.hasNext(), "C" ); +assert( t.find().sort( { i : 1 } ).hint( { i : 1 } ).toArray().length > 10, "D" ); + +assert( t.findOne( { i : i - 1 } ), "E" ); +t.remove( { i : i - 1 } ); +assert( db.getLastError().indexOf( "capped" ) >= 0, "F" ); + +assert( t.validate().valid, "G" ); diff --git a/jstests/capped5.js b/jstests/capped5.js new file mode 100644 index 0000000..a5d04de --- /dev/null +++ b/jstests/capped5.js @@ -0,0 +1,18 @@ + +tn = "capped5" + +t = db[tn] +t.drop(); + +db.createCollection( tn , {capped: true, size: 1024 * 1024 * 1 } ); +t.insert( { _id : 5 , x : 11 , z : 52 } ); + +assert.eq( 0 , t.getIndexKeys().length , "A0" ) +assert.eq( 52 , t.findOne( { x : 11 } ).z , "A1" ); +assert.eq( 52 , t.findOne( { _id : 5 } ).z , "A2" ); + +t.ensureIndex( { _id : 1 } ) +t.ensureIndex( { x : 1 } ) + +assert.eq( 52 , t.findOne( { x : 11 } ).z , "B1" ); +assert.eq( 52 , t.findOne( { _id : 5 } ).z , "B2" ); diff --git a/jstests/clone/clonecollection.js b/jstests/clone/clonecollection.js new file mode 100644 index 0000000..64d4ff0 --- /dev/null +++ b/jstests/clone/clonecollection.js @@ -0,0 +1,165 @@ +// Test cloneCollection command + +var baseName = "jstests_clonecollection"; + +parallel = function() { + return t.parallelStatus; +} + +resetParallel = function() { + parallel().drop(); +} + +doParallel = function( work ) { + resetParallel(); + startMongoProgramNoConnect( "mongo", "--port", ports[ 1 ], "--eval", work + "; db.parallelStatus.save( {done:1} );", baseName ); +} + +doneParallel = function() { + return !!parallel().findOne(); +} + +waitParallel = function() { + assert.soon( function() { return doneParallel(); }, "parallel did not finish in time", 300000, 1000 ); +} + +ports = allocatePorts( 2 ); + +f = startMongod( "--port", ports[ 0 ], "--dbpath", "/data/db/" + baseName + "_from", "--nohttpinterface", "--bind_ip", "127.0.0.1" ).getDB( baseName ); +t = startMongod( "--port", ports[ 1 ], "--dbpath", "/data/db/" + baseName + "_to", "--nohttpinterface", "--bind_ip", "127.0.0.1" ).getDB( baseName ); + +for( i = 0; i < 1000; ++i ) { + f.a.save( { i: i } ); +} +assert.eq( 1000, f.a.find().count() ); + +assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); +assert.eq( 1000, t.a.find().count() ); + +t.a.drop(); + +assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a", { i: { $gte: 10, $lt: 20 } } ) ); +assert.eq( 10, t.a.find().count() ); + +t.a.drop(); +assert.eq( 0, t.system.indexes.find().count() ); + +f.a.ensureIndex( { i: 1 } ); +assert.eq( 2, f.system.indexes.find().count(), "expected index missing" ); +assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); +if ( t.system.indexes.find().count() != 2 ) { + printjson( t.system.indexes.find().toArray() ); +} +assert.eq( 2, t.system.indexes.find().count(), "expected index missing" ); +// Verify index works +assert.eq( 50, t.a.find( { i: 50 } ).hint( { i: 1 } ).explain().startKey.i ); +assert.eq( 1, t.a.find( { i: 50 } ).hint( { i: 1 } ).toArray().length, "match length did not match expected" ); + +// Check that capped-ness is preserved on clone +f.a.drop(); +t.a.drop(); + +f.createCollection( "a", {capped:true,size:1000} ); +assert( f.a.isCapped() ); +assert.commandWorked( t.cloneCollection( "localhost:" + ports[ 0 ], "a" ) ); +assert( t.a.isCapped(), "cloned collection not capped" ); + +// Now test insert + delete + update during clone +f.a.drop(); +t.a.drop(); + +for( i = 0; i < 100000; ++i ) { + f.a.save( { i: i } ); +} + +doParallel( "assert.commandWorked( db.cloneCollection( \"localhost:" + ports[ 0 ] + "\", \"a\", {i:{$gte:0}} ) );" ); + +sleep( 200 ); +f.a.save( { i: 200000 } ); +f.a.save( { i: -1 } ); +f.a.remove( { i: 0 } ); +f.a.update( { i: 99998 }, { i: 99998, x: "y" } ); +assert( !doneParallel(), "test run invalid" ); +waitParallel(); + +assert.eq( 100000, t.a.find().count() ); +assert.eq( 1, t.a.find( { i: 200000 } ).count() ); +assert.eq( 0, t.a.find( { i: -1 } ).count() ); +assert.eq( 0, t.a.find( { i: 0 } ).count() ); +assert.eq( 1, t.a.find( { i: 99998, x: "y" } ).count() ); + + +// Now test oplog running out of space -- specify small size clone oplog for test. +f.a.drop(); +t.a.drop(); + +for( i = 0; i < 200000; ++i ) { + f.a.save( { i: i } ); +} + +doParallel( "assert.commandFailed( db.runCommand( { cloneCollection: \"jstests_clonecollection.a\", from: \"localhost:" + ports[ 0 ] + "\", logSizeMb:1 } ) );" ); + +sleep( 200 ); +for( i = 200000; i < 250000; ++i ) { + f.a.save( { i: i } ); +} + +waitParallel(); + +// Make sure the same works with standard size op log. +f.a.drop(); +t.a.drop(); + +for( i = 0; i < 200000; ++i ) { + f.a.save( { i: i } ); +} + +doParallel( "assert.commandWorked( db.cloneCollection( \"localhost:" + ports[ 0 ] + "\", \"a\" ) );" ); + +sleep( 200 ); +for( i = 200000; i < 250000; ++i ) { + f.a.save( { i: i } ); +} + +waitParallel(); +assert.eq( 250000, t.a.find().count() ); + +// Test startCloneCollection and finishCloneCollection commands. +f.a.drop(); +t.a.drop(); + +for( i = 0; i < 100000; ++i ) { + f.a.save( { i: i } ); +} + +doParallel( "z = db.runCommand( {startCloneCollection:\"jstests_clonecollection.a\", from:\"localhost:" + ports[ 0 ] + "\" } ); print( \"clone_clone_clone_commandResult:::::\" + tojson( z , '' , true ) + \":::::\" );" ); + +sleep( 200 ); +f.a.save( { i: -1 } ); + +waitParallel(); +// even after parallel shell finished, must wait for finishToken line to appear in log +assert.soon( function() { + raw = rawMongoProgramOutput().replace( /[\r\n]/gm , " " ) + ret = raw.match( /clone_clone_clone_commandResult:::::(.*):::::/ ); + if ( ret == null ) { + return false; + } + ret = ret[ 1 ]; + return true; + } ); + +eval( "ret = " + ret ); + +assert.commandWorked( ret ); +assert.eq( 100001, t.a.find().count() ); + +f.a.save( { i: -2 } ); +assert.eq( 100002, f.a.find().count() ); +finishToken = ret.finishToken; +// Round-tripping through JS can corrupt the cursor ids we store as BSON +// Date elements. Date( 0 ) will correspond to a cursorId value of 0, which +// makes the db start scanning from the beginning of the collection. +finishToken.cursorId = new Date( 0 ); +assert.commandWorked( t.runCommand( {finishCloneCollection:finishToken} ) ); +assert.eq( 100002, t.a.find().count() ); diff --git a/jstests/copydb.js b/jstests/copydb.js new file mode 100644 index 0000000..7c7c025 --- /dev/null +++ b/jstests/copydb.js @@ -0,0 +1,20 @@ + + + + +a = db.getSisterDB( "copydb-test-a" ); +b = db.getSisterDB( "copydb-test-b" ); + +a.dropDatabase(); +b.dropDatabase(); + +a.foo.save( { a : 1 } ); + +assert.eq( 1 , a.foo.count() , "A" ); +assert.eq( 0 , b.foo.count() , "B" ); + +a.copyDatabase( a._name , b._name ); + +assert.eq( 1 , a.foo.count() , "C" ); +assert.eq( 1 , b.foo.count() , "D" ); + diff --git a/jstests/count.js b/jstests/count.js new file mode 100644 index 0000000..5502d71 --- /dev/null +++ b/jstests/count.js @@ -0,0 +1,25 @@ +t = db.jstests_count; + +t.drop(); +t.save( { i: 1 } ); +t.save( { i: 2 } ); +assert.eq( 1, t.find( { i: 1 } ).count(), "A" ); +assert.eq( 1, t.count( { i: 1 } ) , "B" ); +assert.eq( 2, t.find().count() , "C" ); +assert.eq( 2, t.find( undefined ).count() , "D" ); +assert.eq( 2, t.find( null ).count() , "E" ); +assert.eq( 2, t.count() , "F" ); + +t.drop(); +t.save( {a:true,b:false} ); +t.ensureIndex( {b:1,a:1} ); +assert.eq( 1, t.find( {a:true,b:false} ).count() , "G" ); +assert.eq( 1, t.find( {b:false,a:true} ).count() , "H" ); + +t.drop(); +t.save( {a:true,b:false} ); +t.ensureIndex( {b:1,a:1,c:1} ); + +assert.eq( 1, t.find( {a:true,b:false} ).count() , "I" ); +assert.eq( 1, t.find( {b:false,a:true} ).count() , "J" ); + diff --git a/jstests/count2.js b/jstests/count2.js new file mode 100644 index 0000000..33ff712 --- /dev/null +++ b/jstests/count2.js @@ -0,0 +1,23 @@ +t = db.count2; +t.drop(); + +for ( var i=0; i<1000; i++ ){ + t.save( { num : i , m : i % 20 } ); +} + +assert.eq( 1000 , t.count() , "A" ) +assert.eq( 1000 , t.find().count() , "B" ) +assert.eq( 1000 , t.find().toArray().length , "C" ) + +assert.eq( 50 , t.find( { m : 5 } ).toArray().length , "D" ) +assert.eq( 50 , t.find( { m : 5 } ).count() , "E" ) + +assert.eq( 40 , t.find( { m : 5 } ).skip( 10 ).toArray().length , "F" ) +assert.eq( 50 , t.find( { m : 5 } ).skip( 10 ).count() , "G" ) +assert.eq( 40 , t.find( { m : 5 } ).skip( 10 ).countReturn() , "H" ) + +assert.eq( 20 , t.find( { m : 5 } ).skip( 10 ).limit(20).toArray().length , "I" ) +assert.eq( 50 , t.find( { m : 5 } ).skip( 10 ).limit(20).count() , "J" ) +assert.eq( 20 , t.find( { m : 5 } ).skip( 10 ).limit(20).countReturn() , "K" ) + +assert.eq( 5 , t.find( { m : 5 } ).skip( 45 ).limit(20).countReturn() , "L" ) diff --git a/jstests/count3.js b/jstests/count3.js new file mode 100644 index 0000000..a8c3ef5 --- /dev/null +++ b/jstests/count3.js @@ -0,0 +1,26 @@ + +t = db.count3; + +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 1 , b : 2 } ); + +assert.eq( 2 , t.find( { a : 1 } ).itcount() , "A" ); +assert.eq( 2 , t.find( { a : 1 } ).count() , "B" ); + +assert.eq( 2 , t.find( { a : 1 } , { b : 1 } ).itcount() , "C" ); +assert.eq( 2 , t.find( { a : 1 } , { b : 1 } ).count() , "D" ); + +t.drop(); + +t.save( { a : 1 } ); + +assert.eq( 1 , t.find( { a : 1 } ).itcount() , "E" ); +assert.eq( 1 , t.find( { a : 1 } ).count() , "F" ); + +assert.eq( 1 , t.find( { a : 1 } , { b : 1 } ).itcount() , "G" ); +assert.eq( 1 , t.find( { a : 1 } , { b : 1 } ).count() , "H" ); + + + diff --git a/jstests/count4.js b/jstests/count4.js new file mode 100644 index 0000000..7be7436 --- /dev/null +++ b/jstests/count4.js @@ -0,0 +1,17 @@ + +t = db.count4; +t.drop(); + +for ( i=0; i<100; i++ ){ + t.save( { x : i } ); +} + +q = { x : { $gt : 25 , $lte : 75 } } + +assert.eq( 50 , t.find( q ).count() , "A" ); +assert.eq( 50 , t.find( q ).itcount() , "B" ); + +t.ensureIndex( { x : 1 } ); + +assert.eq( 50 , t.find( q ).count() , "C" ); +assert.eq( 50 , t.find( q ).itcount() , "D" ); diff --git a/jstests/count5.js b/jstests/count5.js new file mode 100644 index 0000000..b6bbc54 --- /dev/null +++ b/jstests/count5.js @@ -0,0 +1,30 @@ + +t = db.count5; +t.drop(); + +for ( i=0; i<100; i++ ){ + t.save( { x : i } ); +} + +q = { x : { $gt : 25 , $lte : 75 } }; + +assert.eq( 50 , t.find( q ).count() , "A" ); +assert.eq( 50 , t.find( q ).itcount() , "B" ); + +t.ensureIndex( { x : 1 } ); + +assert.eq( 50 , t.find( q ).count() , "C" ); +assert.eq( 50 , t.find( q ).itcount() , "D" ); + +assert.eq( 50 , t.find( q ).limit(1).count() , "E" ); +assert.eq( 1 , t.find( q ).limit(1).itcount() , "F" ); + +assert.eq( 5 , t.find( q ).limit(5).size() , "G" ); +assert.eq( 5 , t.find( q ).skip(5).limit(5).size() , "H" ); +assert.eq( 2 , t.find( q ).skip(48).limit(5).size() , "I" ); + +assert.eq( 20 , t.find().limit(20).size() , "J" ); + +assert.eq( 0 , t.find().skip(120).size() , "K" ); +assert.eq( 1 , db.$cmd.findOne( { count: "count5" } )["ok"] , "L" ); +assert.eq( 1 , db.$cmd.findOne( { count: "count5", skip: 120 } )["ok"] , "M" ); diff --git a/jstests/cursor1.js b/jstests/cursor1.js new file mode 100644 index 0000000..8448752 --- /dev/null +++ b/jstests/cursor1.js @@ -0,0 +1,20 @@ + +t = db.cursor1 +t.drop(); + +big = ""; +while ( big.length < 50000 ) + big += "asdasdasdasdsdsdadsasdasdasD"; + +num = Math.ceil( 10000000 / big.length ); + +for ( var i=0; i<num; i++ ){ + t.save( { num : i , str : big } ); +} + +assert.eq( num , t.find().count() ); +assert.eq( num , t.find().itcount() ); + +assert.eq( num / 2 , t.find().limit(num/2).itcount() ); + +t.drop(); // save some space diff --git a/jstests/cursor2.js b/jstests/cursor2.js new file mode 100644 index 0000000..2389a6a --- /dev/null +++ b/jstests/cursor2.js @@ -0,0 +1,24 @@ + +/** + * test to see if the count returned from the cursor is the number of objects that would be returned + * + * BUG 884 + */ +function testCursorCountVsArrLen(dbConn) { + + var coll = dbConn.ed_db_cursor2_ccvsal; + + coll.drop(); + + coll.save({ a: 1, b : 1}); + coll.save({ a: 2, b : 1}); + coll.save({ a: 3}); + + var fromCount = coll.find({}, {b:1}).count(); + var fromArrLen = coll.find({}, {b:1}).toArray().length; + + assert(fromCount == fromArrLen, "count from cursor [" + fromCount + "] != count from arrlen [" + fromArrLen + "]"); +} + + +testCursorCountVsArrLen(db); diff --git a/jstests/cursor3.js b/jstests/cursor3.js new file mode 100644 index 0000000..d23264c --- /dev/null +++ b/jstests/cursor3.js @@ -0,0 +1,35 @@ +// Test inequality bounds combined with ordering for a single-field index. +// BUG 1079 (fixed) + +testNum = 1; + +function checkResults( expected, cursor , testNum ) { + assert.eq( expected.length, cursor.count() , "testNum: " + testNum + " A : " + tojson( cursor.toArray() ) + " " + tojson( cursor.explain() ) ); + for( i = 0; i < expected.length; ++i ) { + assert.eq( expected[ i ], cursor[ i ][ "a" ] , "testNum: " + testNum + " B" ); + } +} + +t = db.cursor3; +t.drop() + +t.save( { a: 0 } ); +t.save( { a: 1 } ); +t.save( { a: 2 } ); + +t.ensureIndex( { a: 1 } ); + + + +checkResults( [ 1 ], t.find( { a: 1 } ).sort( { a: 1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 1 ], t.find( { a: 1 } ).sort( { a: -1 } ).hint( { a: 1 } ) , testNum++ ) + +checkResults( [ 1, 2 ], t.find( { a: { $gt: 0 } } ).sort( { a: 1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 2, 1 ], t.find( { a: { $gt: 0 } } ).sort( { a: -1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 1, 2 ], t.find( { a: { $gte: 1 } } ).sort( { a: 1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 2, 1 ], t.find( { a: { $gte: 1 } } ).sort( { a: -1 } ).hint( { a: 1 } ) , testNum++ ) + +checkResults( [ 0, 1 ], t.find( { a: { $lt: 2 } } ).sort( { a: 1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 1, 0 ], t.find( { a: { $lt: 2 } } ).sort( { a: -1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 0, 1 ], t.find( { a: { $lte: 1 } } ).sort( { a: 1 } ).hint( { a: 1 } ) , testNum++ ) +checkResults( [ 1, 0 ], t.find( { a: { $lte: 1 } } ).sort( { a: -1 } ).hint( { a: 1 } ) , testNum++ ) diff --git a/jstests/cursor4.js b/jstests/cursor4.js new file mode 100644 index 0000000..b08a72f --- /dev/null +++ b/jstests/cursor4.js @@ -0,0 +1,47 @@ +// Test inequality bounds with multi-field sorting + +function checkResults( expected, cursor ) { + assert.eq( expected.length, cursor.count() ); + for( i = 0; i < expected.length; ++i ) { + assert.eq( expected[ i ].a, cursor[ i ].a ); + assert.eq( expected[ i ].b, cursor[ i ].b ); + } +} + +function testConstrainedFindMultiFieldSorting( db ) { + r = db.ed_db_cursor4_cfmfs; + r.drop(); + + entries = [ { a: 0, b: 0 }, + { a: 0, b: 1 }, + { a: 1, b: 1 }, + { a: 1, b: 1 }, + { a: 2, b: 0 } ]; + for( i = 0; i < entries.length; ++i ) + r.save( entries[ i ] ); + r.ensureIndex( { a: 1, b: 1 } ); + reverseEntries = entries.slice(); + reverseEntries.reverse(); + + checkResults( entries.slice( 2, 4 ), r.find( { a: 1, b: 1 } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( entries.slice( 2, 4 ), r.find( { a: 1, b: 1 } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + + checkResults( entries.slice( 2, 5 ), r.find( { a: { $gt: 0 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( reverseEntries.slice( 0, 3 ), r.find( { a: { $gt: 0 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( entries.slice( 0, 4 ), r.find( { a: { $lt: 2 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( reverseEntries.slice( 1, 5 ), r.find( { a: { $lt: 2 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + + checkResults( entries.slice( 4, 5 ), r.find( { a: { $gt: 0 }, b: { $lt: 1 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( entries.slice( 2, 4 ), r.find( { a: { $gt: 0 }, b: { $gt: 0 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + + checkResults( reverseEntries.slice( 0, 1 ), r.find( { a: { $gt: 0 }, b: { $lt: 1 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( reverseEntries.slice( 1, 3 ), r.find( { a: { $gt: 0 }, b: { $gt: 0 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + + checkResults( entries.slice( 0, 1 ), r.find( { a: { $lt: 2 }, b: { $lt: 1 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( entries.slice( 1, 4 ), r.find( { a: { $lt: 2 }, b: { $gt: 0 } } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + + checkResults( reverseEntries.slice( 4, 5 ), r.find( { a: { $lt: 2 }, b: { $lt: 1 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); + checkResults( reverseEntries.slice( 1, 4 ), r.find( { a: { $lt: 2 }, b: { $gt: 0 } } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); +} + +testConstrainedFindMultiFieldSorting( db ); diff --git a/jstests/cursor5.js b/jstests/cursor5.js new file mode 100644 index 0000000..6434d2b --- /dev/null +++ b/jstests/cursor5.js @@ -0,0 +1,36 @@ +// Test bounds with subobject indexes. + +function checkResults( expected, cursor ) { + assert.eq( expected.length, cursor.count() ); + for( i = 0; i < expected.length; ++i ) { + assert.eq( expected[ i ].a.b, cursor[ i ].a.b ); + assert.eq( expected[ i ].a.c, cursor[ i ].a.c ); + assert.eq( expected[ i ].a.d, cursor[ i ].a.d ); + assert.eq( expected[ i ].e, cursor[ i ].e ); + } +} + +function testBoundsWithSubobjectIndexes( db ) { + r = db.ed_db_cursor5_bwsi; + r.drop(); + + z = [ { a: { b: 1, c: 2, d: 3 }, e: 4 }, + { a: { b: 1, c: 2, d: 3 }, e: 5 }, + { a: { b: 1, c: 2, d: 4 }, e: 4 }, + { a: { b: 1, c: 2, d: 4 }, e: 5 }, + { a: { b: 2, c: 2, d: 3 }, e: 4 }, + { a: { b: 2, c: 2, d: 3 }, e: 5 } ]; + for( i = 0; i < z.length; ++i ) + r.save( z[ i ] ); + idx = { "a.d": 1, a: 1, e: -1 }; + rIdx = { "a.d": -1, a: -1, e: 1 }; + r.ensureIndex( idx ); + + checkResults( [ z[ 0 ], z[ 4 ], z[ 2 ] ], r.find( { e: 4 } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 1 ], z[ 3 ] ], r.find( { e: { $gt: 4 }, "a.b": 1 } ).sort( idx ).hint( idx ) ); + + checkResults( [ z[ 2 ], z[ 4 ], z[ 0 ] ], r.find( { e: 4 } ).sort( rIdx ).hint( idx ) ); + checkResults( [ z[ 3 ], z[ 1 ] ], r.find( { e: { $gt: 4 }, "a.b": 1 } ).sort( rIdx ).hint( idx ) ); +} + +testBoundsWithSubobjectIndexes( db ); diff --git a/jstests/cursor6.js b/jstests/cursor6.js new file mode 100644 index 0000000..9a45f93 --- /dev/null +++ b/jstests/cursor6.js @@ -0,0 +1,100 @@ +// Test different directions for compound indexes + +function eq( one, two ) { + assert.eq( one.a, two.a ); + assert.eq( one.b, two.b ); +} + +function checkExplain( e, idx, reverse, nScanned ) { + if ( !reverse ) { + if ( idx ) { + assert.eq( "BtreeCursor a_1_b_-1", e.cursor ); + } else { + assert.eq( "BasicCursor", e.cursor ); + } + } else { + if ( idx ) { + assert.eq( "BtreeCursor a_1_b_-1 reverse", e.cursor ); + } else { + assert( false ); + } + } + assert.eq( nScanned, e.nscanned ); +} + +function check( indexed ) { + var hint; + if ( indexed ) { + hint = { a: 1, b: -1 }; + } else { + hint = { $natural: 1 }; + } + + e = r.find().sort( { a: 1, b: 1 } ).hint( hint ).explain(); + checkExplain( e, indexed, false, 4 ); + f = r.find().sort( { a: 1, b: 1 } ).hint( hint ); + eq( z[ 0 ], f[ 0 ] ); + eq( z[ 1 ], f[ 1 ] ); + eq( z[ 2 ], f[ 2 ] ); + eq( z[ 3 ], f[ 3 ] ); + + e = r.find().sort( { a: 1, b: -1 } ).hint( hint ).explain(); + checkExplain( e, indexed, false, 4 ); + f = r.find().sort( { a: 1, b: -1 } ).hint( hint ); + eq( z[ 1 ], f[ 0 ] ); + eq( z[ 0 ], f[ 1 ] ); + eq( z[ 3 ], f[ 2 ] ); + eq( z[ 2 ], f[ 3 ] ); + + e = r.find().sort( { a: -1, b: 1 } ).hint( hint ).explain(); + checkExplain( e, indexed, true && indexed, 4 ); + f = r.find().sort( { a: -1, b: 1 } ).hint( hint ); + eq( z[ 2 ], f[ 0 ] ); + eq( z[ 3 ], f[ 1 ] ); + eq( z[ 0 ], f[ 2 ] ); + eq( z[ 1 ], f[ 3 ] ); + + e = r.find( { a: { $gte: 2 } } ).sort( { a: 1, b: -1 } ).hint( hint ).explain(); + checkExplain( e, indexed, false, indexed ? 2 : 4 ); + f = r.find( { a: { $gte: 2 } } ).sort( { a: 1, b: -1 } ).hint( hint ); + eq( z[ 3 ], f[ 0 ] ); + eq( z[ 2 ], f[ 1 ] ); + + e = r.find( { a : { $gte: 2 } } ).sort( { a: -1, b: 1 } ).hint( hint ).explain(); + checkExplain( e, indexed, true && indexed, indexed ? 2 : 4 ); + f = r.find( { a: { $gte: 2 } } ).sort( { a: -1, b: 1 } ).hint( hint ); + eq( z[ 2 ], f[ 0 ] ); + eq( z[ 3 ], f[ 1 ] ); + + e = r.find( { a : { $gte: 2 } } ).sort( { a: 1, b: 1 } ).hint( hint ).explain(); + checkExplain( e, indexed, false, indexed ? 2 : 4 ); + f = r.find( { a: { $gte: 2 } } ).sort( { a: 1, b: 1 } ).hint( hint ); + eq( z[ 2 ], f[ 0 ] ); + eq( z[ 3 ], f[ 1 ] ); + + e = r.find().sort( { a: -1, b: -1 } ).hint( hint ).explain(); + checkExplain( e, indexed, false, 4 ); + f = r.find().sort( { a: -1, b: -1 } ).hint( hint ); + eq( z[ 3 ], f[ 0 ] ); + eq( z[ 2 ], f[ 1 ] ); + eq( z[ 1 ], f[ 2 ] ); + eq( z[ 0 ], f[ 3 ] ); +} + +db.setProfilingLevel( 1 ); +r = db.ed_db_cursor6; +r.drop(); + +z = [ { a: 1, b: 1 }, + { a: 1, b: 2 }, + { a: 2, b: 1 }, + { a: 2, b: 2 } ]; +for( i = 0; i < z.length; ++i ) + r.save( z[ i ] ); + +r.ensureIndex( { a: 1, b: -1 } ); + +check( false ); +check( true ); + +assert.eq( "BasicCursor", r.find().sort( { a: 1, b: -1, z: 1 } ).hint( { $natural: -1 } ).explain().cursor ); diff --git a/jstests/cursor7.js b/jstests/cursor7.js new file mode 100644 index 0000000..97cfbb7 --- /dev/null +++ b/jstests/cursor7.js @@ -0,0 +1,42 @@ +// Test bounds with multiple inequalities and sorting. + +function checkResults( expected, cursor ) { + assert.eq( expected.length, cursor.count() ); + for( i = 0; i < expected.length; ++i ) { + assert.eq( expected[ i ].a, cursor[ i ].a ); + assert.eq( expected[ i ].b, cursor[ i ].b ); + } +} + +function testMultipleInequalities( db ) { + r = db.ed_db_cursor_mi; + r.drop(); + + z = [ { a: 1, b: 2 }, + { a: 3, b: 4 }, + { a: 5, b: 6 }, + { a: 7, b: 8 } ]; + for( i = 0; i < z.length; ++i ) + r.save( z[ i ] ); + idx = { a: 1, b: 1 }; + rIdx = { a: -1, b: -1 }; + r.ensureIndex( idx ); + + checkResults( [ z[ 2 ], z[ 3 ] ], r.find( { a: { $gt: 3 } } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 3, $lt: 7 } } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 1, $lt: 7, $gt: 3 } } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 3, $lt: 7, $lte: 5 } } ).sort( idx ).hint( idx ) ); + + checkResults( [ z[ 3 ], z[ 2 ] ], r.find( { a: { $gt: 3 } } ).sort( rIdx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 3, $lt: 7 } } ).sort( rIdx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 1, $lt: 7, $gt: 3 } } ).sort( rIdx ).hint( idx ) ); + checkResults( [ z[ 2 ] ], r.find( { a: { $gt: 3, $lt: 7, $lte: 5 } } ).sort( rIdx ).hint( idx ) ); + + checkResults( [ z[ 1 ], z[ 2 ] ], r.find( { a: { $gt: 1, $lt: 7, $gte: 3, $lte: 5 }, b: { $gt: 2, $lt: 8, $gte: 4, $lte: 6 } } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 2 ], z[ 1 ] ], r.find( { a: { $gt: 1, $lt: 7, $gte: 3, $lte: 5 }, b: { $gt: 2, $lt: 8, $gte: 4, $lte: 6 } } ).sort( rIdx ).hint( idx ) ); + + checkResults( [ z[ 1 ], z[ 2 ] ], r.find( { a: { $gte: 1, $lte: 7, $gt: 2, $lt: 6 }, b: { $gte: 2, $lte: 8, $gt: 3, $lt: 7 } } ).sort( idx ).hint( idx ) ); + checkResults( [ z[ 2 ], z[ 1 ] ], r.find( { a: { $gte: 1, $lte: 7, $gt: 2, $lt: 6 }, b: { $gte: 2, $lte: 8, $gt: 3, $lt: 7 } } ).sort( rIdx ).hint( idx ) ); +} + +testMultipleInequalities( db ); diff --git a/jstests/cursor8.js b/jstests/cursor8.js new file mode 100644 index 0000000..169bb5d --- /dev/null +++ b/jstests/cursor8.js @@ -0,0 +1,10 @@ +db.f.drop(); +db.f.save( {} ); +db.f.save( {} ); +db.f.save( {} ); + +db.getMongo().getDB( "admin" ).runCommand( {closeAllDatabases:1} ); + +assert.eq( 0, db.runCommand( {cursorInfo:1} ).clientCursors_size ); +assert.eq( 2, db.f.find( {} ).limit( 2 ).toArray().length ); +assert.eq( 1, db.runCommand( {cursorInfo:1} ).clientCursors_size ); diff --git a/jstests/datasize.js b/jstests/datasize.js new file mode 100644 index 0000000..396d24d --- /dev/null +++ b/jstests/datasize.js @@ -0,0 +1,28 @@ +f = db.jstests_datasize; +f.drop(); + +assert.eq( 0, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); +f.save( {qq:'c'} ); +assert.eq( 32, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); +f.save( {qq:'fg'} ); +assert.eq( 65, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); + +f.drop(); +f.ensureIndex( {qq:1} ); +assert.eq( 0, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); +f.save( {qq:'c'} ); +assert.eq( 32, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); +f.save( {qq:'fg'} ); +assert.eq( 65, db.runCommand( {datasize:"test.jstests_datasize"} ).size ); + +assert.eq( 0, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'a'}} ).ok ); + +assert.eq( 65, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'a'}, max:{qq:'z' }} ).size ); +assert.eq( 32, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'a'}, max:{qq:'d' }} ).size ); +assert.eq( 32, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'a'}, max:{qq:'d' }, keyPattern:{qq:1}} ).size ); +assert.eq( 33, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'d'}, max:{qq:'z' }, keyPattern:{qq:1}} ).size ); + +assert.eq( 0, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'c'}, max:{qq:'c' }} ).size ); +assert.eq( 32, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'c'}, max:{qq:'d' }} ).size ); + +assert.eq( 0, db.runCommand( {datasize:"test.jstests_datasize", min:{qq:'a'}, max:{qq:'d' }, keyPattern:{a:1}} ).ok ); diff --git a/jstests/date1.js b/jstests/date1.js new file mode 100644 index 0000000..ca2e616 --- /dev/null +++ b/jstests/date1.js @@ -0,0 +1,14 @@ + +t = db.date1; + + +function go( d , msg ){ + t.drop(); + t.save( { a : 1 , d : d } ); + assert.eq( d , t.findOne().d , msg ) +} + +go( new Date() , "A" ) +go( new Date( 1 ) , "B") +go( new Date( 0 ) , "C (old spidermonkey lib fails this test)") + diff --git a/jstests/dbadmin.js b/jstests/dbadmin.js new file mode 100644 index 0000000..c7b7bc8 --- /dev/null +++ b/jstests/dbadmin.js @@ -0,0 +1,22 @@ + +t = db.dbadmin; +t.save( { x : 1 } ); + +before = db._adminCommand( "serverStatus" ) +if ( before.mem.supported ){ + db._adminCommand( "closeAllDatabases" ); + after = db._adminCommand( "serverStatus" ); + assert( before.mem.mapped > after.mem.mapped , "closeAllDatabases does something before:" + tojson( before ) + " after:" + tojson( after ) ); +} +else { + print( "can't test serverStatus on this machine" ); +} + +t.save( { x : 1 } ); + +res = db._adminCommand( "listDatabases" ); +assert( res.databases.length > 0 , "listDatabases 1" ); + +print( "BEFORE: " + tojson( before ) ); +print( "AFTER : " + tojson( after ) ); +// TODO: add more tests here diff --git a/jstests/dbref1.js b/jstests/dbref1.js new file mode 100644 index 0000000..4a82766 --- /dev/null +++ b/jstests/dbref1.js @@ -0,0 +1,10 @@ + +a = db.dbref1a; +b = db.dbref1b; + +a.drop(); +b.drop(); + +a.save( { name : "eliot" } ); +b.save( { num : 1 , link : new DBPointer( "dbref1a" , a.findOne()._id ) } ); +assert.eq( "eliot" , b.findOne().link.fetch().name , "A" ); diff --git a/jstests/dbref2.js b/jstests/dbref2.js new file mode 100644 index 0000000..6ea7305 --- /dev/null +++ b/jstests/dbref2.js @@ -0,0 +1,13 @@ + +a = db.dbref2a; +b = db.dbref2b; + +a.drop(); +b.drop(); + +a.save( { name : "eliot" } ); +b.save( { num : 1 , link : new DBRef( "dbref2a" , a.findOne()._id ) } ); +assert.eq( "eliot" , b.findOne().link.fetch().name , "A" ); + +assert.eq( 1 , b.find( function(){ return this.link.fetch().name == "eliot"; } ).count() , "B" ); +assert.eq( 0 , b.find( function(){ return this.link.fetch().name == "el"; } ).count() , "C" ); diff --git a/jstests/disk/dbNoCreate.js b/jstests/disk/dbNoCreate.js new file mode 100644 index 0000000..c93267b --- /dev/null +++ b/jstests/disk/dbNoCreate.js @@ -0,0 +1,19 @@ +var baseName = "jstests_dbNoCreate"; + +var m = startMongod( "--port", "27018", "--dbpath", "/data/db/" + baseName ); + +var t = m.getDB( baseName ).t; + +var no = function( dbName ) { + assert.eq( -1, db.getMongo().getDBNames().indexOf( dbName ) ); +} + +assert.eq( 0, t.find().toArray().length ); +t.remove(); +t.update( {}, { a:1 } ); +t.drop(); + +stopMongod( 27018 ); + +var m = startMongoProgram( "mongod", "--port", "27018", "--dbpath", "/data/db/" + baseName ); +assert.eq( -1, m.getDBNames().indexOf( baseName ) ); diff --git a/jstests/disk/diskfull.js b/jstests/disk/diskfull.js new file mode 100644 index 0000000..7f75266 --- /dev/null +++ b/jstests/disk/diskfull.js @@ -0,0 +1,20 @@ +doIt = false; +files = listFiles( "/data/db" ); +for ( i in files ) { + if ( files[ i ].name == "/data/db/diskfulltest" ) { + doIt = true; + } +} + +if ( !doIt ) { + print( "path /data/db/diskfulltest/ missing, skipping diskfull test" ); + doIt = false; +} + +if ( doIt ) { + port = allocatePorts( 1 )[ 0 ]; + m = startMongoProgram( "mongod", "--port", port, "--dbpath", "/data/db/diskfulltest", "--nohttpinterface", "--bind_ip", "127.0.0.1" ); + m.getDB( "diskfulltest" ).getCollection( "diskfulltest" ).save( { a: 6 } ); + assert.soon( function() { return rawMongoProgramOutput().match( /dbexit: really exiting now/ ); }, "didn't see 'really exiting now'" ); + assert( !rawMongoProgramOutput().match( /Got signal/ ), "saw 'Got signal', not expected. Output: " + rawMongoProgramOutput() ); +} diff --git a/jstests/disk/norepeat.js b/jstests/disk/norepeat.js new file mode 100644 index 0000000..d9f1cd3 --- /dev/null +++ b/jstests/disk/norepeat.js @@ -0,0 +1,61 @@ +/* +baseName = "jstests_disk_norepeat"; + +ports = allocatePorts( 1 ); +m = startMongod( "--port", ports[ 0 ], "--deDupMem", "200", "--dbpath", "/data/db/" + baseName, "--nohttpinterface", "--bind_ip", "127.0.0.1" ); + +t = m.getDB( baseName ).getCollection( baseName ); + +t.drop(); +t.ensureIndex( { i: 1 } ); +for( i = 0; i < 3; ++i ) { + t.save( { i: i } ); +} + +c = t.find().hint( { i: 1 } ).limit( 2 ); +assert.eq( 0, c.next().i ); +t.update( { i: 0 }, { i: 3 } ); +assert.eq( 1, c.next().i ); +assert.eq( 2, c.next().i ); +assert.throws( function() { c.next() }, [], "unexpected: object found" ); + +// now force upgrade to disk storage + +t.drop(); +t.ensureIndex( { i: 1 } ); +for( i = 0; i < 10; ++i ) { + t.save( { i: i } ); +} +// apparently this means we also request 2 in subsequent getMore's +c = t.find().hint( {i:1} ).limit( 2 ); +assert.eq( 0, c.next().i ); +t.update( { i: 0 }, { i: 10 } ); +for( i = 1; i < 10; ++i ) { + if ( i == 7 ) { + t.update( { i: 6 }, { i: 11 } ); + t.update( { i: 9 }, { i: 12 } ); + } + if ( i == 9 ) { + i = 12; + } + assert.eq( i, c.next().i ); +} +assert.throws( function() { c.next() }, [], "unexpected: object found" ); + +m.getDB( "local" ).getCollectionNames().forEach( function( x ) { assert( !x.match( /^temp/ ), "temp collection found" ); } ); + +t.drop(); +m.getDB( baseName ).createCollection( baseName, { capped:true, size:100000, autoIdIndex:false } ); +t = m.getDB( baseName ).getCollection( baseName ); +t.insert( {_id:"a"} ); +t.insert( {_id:"a"} ); +t.insert( {_id:"a"} ); + +c = t.find().limit( 2 ); +assert.eq( "a", c.next()._id ); +assert.eq( "a", c.next()._id ); +assert.eq( "a", c.next()._id ); +assert( !c.hasNext() ); + +assert( t.validate().valid ); +*/ diff --git a/jstests/disk/preallocate.js b/jstests/disk/preallocate.js new file mode 100644 index 0000000..69f9a47 --- /dev/null +++ b/jstests/disk/preallocate.js @@ -0,0 +1,21 @@ +port = allocatePorts( 1 )[ 0 ] + +var baseName = "jstests_preallocate"; + +vsize = function() { + return m.getDB( "admin" ).runCommand( "serverStatus" ).mem.virtual; +} + +var m = startMongod( "--port", port, "--dbpath", "/data/db/" + baseName ); + +m.getDB( baseName ).createCollection( baseName + "1" ); + +vs = vsize(); + +stopMongod( port ); + +var m = startMongoProgram( "mongod", "--port", port, "--dbpath", "/data/db/" + baseName ); + +m.getDB( baseName ).createCollection( baseName + "2" ); + +assert.eq( vs, vsize() ); diff --git a/jstests/distinct1.js b/jstests/distinct1.js new file mode 100644 index 0000000..433e051 --- /dev/null +++ b/jstests/distinct1.js @@ -0,0 +1,25 @@ + +t = db.distinct1; +t.drop(); + +t.save( { a : 1 } ) +t.save( { a : 2 } ) +t.save( { a : 2 } ) +t.save( { a : 2 } ) +t.save( { a : 3 } ) + + +res = t.distinct( "a" ); +assert.eq( "1,2,3" , res.toString() , "A1" ); + +assert.eq( "1,2" , t.distinct( "a" , { a : { $lt : 3 } } ) , "A2" ); + +t.drop(); + +t.save( { a : { b : "a" } , c : 12 } ); +t.save( { a : { b : "b" } , c : 12 } ); +t.save( { a : { b : "c" } , c : 12 } ); +t.save( { a : { b : "c" } , c : 12 } ); + +res = t.distinct( "a.b" ); +assert.eq( "a,b,c" , res.toString() , "B1" ); diff --git a/jstests/distinct2.js b/jstests/distinct2.js new file mode 100644 index 0000000..41ee78c --- /dev/null +++ b/jstests/distinct2.js @@ -0,0 +1,13 @@ + +t = db.distinct2; +t.drop(); + +t.save({a:null}); +assert.eq( 0 , t.distinct('a.b').length , "A" ); + +t.drop(); +t.save( { a : 1 } ); +assert.eq( [1] , t.distinct( "a" ) , "B" ); +t.save( {} ) +assert.eq( [1] , t.distinct( "a" ) , "C" ); + diff --git a/jstests/drop.js b/jstests/drop.js new file mode 100644 index 0000000..b233409 --- /dev/null +++ b/jstests/drop.js @@ -0,0 +1,21 @@ +f = db.jstests_drop; + +f.drop(); + +assert.eq( 0, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "A" ); +f.save( {} ); +assert.eq( 1, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "B" ); +f.ensureIndex( {a:1} ); +assert.eq( 2, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "C" ); +assert.commandWorked( db.runCommand( {drop:"jstests_drop"} ) ); +assert.eq( 0, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "D" ); + +f.resetIndexCache(); +f.ensureIndex( {a:1} ); +assert.eq( 2, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "E" ); +assert.commandWorked( db.runCommand( {deleteIndexes:"jstests_drop",index:"*"} ) ); +assert.eq( 1, db.system.indexes.find( {ns:"test.jstests_drop"} ).count() , "G" ); + +// make sure we can still use it +f.save( {} ); +assert.eq( 1, f.find().hint( {_id:new ObjectId( "000000000000000000000000" )} ).toArray().length , "H" ); diff --git a/jstests/error1.js b/jstests/error1.js new file mode 100644 index 0000000..4043bff --- /dev/null +++ b/jstests/error1.js @@ -0,0 +1,41 @@ +db.jstests_error1.drop(); + +// test 1 +db.$cmd.findOne({reseterror:1}); +assert( db.$cmd.findOne({getlasterror:1}).err == null, "A" ); +assert( db.$cmd.findOne({getpreverror:1}).err == null, "B" ); + +db.resetError(); +assert( db.getLastError() == null, "C" ); +assert( db.getPrevError().err == null , "preverror 1" ); + +// test 2 + +db.$cmd.findOne({forceerror:1}); +assert( db.$cmd.findOne({getlasterror:1}).err != null, "D" ); +assert( db.$cmd.findOne({getpreverror:1}).err != null, "E" ); + + +assert( db.getLastError() != null, "F" ); +assert( db.getPrevError().err != null , "preverror 2" ); +assert( db.getPrevError().nPrev == 1, "G" ); + +db.jstests_error1.findOne(); +assert( db.$cmd.findOne({getlasterror:1}).err == null, "H" ); +assert( db.$cmd.findOne({getpreverror:1}).err != null, "I" ); +assert( db.$cmd.findOne({getpreverror:1}).nPrev == 2, "J" ); + +db.jstests_error1.findOne(); +assert( db.$cmd.findOne({getlasterror:1}).err == null, "K" ); +assert( db.$cmd.findOne({getpreverror:1}).err != null, "L" ); +assert( db.$cmd.findOne({getpreverror:1}).nPrev == 3, "M" ); + +db.resetError(); +db.forceError(); +db.jstests_error1.findOne(); +assert( db.getLastError() == null , "getLastError 5" ); +assert( db.getPrevError().err != null , "preverror 3" ); + +// test 3 +db.$cmd.findOne({reseterror:1}); +assert( db.$cmd.findOne({getpreverror:1}).err == null, "N" ); diff --git a/jstests/error2.js b/jstests/error2.js new file mode 100644 index 0000000..8c27d62 --- /dev/null +++ b/jstests/error2.js @@ -0,0 +1,21 @@ +// Test that client gets stack trace on failed invoke + +f = db.jstests_error2; + +f.drop(); + +f.save( {a:1} ); + +assert.throws( + function(){ + c = f.find({$where : function(){ return a() }}); + c.next(); + } +); + +assert.throws( + function(){ + db.eval( function() { return a(); } ); + } +); + diff --git a/jstests/error3.js b/jstests/error3.js new file mode 100644 index 0000000..9f7f298 --- /dev/null +++ b/jstests/error3.js @@ -0,0 +1,5 @@ + +db.runCommand( "forceerror" ); +assert.eq( "forced error" , db.getLastError() ); +db.runCommand( "switchtoclienterrors" ); +assert.isnull( db.getLastError() ); diff --git a/jstests/error4.js b/jstests/error4.js new file mode 100644 index 0000000..deb2eb2 --- /dev/null +++ b/jstests/error4.js @@ -0,0 +1,7 @@ + +t = db.error4; +t.drop() +t.insert( { _id : 1 } ) +t.insert( { _id : 1 } ) +assert.eq( 11000 , db.getLastErrorCmd().code , "A" ) + diff --git a/jstests/error5.js b/jstests/error5.js new file mode 100644 index 0000000..ed8d922 --- /dev/null +++ b/jstests/error5.js @@ -0,0 +1,8 @@ + +t = db.error5 +t.drop(); + +assert.throws( function(){ t.save( 4 ); } , "A" ); +t.save( { a : 1 } ) +assert.eq( 1 , t.count() , "B" ); + diff --git a/jstests/eval0.js b/jstests/eval0.js new file mode 100644 index 0000000..1b9bd35 --- /dev/null +++ b/jstests/eval0.js @@ -0,0 +1,3 @@ + +assert.eq( 17 , db.eval( function(){ return 11 + 6; } ) , "A" ); +assert.eq( 17 , db.eval( function( x ){ return 10 + x; } , 7 ) , "B" ); diff --git a/jstests/eval1.js b/jstests/eval1.js new file mode 100644 index 0000000..4a5ca75 --- /dev/null +++ b/jstests/eval1.js @@ -0,0 +1,17 @@ + +t = db.eval1; +t.drop(); + +t.save( { _id : 1 , name : "eliot" } ); +t.save( { _id : 2 , name : "sara" } ); + +f = function(id){ + return db["eval1"].findOne( { _id : id } ).name; +} + + +assert.eq( "eliot" , f( 1 ) , "A" ); +assert.eq( "sara" , f( 2 ) , "B" ); +assert.eq( "eliot" , db.eval( f , 1 ) , "C" ); +assert.eq( "sara" , db.eval( f , 2 ) , "D" ); + diff --git a/jstests/eval2.js b/jstests/eval2.js new file mode 100644 index 0000000..c3a7499 --- /dev/null +++ b/jstests/eval2.js @@ -0,0 +1,28 @@ + +t = db.test; +t.drop(); +t.save({a:1}); +t.save({a:1}); + +var f = db.group( + { + ns: "test", + key: { a:true}, + cond: { a:1 }, + reduce: function(obj,prev) { prev.csum++; } , + initial: { csum: 0} + } +); + +assert(f[0].a == 1 && f[0].csum == 2 , "on db" ); + +var f = t.group( + { + key: { a:true}, + cond: { a:1 }, + reduce: function(obj,prev) { prev.csum++; } , + initial: { csum: 0} + } +); + +assert(f[0].a == 1 && f[0].csum == 2 , "on coll" ); diff --git a/jstests/eval3.js b/jstests/eval3.js new file mode 100644 index 0000000..404d4d8 --- /dev/null +++ b/jstests/eval3.js @@ -0,0 +1,21 @@ + +t = db.eval3; +t.drop(); + +t.save( { _id : 1 , name : "eliot" } ); +assert.eq( 1 , t.count() , "A" ); + +function z( a , b ){ + db.eval3.save( { _id : a , name : b } ); + return b; +} + +z( 2 , "sara" ); +assert.eq( 2 , t.count() , "B" ); + +assert.eq( "eliot,sara" , t.find().toArray().map( function(z){ return z.name; } ).sort().toString() ); + +assert.eq( "joe" , db.eval( z , 3 , "joe" ) , "C" ); +assert.eq( 3 , t.count() , "D" ); + +assert.eq( "eliot,joe,sara" , t.find().toArray().map( function(z){ return z.name; } ).sort().toString() ); diff --git a/jstests/eval4.js b/jstests/eval4.js new file mode 100644 index 0000000..31d6ef0 --- /dev/null +++ b/jstests/eval4.js @@ -0,0 +1,23 @@ + +t = db.eval4; +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 2 } ); +t.save( { a : 3 } ); + +assert.eq( 3 , t.count() , "A" ); + +function f( x ){ + db.eval4.remove( { a : x } ); +} + +f( 2 ); +assert.eq( 2 , t.count() , "B" ); + +db.eval( f , 2 ); +assert.eq( 2 , t.count() , "C" ); + +db.eval( f , 3 ); +assert.eq( 1 , t.count() , "D" ); + diff --git a/jstests/eval5.js b/jstests/eval5.js new file mode 100644 index 0000000..a9223a5 --- /dev/null +++ b/jstests/eval5.js @@ -0,0 +1,23 @@ + +t = db.eval5; +t.drop(); + +t.save( { a : 1 , b : 2 , c : 3 } ); + +assert.eq( 3 , + db.eval( + function(z){ + return db.eval5.find().toArray()[0].c; + } + ) , + "something weird A" + ); + +assert.isnull( + db.eval( + function(z){ + return db.eval5.find( {} , { a : 1 } ).toArray()[0].c; + } + ), + "field spec didn't work" + ); diff --git a/jstests/eval6.js b/jstests/eval6.js new file mode 100644 index 0000000..5fe0969 --- /dev/null +++ b/jstests/eval6.js @@ -0,0 +1,15 @@ + +t = db.eval6; +t.drop(); + +t.save( { a : 1 } ); + +db.eval( + function(){ + o = db.eval6.findOne(); + o.b = 2; + db.eval6.save( o ); + } +); + +assert.eq( 2 , t.findOne().b ); diff --git a/jstests/eval7.js b/jstests/eval7.js new file mode 100644 index 0000000..45e06af --- /dev/null +++ b/jstests/eval7.js @@ -0,0 +1,3 @@ + +assert.eq( 6 , db.eval( "5 + 1" ) , "A" ) +assert.throws( function(z){ db.eval( "5 + function x; + 1" )} ); diff --git a/jstests/eval8.js b/jstests/eval8.js new file mode 100644 index 0000000..072a890 --- /dev/null +++ b/jstests/eval8.js @@ -0,0 +1,19 @@ + +t = db.eval8; +t.drop(); + +x = { a : 1 , b : 2 }; +t.save( x ); +x = t.findOne(); + +assert( x.a && x.b , "A" ); +delete x.b; + +assert( x.a && ! x.b , "B" ) +x.b = 3; +assert( x.a && x.b , "C" ); +assert.eq( 3 , x.b , "D" ); + +t.save( x ); +y = t.findOne(); +assert.eq( tojson( x ) , tojson( y ) , "E" ); diff --git a/jstests/eval9.js b/jstests/eval9.js new file mode 100644 index 0000000..cfa1f58 --- /dev/null +++ b/jstests/eval9.js @@ -0,0 +1,19 @@ + +a = [ 1 , "asd" , null , [ 2 , 3 ] , new Date() , { x : 1 } ] + +for ( var i=0; i<a.length; i++ ){ + var ret = db.eval( "function( a , i ){ return a[i]; }" , a , i ); + assert.eq( typeof( a[i] ) , typeof( ret ) , "type test" ); + assert.eq( a[i] , ret , "val test: " + typeof( a[i] ) ); +} + +db.eval9.drop(); +db.eval9.save( { a : 17 } ); + +assert.eq( 1 , db.eval( "return db.eval9.find().toArray()" ).length , "A" ); +assert.eq( 17 , db.eval( "return db.eval9.find().toArray()" )[0].a , "B" ); + +// just to make sure these things don't crash +assert( db.eval( "return db.eval9.find()" ) ); +assert( db.eval( "return db.eval9" ) ); +assert( db.eval( "return db" ) ); diff --git a/jstests/evala.js b/jstests/evala.js new file mode 100644 index 0000000..ed72582 --- /dev/null +++ b/jstests/evala.js @@ -0,0 +1,9 @@ + +t = db.evala; +t.drop() + +t.save( { x : 5 } ) + +assert.eq( 5 , db.eval( "function(){ return db.evala.findOne().x; }" ) , "A" ); +assert.eq( 5 , db.eval( "/* abc */function(){ return db.evala.findOne().x; }" ) , "B" ); + diff --git a/jstests/evalb.js b/jstests/evalb.js new file mode 100644 index 0000000..3bc3db1 --- /dev/null +++ b/jstests/evalb.js @@ -0,0 +1,14 @@ + +t = db.evalb; +t.drop(); + +t.save( { x : 3 } ); + +assert.eq( 3, db.eval( function(){ return db.evalb.findOne().x; } ) , "A" ); + +db.setProfilingLevel( 2 ); + +assert.eq( 3, db.eval( function(){ return db.evalb.findOne().x; } ) , "B" ); + +db.setProfilingLevel( 0 ); + diff --git a/jstests/exists.js b/jstests/exists.js new file mode 100644 index 0000000..28f69e8 --- /dev/null +++ b/jstests/exists.js @@ -0,0 +1,48 @@ +t = db.jstests_exists; +t.drop(); + +t.save( {} ); +t.save( {a:1} ); +t.save( {a:{b:1}} ); +t.save( {a:{b:{c:1}}} ); +t.save( {a:{b:{c:{d:null}}}} ); + +function dotest( n ){ + + assert.eq( 5, t.count() , n ); + assert.eq( 1, t.count( {a:null} ) , n ); + assert.eq( 2, t.count( {'a.b':null} ) , n ); + assert.eq( 3, t.count( {'a.b.c':null} ) , n ); + assert.eq( 5, t.count( {'a.b.c.d':null} ) , n ); + + assert.eq( 5, t.count() , n ); + assert.eq( 4, t.count( {a:{$ne:null}} ) , n ); + assert.eq( 3, t.count( {'a.b':{$ne:null}} ) , n ); + assert.eq( 2, t.count( {'a.b.c':{$ne:null}} ) , n ); + assert.eq( 0, t.count( {'a.b.c.d':{$ne:null}} ) , n ); + + assert.eq( 4, t.count( {a: {$exists:true}} ) , n ); + assert.eq( 3, t.count( {'a.b': {$exists:true}} ) , n ); + assert.eq( 2, t.count( {'a.b.c': {$exists:true}} ) , n ); + assert.eq( 1, t.count( {'a.b.c.d': {$exists:true}} ) , n ); + + assert.eq( 1, t.count( {a: {$exists:false}} ) , n ); + assert.eq( 2, t.count( {'a.b': {$exists:false}} ) , n ); + assert.eq( 3, t.count( {'a.b.c': {$exists:false}} ) , n ); + assert.eq( 4, t.count( {'a.b.c.d': {$exists:false}} ) , n ); +} + +dotest( "before index" ) +t.ensureIndex( { "a" : 1 } ) +t.ensureIndex( { "a.b" : 1 } ) +t.ensureIndex( { "a.b.c" : 1 } ) +t.ensureIndex( { "a.b.c.d" : 1 } ) +dotest( "after index" ) + +t.drop(); + +t.save( {r:[{s:1}]} ); +assert( t.findOne( {'r.s':{$exists:true}} ) ); +assert( !t.findOne( {'r.s':{$exists:false}} ) ); +assert( !t.findOne( {'r.t':{$exists:true}} ) ); +assert( t.findOne( {'r.t':{$exists:false}} ) ); diff --git a/jstests/explain1.js b/jstests/explain1.js new file mode 100644 index 0000000..6d5ac55 --- /dev/null +++ b/jstests/explain1.js @@ -0,0 +1,24 @@ + +t = db.explain1; +t.drop(); + +for ( var i=0; i<100; i++ ){ + t.save( { x : i } ); +} + +q = { x : { $gt : 50 } }; + +assert.eq( 49 , t.find( q ).count() , "A" ); +assert.eq( 49 , t.find( q ).itcount() , "B" ); +assert.eq( 20 , t.find( q ).limit(20).itcount() , "C" ); + +t.ensureIndex( { x : 1 } ); + +assert.eq( 49 , t.find( q ).count() , "D" ); +assert.eq( 49 , t.find( q ).itcount() , "E" ); +assert.eq( 20 , t.find( q ).limit(20).itcount() , "F" ); + +assert.eq( 49 , t.find(q).explain().n , "G" ); +assert.eq( 20 , t.find(q).limit(20).explain().n , "H" ); +assert.eq( 49 , t.find(q).limit(-20).explain().n , "I" ); + diff --git a/jstests/extent.js b/jstests/extent.js new file mode 100644 index 0000000..8fca699 --- /dev/null +++ b/jstests/extent.js @@ -0,0 +1,11 @@ +t = db.reclaimExtentsTest; +t.drop(); + +for ( var i=0; i<50; i++ ) { // enough iterations to break 32 bit. + db.createCollection('reclaimExtentsTest', { size : 100000000 }); + t.insert({x:1}); + assert( t.count() == 1 ); + t.drop(); +} + +db.dropDatabase(); diff --git a/jstests/find1.js b/jstests/find1.js new file mode 100644 index 0000000..93b8f60 --- /dev/null +++ b/jstests/find1.js @@ -0,0 +1,30 @@ +t = db.find1; +t.drop(); + +t.save( { a : 1 , b : "hi" } ); +t.save( { a : 2 , b : "hi" } ); + +/* very basic test of $snapshot just that we get some result */ +// we are assumign here that snapshot uses the id index; maybe one day it doesn't if so this would need to change then +assert( t.find({$query:{},$snapshot:1})[0].a == 1 , "$snapshot simple test 1" ); +var q = t.findOne(); +q.c = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; +t.save(q); // will move a:1 object to after a:2 in the file +assert( t.find({$query:{},$snapshot:1})[0].a == 1 , "$snapshot simple test 2" ); + +assert( t.findOne( { a : 1 } ).b != null , "A" ); +assert( t.findOne( { a : 1 } , { a : 1 } ).b == null , "B"); + +assert( t.find( { a : 1 } )[0].b != null , "C" ); +assert( t.find( { a : 1 } , { a : 1 } )[0].b == null , "D" ); + +id = t.findOne()._id; + +assert( t.findOne( id ) , "E" ); +assert( t.findOne( id ).a , "F" ); +assert( t.findOne( id ).b , "G" ); + +assert( t.findOne( id , { a : 1 } ).a , "H" ); +assert( ! t.findOne( id , { a : 1 } ).b , "I" ); + +assert(t.validate().valid,"not valid"); diff --git a/jstests/find2.js b/jstests/find2.js new file mode 100644 index 0000000..f722034 --- /dev/null +++ b/jstests/find2.js @@ -0,0 +1,16 @@ +// Test object id sorting. + +function testObjectIdFind( db ) { + r = db.ed_db_find2_oif; + r.drop(); + + for( i = 0; i < 3; ++i ) + r.save( {} ); + + f = r.find().sort( { _id: 1 } ); + assert.eq( 3, f.count() ); + assert( f[ 0 ]._id < f[ 1 ]._id ); + assert( f[ 1 ]._id < f[ 2 ]._id ); +} + +testObjectIdFind( db ); diff --git a/jstests/find3.js b/jstests/find3.js new file mode 100644 index 0000000..a5e4b7a --- /dev/null +++ b/jstests/find3.js @@ -0,0 +1,10 @@ +t = db.find3; +t.drop(); + +for ( i=1; i<=50; i++) + t.save( { a : i } ); + +assert.eq( 50 , t.find().toArray().length ); +assert.eq( 20 , t.find().limit(20).toArray().length ); + +assert(t.validate().valid); diff --git a/jstests/find4.js b/jstests/find4.js new file mode 100644 index 0000000..17639d3 --- /dev/null +++ b/jstests/find4.js @@ -0,0 +1,26 @@ + +t = db.find4; +t.drop(); + +t.save( { a : 1123 , b : 54332 } ); + +o = t.find( {} , {} )[0]; +assert.eq( 1123 , o.a , "A" ); +assert.eq( 54332 , o.b , "B" ); +assert( o._id.str , "C" ); + +o = t.find( {} , { a : 1 } )[0]; +assert.eq( 1123 , o.a , "D" ); +assert( o._id.str , "E" ); +assert( ! o.b , "F" ); + +o = t.find( {} , { b : 1 } )[0]; +assert.eq( 54332 , o.b , "G" ); +assert( o._id.str , "H" ); +assert( ! o.a , "I" ); + +t.drop(); +t.save( { a : 1 , b : 1 } ); +t.save( { a : 2 , b : 2 } ); +assert.eq( "1-1,2-2" , t.find().map( function(z){ return z.a + "-" + z.b } ).toString() ); +assert.eq( "1-undefined,2-undefined" , t.find( {} , { a : 1 }).map( function(z){ return z.a + "-" + z.b } ).toString() ); diff --git a/jstests/find5.js b/jstests/find5.js new file mode 100644 index 0000000..b4a2c0f --- /dev/null +++ b/jstests/find5.js @@ -0,0 +1,51 @@ + +t = db.find5; +t.drop(); + +t.save({a: 1}); +t.save({b: 5}); + +assert.eq( 2 , t.find({}, {b:1}).count(), "A"); + +function getIds( f ){ + return t.find( {} , f ).map( function(z){ return z._id; } ); +} + +assert.eq( Array.tojson( getIds( null ) ) , Array.tojson( getIds( {} ) ) , "B1 " ); +assert.eq( Array.tojson( getIds( null ) ) , Array.tojson( getIds( { a : 1 } ) ) , "B2 " ); +assert.eq( Array.tojson( getIds( null ) ) , Array.tojson( getIds( { b : 1 } ) ) , "B3 " ); +assert.eq( Array.tojson( getIds( null ) ) , Array.tojson( getIds( { c : 1 } ) ) , "B4 " ); + +x = t.find( {} , { a : 1 } )[0]; +assert.eq( 1 , x.a , "C1" ); +assert.isnull( x.b , "C2" ); + +x = t.find( {} , { a : 1 } )[1]; +assert.isnull( x.a , "C3" ); +assert.isnull( x.b , "C4" ); + +x = t.find( {} , { b : 1 } )[0]; +assert.isnull( x.a , "C5" ); +assert.isnull( x.b , "C6" ); + +x = t.find( {} , { b : 1 } )[1]; +assert.isnull( x.a , "C7" ); +assert.eq( 5 , x.b , "C8" ); + +t.drop(); + + +t.save( { a : 1 , b : { c : 2 , d : 3 , e : 4 } } ); +assert.eq( 2 , t.find( {} , { "b.c" : 1 } ).toArray()[0].b.c , "D" ); + +o = t.find( {} , { "b.c" : 1 , "b.d" : 1 } ).toArray()[0]; +assert( o.b.c , "E 1" ); +assert( o.b.d , "E 2" ); +assert( !o.b.e , "E 3" ); + +assert( ! t.find( {} , { "b.c" : 1 } ).toArray()[0].b.d , "F" ); + +t.drop(); +t.save( { a : { b : { c : 1 } } } ) +assert.eq( 1 , t.find( {} , { "a.b.c" : 1 } )[0].a.b.c , "G" ); + diff --git a/jstests/find6.js b/jstests/find6.js new file mode 100644 index 0000000..baa5969 --- /dev/null +++ b/jstests/find6.js @@ -0,0 +1,11 @@ + +t = db.find6; +t.drop(); + +t.save( { a : 1 } ) +t.save( { a : 1 , b : 1 } ) + +assert.eq( 2 , t.find().count() , "A" ); +assert.eq( 1 , t.find( { b : null } ).count() , "B" ); +assert.eq( 1 , t.find( "function() { return this.b == null; }" ).itcount() , "C" ); +assert.eq( 1 , t.find( "function() { return this.b == null; }" ).count() , "D" ); diff --git a/jstests/find_and_modify.js b/jstests/find_and_modify.js new file mode 100644 index 0000000..5e10079 --- /dev/null +++ b/jstests/find_and_modify.js @@ -0,0 +1,38 @@ +t = db.find_and_modify; +t.drop(); + +// fill db +for(var i=1; i<=10; i++) { + t.insert({priority:i, inprogress:false, value:0}); +} + +// returns old +out = t.findAndModify({update: {$set: {inprogress: true}, $inc: {value:1}}}); +assert.eq(out.value, 0); +assert.eq(out.inprogress, false); +t.update({_id: out._id}, {$set: {inprogress: false}}); + +// returns new +out = t.findAndModify({update: {$set: {inprogress: true}, $inc: {value:1}}, 'new': true}); +assert.eq(out.value, 2); +assert.eq(out.inprogress, true); +t.update({_id: out._id}, {$set: {inprogress: false}}); + +// update highest priority +out = t.findAndModify({query: {inprogress:false}, sort:{priority:-1}, update: {$set: {inprogress: true}}}); +assert.eq(out.priority, 10); +// update next highest priority +out = t.findAndModify({query: {inprogress:false}, sort:{priority:-1}, update: {$set: {inprogress: true}}}); +assert.eq(out.priority, 9); + +// remove lowest priority +out = t.findAndModify({sort:{priority:1}, remove:true}); +assert.eq(out.priority, 1); + +// remove next lowest priority +out = t.findAndModify({sort:{priority:1}, remove:1}); +assert.eq(out.priority, 2); + +// return empty obj if no matches (drivers may handle this differently) +out = t.findAndModify({query:{no_such_field:1}, remove:1}); +assert.eq(out, {}); diff --git a/jstests/fm1.js b/jstests/fm1.js new file mode 100644 index 0000000..bc60a3d --- /dev/null +++ b/jstests/fm1.js @@ -0,0 +1,12 @@ + +t = db.fm1; +t.drop(); + +t.insert({foo:{bar:1}}) +t.find({},{foo:1}).toArray(); +t.find({},{'foo.bar':1}).toArray(); +t.find({},{'baz':1}).toArray(); +t.find({},{'baz.qux':1}).toArray(); +t.find({},{'foo.qux':1}).toArray(); + + diff --git a/jstests/fm2.js b/jstests/fm2.js new file mode 100644 index 0000000..00ccdf4 --- /dev/null +++ b/jstests/fm2.js @@ -0,0 +1,9 @@ + +t = db.fm2 +t.drop(); + +t.insert( { "one" : { "two" : {"three":"four"} } } ); + +x = t.find({},{"one.two":1})[0] +assert.eq( 1 , Object.keySet( x.one ).length , "ks l 1" ); + diff --git a/jstests/fm3.js b/jstests/fm3.js new file mode 100644 index 0000000..8ccde6d --- /dev/null +++ b/jstests/fm3.js @@ -0,0 +1,37 @@ +t = db.fm3 +t.drop(); + +t.insert( {a:[{c:{e:1, f:1}}, {d:2}, 'z'], b:1} ); + + +res = t.findOne({}, {a:1}); +assert.eq(res.a, [{c:{e:1, f:1}}, {d:2}, 'z'], "one a"); +assert.eq(res.b, undefined, "one b"); + +res = t.findOne({}, {a:0}); +assert.eq(res.a, undefined, "two a"); +assert.eq(res.b, 1, "two b"); + +res = t.findOne({}, {'a.d':1}); +assert.eq(res.a, [{}, {d:2}], "three a"); +assert.eq(res.b, undefined, "three b"); + +res = t.findOne({}, {'a.d':0}); +assert.eq(res.a, [{c:{e:1, f:1}}, {}, 'z'], "four a"); +assert.eq(res.b, 1, "four b"); + +res = t.findOne({}, {'a.c':1}); +assert.eq(res.a, [{c:{e:1, f:1}}, {}], "five a"); +assert.eq(res.b, undefined, "five b"); + +res = t.findOne({}, {'a.c':0}); +assert.eq(res.a, [{}, {d:2}, 'z'], "six a"); +assert.eq(res.b, 1, "six b"); + +res = t.findOne({}, {'a.c.e':1}); +assert.eq(res.a, [{c:{e:1}}, {}], "seven a"); +assert.eq(res.b, undefined, "seven b"); + +res = t.findOne({}, {'a.c.e':0}); +assert.eq(res.a, [{c:{f:1}}, {d:2}, 'z'], "eight a"); +assert.eq(res.b, 1, "eight b"); diff --git a/jstests/fsync.js b/jstests/fsync.js new file mode 100644 index 0000000..fccd623 --- /dev/null +++ b/jstests/fsync.js @@ -0,0 +1,22 @@ +// test the lock/unlock snapshotting feature a bit + +x=db.runCommand({fsync:1,lock:1}); +assert(!x.ok,"D"); + +d=db.getSisterDB("admin"); + +x=d.runCommand({fsync:1,lock:1}); + +assert(x.ok,"C"); + +y = d.currentOp(); +assert(y.fsyncLock,"B"); + +z = d.$cmd.sys.unlock.findOne(); + +// it will take some time to unlock, and unlock does not block and wait for that +// doing a write will make us wait until db is writeable. +db.jstests_fsync.insert({x:1}); + +assert( d.currentOp().fsyncLock == null, "A" ); + diff --git a/jstests/fsync2.js b/jstests/fsync2.js new file mode 100644 index 0000000..2b5370b --- /dev/null +++ b/jstests/fsync2.js @@ -0,0 +1,15 @@ +db.fsync2.drop(); + +d = db.getSisterDB( "admin" ); + +assert.commandWorked( d.runCommand( {fsync:1, lock: 1 } ) ); + +// uncomment when fixed SERVER-519 +db.fsync2.save( {x:1} ); + +m = new Mongo( db.getMongo().host ); + +assert( m.getDB("admin").$cmd.sys.unlock.findOne().ok ); + +// uncomment when fixed SERVER-519 +assert.eq( 1, db.fsync2.count() ); diff --git a/jstests/group1.js b/jstests/group1.js new file mode 100644 index 0000000..c4147c0 --- /dev/null +++ b/jstests/group1.js @@ -0,0 +1,64 @@ +t = db.group1; +t.drop(); + +t.save( { n : 1 , a : 1 } ); +t.save( { n : 2 , a : 1 } ); +t.save( { n : 3 , a : 2 } ); +t.save( { n : 4 , a : 2 } ); +t.save( { n : 5 , a : 2 } ); + +var p = { key : { a : true } , + reduce : function(obj,prev) { prev.count++; }, + initial: { count: 0 } + }; + +res = t.group( p ); + +assert( res.length == 2 , "A" ); +assert( res[0].a == 1 , "B" ); +assert( res[0].count == 2 , "C" ); +assert( res[1].a == 2 , "D" ); +assert( res[1].count == 3 , "E" ); + +assert.eq( res , t.groupcmd( p ) , "ZZ" ); + +ret = t.groupcmd( { key : {} , reduce : p.reduce , initial : p.initial } ); +assert.eq( 1 , ret.length , "ZZ 2" ); +assert.eq( 5 , ret[0].count , "ZZ 3" ); + +ret = t.groupcmd( { key : {} , reduce : function(obj,prev){ prev.sum += obj.n } , initial : { sum : 0 } } ); +assert.eq( 1 , ret.length , "ZZ 4" ); +assert.eq( 15 , ret[0].sum , "ZZ 5" ); + +t.drop(); + +t.save( { "a" : 2 } ); +t.save( { "b" : 5 } ); +t.save( { "a" : 1 } ); +t.save( { "a" : 2 } ); + +c = {key: {a:1}, cond: {}, initial: {"count": 0}, reduce: function(obj, prev) { prev.count++; } }; + +assert.eq( t.group( c ) , t.groupcmd( c ) , "ZZZZ" ); + + +t.drop(); + +t.save( { name : { first : "a" , last : "A" } } ); +t.save( { name : { first : "b" , last : "B" } } ); +t.save( { name : { first : "a" , last : "A" } } ); + + +p = { key : { 'name.first' : true } , + reduce : function(obj,prev) { prev.count++; }, + initial: { count: 0 } + }; + +res = t.group( p ); +assert.eq( 2 , res.length , "Z1" ); +assert.eq( "a" , res[0]['name.first'] , "Z2" ) +assert.eq( "b" , res[1]['name.first'] , "Z3" ) +assert.eq( 2 , res[0].count , "Z4" ) +assert.eq( 1 , res[1].count , "Z5" ) + + diff --git a/jstests/group2.js b/jstests/group2.js new file mode 100644 index 0000000..f687e88 --- /dev/null +++ b/jstests/group2.js @@ -0,0 +1,38 @@ +t = db.group2; +t.drop(); + +t.save({a: 2}); +t.save({b: 5}); +t.save({a: 1}); + +cmd = { key: {a: 1}, + initial: {count: 0}, + reduce: function(obj, prev) { + prev.count++; + } + }; + +result = t.group(cmd); + +assert.eq(3, result.length, "A"); +assert.eq(null, result[1].a, "C"); +assert("a" in result[1], "D"); +assert.eq(1, result[2].a, "E"); + +assert.eq(1, result[0].count, "F"); +assert.eq(1, result[1].count, "G"); +assert.eq(1, result[2].count, "H"); + + +delete cmd.key +cmd["$keyf"] = function(x){ return { a : x.a }; }; +result2 = t.group( cmd ); + +assert.eq( result , result2 ); + + +delete cmd.$keyf +cmd["keyf"] = function(x){ return { a : x.a }; }; +result3 = t.group( cmd ); + +assert.eq( result , result3 ); diff --git a/jstests/group3.js b/jstests/group3.js new file mode 100644 index 0000000..afa32f1 --- /dev/null +++ b/jstests/group3.js @@ -0,0 +1,43 @@ +t = db.group2; +t.drop(); + +t.save({a: 1}); +t.save({a: 2}); +t.save({a: 3}); +t.save({a: 4}); + + +cmd = { initial: {count: 0, sum: 0}, + reduce: function(obj, prev) { + prev.count++; + prev.sum += obj.a; + }, + finalize: function(obj) { + if (obj.count){ + obj.avg = obj.sum / obj.count; + }else{ + obj.avg = 0; + } + }, + }; + +result1 = t.group(cmd); + +assert.eq(1, result1.length, "test1"); +assert.eq(10, result1[0].sum, "test1"); +assert.eq(4, result1[0].count, "test1"); +assert.eq(2.5, result1[0].avg, "test1"); + + +cmd['finalize'] = function(obj) { + if (obj.count){ + return obj.sum / obj.count; + }else{ + return 0; + } +}; + +result2 = t.group(cmd); + +assert.eq(1, result2.length, "test2"); +assert.eq(2.5, result2[0], "test2"); diff --git a/jstests/group4.js b/jstests/group4.js new file mode 100644 index 0000000..e75c0d1 --- /dev/null +++ b/jstests/group4.js @@ -0,0 +1,45 @@ + +t = db.group4 +t.drop(); + +function test( c , n ){ + var x = {}; + c.forEach( + function(z){ + assert.eq( z.count , z.values.length , n + "\t" + tojson( z ) ); + } + ); +} + +t.insert({name:'bob',foo:1}) +t.insert({name:'bob',foo:2}) +t.insert({name:'alice',foo:1}) +t.insert({name:'alice',foo:3}) +t.insert({name:'fred',foo:3}) +t.insert({name:'fred',foo:4}) + +x = t.group( + { + key: {foo:1}, + initial: {count:0,values:[]}, + reduce: function (obj, prev){ + prev.count++ + prev.values.push(obj.name) + } + } +); +test( x , "A" ); + +x = t.group( + { + key: {foo:1}, + initial: {count:0}, + reduce: function (obj, prev){ + if (!prev.values) {prev.values = [];} + prev.count++; + prev.values.push(obj.name); + } + } +); +test( x , "B" ); + diff --git a/jstests/group5.js b/jstests/group5.js new file mode 100644 index 0000000..3534fe5 --- /dev/null +++ b/jstests/group5.js @@ -0,0 +1,38 @@ + +t = db.group5; +t.drop(); + +// each group has groupnum+1 5 users +for ( var group=0; group<10; group++ ){ + for ( var i=0; i<5+group; i++ ){ + t.save( { group : "group" + group , user : i } ) + } +} + +function c( group ){ + return t.group( + { + key : { group : 1 } , + q : { group : "group" + group } , + initial : { users : {} }, + reduce : function(obj,prev){ + prev.users[obj.user] = true; // add this user to the hash + }, + finalize : function(x){ + var count = 0; + for (var key in x.users){ + count++; + } + + //replace user obj with count + //count add new field and keep users + x.users = count; + return x; + } + })[0]; // returns array +} + +assert.eq( "group0" , c(0).group , "g0" ); +assert.eq( 5 , c(0).users , "g0 a" ); +assert.eq( "group5" , c(5).group , "g5" ); +assert.eq( 10 , c(5).users , "g5 a" ); diff --git a/jstests/hint1.js b/jstests/hint1.js new file mode 100644 index 0000000..416eb4a --- /dev/null +++ b/jstests/hint1.js @@ -0,0 +1,10 @@ + +p = db.jstests_hint1; +p.drop(); + +p.save( { ts: new Date( 1 ), cls: "entry", verticals: "alleyinsider", live: true } ); +p.ensureIndex( { ts: 1 } ); + +e = p.find( { live: true, ts: { $lt: new Date( 1234119308272 ) }, cls: "entry", verticals: " alleyinsider" } ).sort( { ts: -1 } ).hint( { ts: 1 } ).explain(); +assert.eq( e.startKey.ts.getTime(), new Date( 1234119308272 ).getTime() , "A" ); +assert.eq( 0 , e.endKey.ts.getTime() , "B" ); diff --git a/jstests/id1.js b/jstests/id1.js new file mode 100644 index 0000000..9236340 --- /dev/null +++ b/jstests/id1.js @@ -0,0 +1,16 @@ + +t = db.id1 +t.drop(); + +t.save( { _id : { a : 1 , b : 2 } , x : "a" } ); +t.save( { _id : { a : 1 , b : 2 } , x : "b" } ); +t.save( { _id : { a : 3 , b : 2 } , x : "c" } ); +t.save( { _id : { a : 4 , b : 2 } , x : "d" } ); +t.save( { _id : { a : 4 , b : 2 } , x : "e" } ); +t.save( { _id : { a : 2 , b : 2 } , x : "f" } ); + +assert.eq( 4 , t.find().count() , "A" ); +assert.eq( "b" , t.findOne( { _id : { a : 1 , b : 2 } } ).x ); +assert.eq( "c" , t.findOne( { _id : { a : 3 , b : 2 } } ).x ); +assert.eq( "e" , t.findOne( { _id : { a : 4 , b : 2 } } ).x ); +assert.eq( "f" , t.findOne( { _id : { a : 2 , b : 2 } } ).x ); diff --git a/jstests/in.js b/jstests/in.js new file mode 100644 index 0000000..5442bbe --- /dev/null +++ b/jstests/in.js @@ -0,0 +1,19 @@ + +t = db.in1; +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 2 } ); + +assert.eq( 1 , t.find( { a : { $in : [ 1 ] } } ).itcount() , "A" ); +assert.eq( 1 , t.find( { a : { $in : [ 2 ] } } ).itcount() , "B" ); +assert.eq( 2 , t.find( { a : { $in : [ 1 , 2 ] } } ).itcount() , "C" ); + +t.ensureIndex( { a : 1 } ); + +assert.eq( 1 , t.find( { a : { $in : [ 1 ] } } ).itcount(), "D" ); +assert.eq( 1 , t.find( { a : { $in : [ 2 ] } } ).itcount() , "E" ); +assert.eq( 2 , t.find( { a : { $in : [ 1 , 2 ] } } ).itcount() , "F" ); + +assert.eq( 0 , t.find( { a : { $in : [] } } ).itcount() , "G" ); + diff --git a/jstests/in2.js b/jstests/in2.js new file mode 100644 index 0000000..66b90da --- /dev/null +++ b/jstests/in2.js @@ -0,0 +1,33 @@ + +t = db.in2; + +function go( name , index ){ + + t.drop(); + + t.save( { a : 1 , b : 1 } ); + t.save( { a : 1 , b : 2 } ); + t.save( { a : 1 , b : 3 } ); + + t.save( { a : 1 , b : 1 } ); + t.save( { a : 2 , b : 2 } ); + t.save( { a : 3 , b : 3 } ); + + t.save( { a : 1 , b : 1 } ); + t.save( { a : 2 , b : 1 } ); + t.save( { a : 3 , b : 1 } ); + + if ( index ) + t.ensureIndex( index ); + + assert.eq( 7 , t.find( { a : { $in : [ 1 , 2 ] } } ).count() , name + " A" ); + + assert.eq( 6 , t.find( { a : { $in : [ 1 , 2 ] } , b : { $in : [ 1 , 2 ] } } ).count() , name + " B" ); +} + +go( "no index" ); +go( "index on a" , { a : 1 } ); +go( "index on b" , { b : 1 } ); +go( "index on a&b" , { a : 1 , b : 1 } ); + + diff --git a/jstests/inc1.js b/jstests/inc1.js new file mode 100644 index 0000000..027f307 --- /dev/null +++ b/jstests/inc1.js @@ -0,0 +1,32 @@ + +t = db.inc1; +t.drop(); + +function test( num , name ){ + assert.eq( 1 , t.count() , name + " count" ); + assert.eq( num , t.findOne().x , name + " value" ); +} + +t.save( { _id : 1 , x : 1 } ); +test( 1 , "A" ); + +t.update( { _id : 1 } , { $inc : { x : 1 } } ); +test( 2 , "B" ); + +t.update( { _id : 1 } , { $inc : { x : 1 } } ); +test( 3 , "C" ); + +t.update( { _id : 2 } , { $inc : { x : 1 } } ); +test( 3 , "D" ); + +t.update( { _id : 1 } , { $inc : { x : 2 } } ); +test( 5 , "E" ); + +t.update( { _id : 1 } , { $inc : { x : -1 } } ); +test( 4 , "F" ); + +t.ensureIndex( { x : 1 } ); + +t.update( { _id : 1 } , { $inc : { x : 1 } } ); +test( 5 , "G" ); + diff --git a/jstests/inc2.js b/jstests/inc2.js new file mode 100644 index 0000000..8442f14 --- /dev/null +++ b/jstests/inc2.js @@ -0,0 +1,22 @@ + +t = db.inc1 +t.drop(); + +t.save( { _id : 1 , x : 1 } ); +t.save( { _id : 2 , x : 2 } ); +t.save( { _id : 3 , x : 3 } ); + +function order(){ + return t.find().sort( { x : 1 } ).map( function(z){ return z._id; } ); +} + +assert.eq( "1,2,3" , order() , "A" ); + +t.update( { _id : 1 } , { $inc : { x : 4 } } ); +assert.eq( "2,3,1" , order() , "B" ); + +t.ensureIndex( { x : 1 } ); +assert.eq( "2,3,1" , order() , "C" ); + +t.update( { _id : 3 } , { $inc : { x : 4 } } ); +assert.eq( "2,1,3" , order() , "D" ); diff --git a/jstests/inc3.js b/jstests/inc3.js new file mode 100644 index 0000000..baeeb19 --- /dev/null +++ b/jstests/inc3.js @@ -0,0 +1,16 @@ + +t = db.inc3; + +t.drop(); +t.save( { _id : 1 , z : 1 , a : 1 } ); +t.update( {} , { $inc : { z : 1 , a : 1 } } ); +t.update( {} , { $inc : { a : 1 , z : 1 } } ); +assert.eq( { _id : 1 , z : 3 , a : 3 } , t.findOne() , "A" ) + + +t.drop(); +t.save( { _id : 1 , a : 1 , z : 1 } ); +t.update( {} , { $inc : { z : 1 , a : 1 } } ); +t.update( {} , { $inc : { a : 1 , z : 1 } } ); +assert.eq( { _id : 1 , a : 3 , z : 3 } , t.findOne() , "B" ) + diff --git a/jstests/index1.js b/jstests/index1.js new file mode 100644 index 0000000..620f8bb --- /dev/null +++ b/jstests/index1.js @@ -0,0 +1,33 @@ + +t = db.embeddedIndexTest; + +t.remove( {} ); + +o = { name : "foo" , z : { a : 17 , b : 4} }; +t.save( o ); + +assert( t.findOne().z.a == 17 ); +assert( t.findOne( { z : { a : 17 } } ) == null); + +t.ensureIndex( { "z.a" : 1 } ); + +assert( t.findOne().z.a == 17 ); +assert( t.findOne( { z : { a : 17 } } ) == null); + +o = { name : "bar" , z : { a : 18 } }; +t.save( o ); + +assert( t.find().length() == 2 ); +assert( t.find().sort( { "z.a" : 1 } ).length() == 2 ); +assert( t.find().sort( { "z.a" : -1 } ).length() == 2 ); +// We are planning to phase out this syntax. +assert( t.find().sort( { z : { a : 1 } } ).length() == 2 ); +assert( t.find().sort( { z : { a: -1 } } ).length() == 2 ); + +// +// TODO - these don't work yet as indexing on x.y doesn't work yet +// +//assert( t.find().sort( { z : { a : 1 } } )[0].name == "foo" ); +//assert( t.find().sort( { z : { a : -1 } } )[1].name == "bar" ); + +assert(t.validate().valid); diff --git a/jstests/index10.js b/jstests/index10.js new file mode 100644 index 0000000..105fcc1 --- /dev/null +++ b/jstests/index10.js @@ -0,0 +1,24 @@ +// unique index, drop dups + +t = db.jstests_index10; +t.drop(); + +t.save( {i:1} ); +t.save( {i:2} ); +t.save( {i:1} ); +t.save( {i:3} ); +t.save( {i:1} ); + +t.ensureIndex( {i:1} ); +assert.eq( 5, t.count() ); +t.dropIndexes(); +t.ensureIndex( {i:1}, true ); +assert.eq( 1, db.system.indexes.count( {ns:"test.jstests_index10" } ) ); // only id index +// t.dropIndexes(); + +t.ensureIndex( {i:1}, [ true, true ] ); +assert.eq( 3, t.count() ); +assert.eq( 1, t.count( {i:1} ) ); + +t.ensureIndex( {j:1}, [ true, true ] ); +assert.eq( 1, t.count() ); diff --git a/jstests/index2.js b/jstests/index2.js new file mode 100644 index 0000000..b54abca --- /dev/null +++ b/jstests/index2.js @@ -0,0 +1,40 @@ +/* test indexing where the key is an embedded object. + */ + +t = db.embeddedIndexTest2; + +t.drop(); +assert( t.findOne() == null ); + +o = { name : "foo" , z : { a : 17 } }; +p = { name : "foo" , z : { a : 17 } }; +q = { name : "barrr" , z : { a : 18 } }; +r = { name : "barrr" , z : { k : "zzz", L:[1,2] } }; + +t.save( o ); + +assert( t.findOne().z.a == 17 ); + +t.save( p ); +t.save( q ); + +assert( t.findOne({z:{a:17}}).z.a==17 ); +assert( t.find({z:{a:17}}).length() == 2 ); +assert( t.find({z:{a:18}}).length() == 1 ); + +t.save( r ); + +assert( t.findOne({z:{a:17}}).z.a==17 ); +assert( t.find({z:{a:17}}).length() == 2 ); +assert( t.find({z:{a:18}}).length() == 1 ); + +t.ensureIndex( { z : 1 } ); + +assert( t.findOne({z:{a:17}}).z.a==17 ); +assert( t.find({z:{a:17}}).length() == 2 ); +assert( t.find({z:{a:18}}).length() == 1 ); + +assert( t.find().sort( { z : 1 } ).length() == 4 ); +assert( t.find().sort( { z : -1 } ).length() == 4 ); + +assert(t.validate().valid); diff --git a/jstests/index3.js b/jstests/index3.js new file mode 100644 index 0000000..8013946 --- /dev/null +++ b/jstests/index3.js @@ -0,0 +1,16 @@ + + +t = db.index3; +t.drop(); + +assert( t.getIndexes().length == 0 ); + +t.ensureIndex( { name : 1 } ); + +t.save( { name : "a" } ); + +t.ensureIndex( { name : 1 } ); + +assert( t.getIndexes().length == 2 ); + +assert(t.validate().valid); diff --git a/jstests/index4.js b/jstests/index4.js new file mode 100644 index 0000000..9dd731c --- /dev/null +++ b/jstests/index4.js @@ -0,0 +1,33 @@ +// index4.js + + +t = db.index4; +t.drop(); + +t.save( { name : "alleyinsider" , + instances : [ + { pool : "prod1" } , + { pool : "dev1" } + ] + } ); + +t.save( { name : "clusterstock" , + instances : [ + { pool : "dev1" } + ] + } ); + + +// this should fail, not allowed -- we confirm that. +t.ensureIndex( { instances : { pool : 1 } } ); +assert.eq( 0, db.system.indexes.find( {ns:"test.index4",name:{$ne:"_id_"}} ).count(), "no indexes should be here yet"); + +t.ensureIndex( { "instances.pool" : 1 } ); + +sleep( 10 ); + +a = t.find( { instances : { pool : "prod1" } } ); +assert( a.length() == 1, "len1" ); +assert( a[0].name == "alleyinsider", "alley" ); + +assert(t.validate().valid, "valid" ); diff --git a/jstests/index5.js b/jstests/index5.js new file mode 100644 index 0000000..841ac12 --- /dev/null +++ b/jstests/index5.js @@ -0,0 +1,24 @@ +// index5.js - test reverse direction index + +function validate() { + assert.eq( 2, t.find().count() ); + f = t.find().sort( { a: 1 } ); + assert.eq( 2, t.count() ); + assert.eq( 1, f[ 0 ].a ); + assert.eq( 2, f[ 1 ].a ); + r = t.find().sort( { a: -1 } ); + assert.eq( 2, r.count() ); + assert.eq( 2, r[ 0 ].a ); + assert.eq( 1, r[ 1 ].a ); +} + +t = db.index5; +t.drop(); + +t.save( { a: 1 } ); +t.save( { a: 2 } ); + +validate(); + +t.ensureIndex( { a: -1 } ); +validate(); diff --git a/jstests/index6.js b/jstests/index6.js new file mode 100644 index 0000000..7514aca --- /dev/null +++ b/jstests/index6.js @@ -0,0 +1,8 @@ +// index6.js Test indexes on array subelements. + +r = db.ed.db.index5; +r.drop(); + +r.save( { comments : [ { name : "eliot", foo : 1 } ] } ); +r.ensureIndex( { "comments.name": 1 } ); +assert( r.findOne( { "comments.name": "eliot" } ) ); diff --git a/jstests/index7.js b/jstests/index7.js new file mode 100644 index 0000000..cf5050b --- /dev/null +++ b/jstests/index7.js @@ -0,0 +1,67 @@ +// index7.js Test that we use an index when and only when we expect to. + +function index( q ) { + assert( q.explain().cursor.match( /^BtreeCursor/ ) , "index assert" ); +} + +function noIndex( q ) { + assert( q.explain().cursor.match( /^BasicCursor/ ) , "noIndex assert" ); +} + +function start( k, q ) { + var s = q.explain().startKey; + assert.eq( k.a, s.a ); + assert.eq( k.b, s.b ); +} + +function end( k, q ) { + var e = q.explain().endKey; + assert.eq( k.a, e.a ); + assert.eq( k.b, e.b ); +} + +function both( k, q ) { + start( k, q ); + end( k, q ); +} + +f = db.ed_db_index7; +f.drop(); + +f.save( { a : 5 } ) +f.ensureIndex( { a: 1 } ); +index( f.find( { a: 5 } ).sort( { a: 1 } ).hint( { a: 1 } ) ); +noIndex( f.find( { a: 5 } ).sort( { a: 1 } ).hint( { $natural: 1 } ) ); +f.drop(); + +f.ensureIndex( { a: 1, b: 1 } ); +assert.eq( 1, f.find( { a: 1 } ).hint( { a: 1, b: 1 } ).explain().startKey.a ); +assert.eq( 1, f.find( { a: 1 } ).hint( { a: 1, b: 1 } ).explain().endKey.a ); +assert.eq( 1, f.find( { a: 1, c: 1 } ).hint( { a: 1, b: 1 } ).explain().startKey.a ); +assert.eq( 1, f.find( { a: 1, c: 1 } ).hint( { a: 1, b: 1 } ).explain().endKey.a ); +assert.eq( null, f.find( { a: 1, c: 1 } ).hint( { a: 1, b: 1 } ).explain().startKey.c ); +assert.eq( null, f.find( { a: 1, c: 1 } ).hint( { a: 1, b: 1 } ).explain().endKey.c ); + +start( { a: "a", b: 1 }, f.find( { a: /^a/, b: 1 } ).hint( { a: 1, b: 1 } ) ); +start( { a: "a", b: 1 }, f.find( { a: /^a/, b: 1 } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); +start( { a: "b", b: 1 }, f.find( { a: /^a/, b: 1 } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); +start( { a: "a", b: 1 }, f.find( { b: 1, a: /^a/ } ).hint( { a: 1, b: 1 } ) ); +end( { a: "b", b: 1 }, f.find( { a: /^a/, b: 1 } ).hint( { a: 1, b: 1 } ) ); +end( { a: "b", b: 1 }, f.find( { a: /^a/, b: 1 } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); +end( { a: "a", b: 1 }, f.find( { a: /^a/, b: 1 } ).sort( { a: -1, b: -1 } ).hint( { a: 1, b: 1 } ) ); +end( { a: "b", b: 1 }, f.find( { b: 1, a: /^a/ } ).hint( { a: 1, b: 1 } ) ); + +start( { a: "z", b: 1 }, f.find( { a: /^z/, b: 1 } ).hint( { a: 1, b: 1 } ) ); +end( { a: "{", b: 1 }, f.find( { a: /^z/, b: 1 } ).hint( { a: 1, b: 1 } ) ); + +start( { a: "az", b: 1 }, f.find( { a: /^az/, b: 1 } ).hint( { a: 1, b: 1 } ) ); +end( { a: "a{", b: 1 }, f.find( { a: /^az/, b: 1 } ).hint( { a: 1, b: 1 } ) ); + +both( { a: 1, b: 3 }, f.find( { a: 1, b: 3 } ).hint( { a: 1, b: 1 } ) ); + +both( { a: 1, b: 2 }, f.find( { a: { $gte: 1, $lte: 1 }, b: 2 } ).hint( { a: 1, b: 1 } ) ); +both( { a: 1, b: 2 }, f.find( { a: { $gte: 1, $lte: 1 }, b: 2 } ).sort( { a: 1, b: 1 } ).hint( { a: 1, b: 1 } ) ); + +f.drop(); +f.ensureIndex( { b: 1, a: 1 } ); +both( { a: 1, b: 3 }, f.find( { a: 1, b: 3 } ).hint( { b: 1, a: 1 } ) ); diff --git a/jstests/index8.js b/jstests/index8.js new file mode 100644 index 0000000..09a0645 --- /dev/null +++ b/jstests/index8.js @@ -0,0 +1,59 @@ +// Test key uniqueness + +t = db.jstests_index8; +t.drop(); + +t.ensureIndex( { a: 1 } ); +t.ensureIndex( { b: 1 }, true ); +t.ensureIndex( { c: 1 }, [ false, "cIndex" ] ); + +checkIndexes = function( num ) { +// printjson( db.system.indexes.find( { ns: "test.jstests_index8" } ).toArray() ); + indexes = db.system.indexes.find( { ns: "test.jstests_index8" } ).sort( { key: 1 } ); + assert( !indexes[ 0 ].unique , "A" + num ); + assert( indexes[ 1 ].unique , "B" + num ); + assert( !indexes[ 2 ].unique , "C" + num ); + assert.eq( "cIndex", indexes[ 2 ].name , "D" + num ); +} + +checkIndexes( 1 ); + +t.reIndex(); +checkIndexes( 2 ); + +t.save( { a: 2, b: 1 } ); +t.save( { a: 2 } ); +assert.eq( 2, t.find().count() ); + +t.save( { b: 4 } ); +t.save( { b: 4 } ); +assert.eq( 3, t.find().count() ); +assert.eq( 3, t.find().hint( {c:1} ).toArray().length ); +assert.eq( 3, t.find().hint( {b:1} ).toArray().length ); +assert.eq( 3, t.find().hint( {a:1} ).toArray().length ); + +t.drop(); +t.ensureIndex( { a: 1, b: -1 }, true ); +t.save( { a: 2, b: 3 } ); +t.save( { a: 2, b: 3 } ); +t.save( { a: 2, b: 4 } ); +t.save( { a: 1, b: 3 } ); +assert.eq( 3, t.find().count() ); + +t.drop(); +t.ensureIndex( { a: 1 }, true ); +t.save( { a: [ 2, 3 ] } ); +t.save( { a: 2 } ); +assert.eq( 1, t.find().count() ); + +t.drop(); +t.ensureIndex( { a: 1 }, true ); +t.save( { a: 2 } ); +t.save( { a: [ 1, 2, 3 ] } ); +t.save( { a: [ 3, 2, 1 ] } ); +assert.eq( 1, t.find().sort( { a: 1 } ).hint( { a: 1 } ).toArray().length ); +assert.eq( 1, t.find().sort( { a: -1 } ).hint( { a: 1 } ).toArray().length ); + +assert.eq( t._indexSpec( { x : 1 } , true ) , t._indexSpec( { x : 1 } , [ true ] ) , "spec 1" ); +assert.eq( t._indexSpec( { x : 1 } , "eliot" ) , t._indexSpec( { x : 1 } , [ "eliot" ] ) , "spec 2" ); + diff --git a/jstests/index9.js b/jstests/index9.js new file mode 100644 index 0000000..c832783 --- /dev/null +++ b/jstests/index9.js @@ -0,0 +1,17 @@ +t = db.jstests_index9; + +t.drop(); +db.createCollection( "jstests_index9", {autoIndexId:false} ); +t.createIndex( { _id:1 } ); +assert.eq( 1, db.system.indexes.count( {ns: "test.jstests_index9"} ) ); +t.createIndex( { _id:1 } ); +assert.eq( 1, db.system.indexes.count( {ns: "test.jstests_index9"} ) ); + +t.drop(); +t.createIndex( { _id:1 } ); +assert.eq( 1, db.system.indexes.count( {ns: "test.jstests_index9"} ) ); + +t.drop(); +t.save( {a:1} ); +t.createIndex( { _id:1 } ); +assert.eq( 1, db.system.indexes.count( {ns: "test.jstests_index9"} ) ); diff --git a/jstests/index_check1.js b/jstests/index_check1.js new file mode 100644 index 0000000..7113dff --- /dev/null +++ b/jstests/index_check1.js @@ -0,0 +1,31 @@ + +db.somecollection.drop(); + +assert(db.system.namespaces.find({name:/somecollection/}).length() == 0, 1); + +db.somecollection.save({a:1}); + +assert(db.system.namespaces.find({name:/somecollection/}).length() == 2, 2); + +db.somecollection.ensureIndex({a:1}); + +var z = db.system.namespaces.find({name:/somecollection/}).length(); +assert( z >= 1 , 3 ); + +if( z == 1 ) + print("warning: z==1, should only happen with alternate storage engines"); + +db.somecollection.drop(); + +assert(db.system.namespaces.find({name:/somecollection/}).length() == 0, 4); + +db.somecollection.save({a:1}); + +assert(db.system.namespaces.find({name:/somecollection/}).length() == 2, 5); + +db.somecollection.ensureIndex({a:1}); + +var x = db.system.namespaces.find({name:/somecollection/}).length(); +assert( x == 2 || x == z, 6); + +assert(db.somecollection.validate().valid, 7); diff --git a/jstests/index_check2.js b/jstests/index_check2.js new file mode 100644 index 0000000..56796ac --- /dev/null +++ b/jstests/index_check2.js @@ -0,0 +1,41 @@ + +t = db.index_check2; +t.drop(); + +for ( var i=0; i<1000; i++ ){ + var a = []; + for ( var j=1; j<5; j++ ){ + a.push( "tag" + ( i * j % 50 )); + } + t.save( { num : i , tags : a } ); +} + +q1 = { tags : "tag6" }; +q2 = { tags : "tag12" }; +q3 = { tags : { $all : [ "tag6" , "tag12" ] } } + +assert.eq( 120 , t.find( q1 ).itcount() , "q1 a"); +assert.eq( 120 , t.find( q2 ).itcount() , "q2 a" ); +assert.eq( 60 , t.find( q3 ).itcount() , "q3 a"); + +t.ensureIndex( { tags : 1 } ); + +assert.eq( 120 , t.find( q1 ).itcount() , "q1 a"); +assert.eq( 120 , t.find( q2 ).itcount() , "q2 a" ); +assert.eq( 60 , t.find( q3 ).itcount() , "q3 a"); + +assert.eq( "BtreeCursor tags_1" , t.find( q1 ).explain().cursor , "e1" ); +assert.eq( "BtreeCursor tags_1" , t.find( q2 ).explain().cursor , "e2" ); +assert.eq( "BtreeCursor tags_1" , t.find( q3 ).explain().cursor , "e3" ); + +scanned1 = t.find(q1).explain().nscanned; +scanned2 = t.find(q2).explain().nscanned; +scanned3 = t.find(q3).explain().nscanned; + +//print( "scanned1: " + scanned1 + " scanned2: " + scanned2 + " scanned3: " + scanned3 ); + +// $all should just iterate either of the words +assert( scanned3 <= Math.max( scanned1 , scanned2 ) , "$all makes query optimizer not work well" ); + +exp3 = t.find( q3 ).explain(); +assert.eq( exp3.startKey, exp3.endKey, "$all range not a single key" ); diff --git a/jstests/index_check3.js b/jstests/index_check3.js new file mode 100644 index 0000000..55515af --- /dev/null +++ b/jstests/index_check3.js @@ -0,0 +1,63 @@ + + +t = db.index_check3; +t.drop(); + + + +t.save( { a : 1 } ); +t.save( { a : 2 } ); +t.save( { a : 3 } ); +t.save( { a : "z" } ); + +assert.eq( 1 , t.find( { a : { $lt : 2 } } ).itcount() , "A" ); +assert.eq( 1 , t.find( { a : { $gt : 2 } } ).itcount() , "B" ); + +t.ensureIndex( { a : 1 } ); + +assert.eq( 1 , t.find( { a : { $lt : 2 } } ).itcount() , "C" ); +assert.eq( 1 , t.find( { a : { $gt : 2 } } ).itcount() , "D" ); + +t.drop(); + +for ( var i=0; i<100; i++ ){ + var o = { i : i }; + if ( i % 2 == 0 ) + o.foo = i; + t.save( o ); +} + +t.ensureIndex( { foo : 1 } ); + +//printjson( t.find( { foo : { $lt : 50 } } ).explain() ); +assert.gt( 30 , t.find( { foo : { $lt : 50 } } ).explain().nscanned , "lt" ); +//printjson( t.find( { foo : { $gt : 50 } } ).explain() ); +assert.gt( 30 , t.find( { foo : { $gt : 50 } } ).explain().nscanned , "gt" ); + + +t.drop(); +t.save( {i:'a'} ); +for( var i=0; i < 10; ++i ) { + t.save( {} ); +} + +t.ensureIndex( { i : 1 } ); + +//printjson( t.find( { i : { $lte : 'a' } } ).explain() ); +assert.gt( 3 , t.find( { i : { $lte : 'a' } } ).explain().nscanned , "lte" ); +//printjson( t.find( { i : { $gte : 'a' } } ).explain() ); +// bug SERVER-99 +assert.gt( 3 , t.find( { i : { $gte : 'a' } } ).explain().nscanned , "gte" ); +assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).count() , "gte a" ); +assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).itcount() , "gte b" ); +assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).sort( { i : 1 } ).count() , "gte c" ); +assert.eq( 1 , t.find( { i : { $gte : 'a' } } ).sort( { i : 1 } ).itcount() , "gte d" ); + +t.save( { i : "b" } ); + +assert.gt( 3 , t.find( { i : { $gte : 'a' } } ).explain().nscanned , "gte" ); +assert.eq( 2 , t.find( { i : { $gte : 'a' } } ).count() , "gte a2" ); +assert.eq( 2 , t.find( { i : { $gte : 'a' } } ).itcount() , "gte b2" ); +assert.eq( 2 , t.find( { i : { $gte : 'a' , $lt : MaxKey } } ).itcount() , "gte c2" ); +assert.eq( 2 , t.find( { i : { $gte : 'a' , $lt : MaxKey } } ).sort( { i : -1 } ).itcount() , "gte d2" ); +assert.eq( 2 , t.find( { i : { $gte : 'a' , $lt : MaxKey } } ).sort( { i : 1 } ).itcount() , "gte e2" ); diff --git a/jstests/index_check5.js b/jstests/index_check5.js new file mode 100644 index 0000000..90ac301 --- /dev/null +++ b/jstests/index_check5.js @@ -0,0 +1,17 @@ + +t = db.index_check5 +t.drop(); + +t.save( { "name" : "Player1" , + "scores" : [{"level" : 1 , "score" : 100}, + {"level" : 2 , "score" : 50}], + "total" : 150 } ); +t.save( { "name" : "Player2" , + "total" : 90 , + "scores" : [ {"level" : 1 , "score" : 90}, + {"level" : 2 , "score" : 0} ] + } ); + +assert.eq( 2 , t.find( { "scores.level": 2, "scores.score": {$gt:30} } ).itcount() , "A" ); +t.ensureIndex( { "scores.level" : 1 , "scores.score" : 1 } ); +assert.eq( 1 , t.find( { "scores.level": 2, "scores.score": {$gt:30} } ).itcount() , "B" ); diff --git a/jstests/index_check6.js b/jstests/index_check6.js new file mode 100644 index 0000000..71e6420 --- /dev/null +++ b/jstests/index_check6.js @@ -0,0 +1,17 @@ + +t = db.index_check6; +t.drop(); + +t.ensureIndex( { age : 1 , rating : 1 } ); + +for ( var age=10; age<50; age++ ){ + for ( var rating=0; rating<10; rating++ ){ + t.save( { age : age , rating : rating } ); + } +} + +assert.eq( 10 , t.find( { age : 30 } ).explain().nscanned , "A" ); +assert.eq( 20 , t.find( { age : { $gte : 29 , $lte : 30 } } ).explain().nscanned , "B" ); + +//assert.eq( 2 , t.find( { age : { $gte : 29 , $lte : 30 } , rating : 5 } ).explain().nscanned , "C" ); // SERVER-371 +//assert.eq( 4 , t.find( { age : { $gte : 29 , $lte : 30 } , rating : { $gte : 4 , $lte : 5 } } ).explain().nscanned , "D" ); // SERVER-371 diff --git a/jstests/index_check7.js b/jstests/index_check7.js new file mode 100644 index 0000000..68102d6 --- /dev/null +++ b/jstests/index_check7.js @@ -0,0 +1,15 @@ + +t = db.index_check7 +t.drop() + +for ( var i=0; i<100; i++ ) + t.save( { x : i } ) + +t.ensureIndex( { x : 1 } ) +assert.eq( 1 , t.find( { x : 27 } ).explain().nscanned , "A" ) + +t.ensureIndex( { x : -1 } ) +assert.eq( 1 , t.find( { x : 27 } ).explain().nscanned , "B" ) + +assert.eq( 41 , t.find( { x : { $gt : 59 } } ).explain().nscanned , "C" ); + diff --git a/jstests/index_many.js b/jstests/index_many.js new file mode 100644 index 0000000..9960afa --- /dev/null +++ b/jstests/index_many.js @@ -0,0 +1,34 @@ +t = db.many; + +t.drop(); +db.many2.drop(); + +t.save({x:9}); +t.save({x:19}); + +x = 2; +while( x < 60 ) { + patt={}; + patt[x] = 1; + if( x == 20 ) + patt = { x : 1 }; + t.ensureIndex(patt); + x++; +} + +// print( tojson(db.getLastErrorObj()) ); +assert( db.getLastError(), "should have an error 'too many indexes'" ); + +// 40 is the limit currently + +// print( t.getIndexes().length == 40, "40" ); + +assert( t.getIndexes().length == 40, "40" ); + +assert( t.find({x:9}).length() == 1, "b" ) ; + +t.renameCollection( "many2" ); + +assert( t.find({x:9}).length() == 0, "c" ) ; + +assert( db.many2.find({x:9}).length() == 1, "d" ) ; diff --git a/jstests/indexa.js b/jstests/indexa.js new file mode 100644 index 0000000..7602183 --- /dev/null +++ b/jstests/indexa.js @@ -0,0 +1,22 @@ +// unique index constraint test for updates +// case where object doesn't grow tested here + +t = db.indexa; +t.drop(); + +t.ensureIndex( { x:1 }, true ); + +t.insert( { 'x':'A' } ); +t.insert( { 'x':'B' } ); +t.insert( { 'x':'A' } ); + +assert.eq( 2 , t.count() , "indexa 1" ); + +t.update( {x:'B'}, { x:'A' } ); + +a = t.find().toArray(); +u = Array.unique( a.map( function(z){ return z.x } ) ); +assert.eq( 2 , t.count() , "indexa 2" ); + +assert( a.length == u.length , "unique index update is broken" ); + diff --git a/jstests/indexapi.js b/jstests/indexapi.js new file mode 100644 index 0000000..ae76ec7 --- /dev/null +++ b/jstests/indexapi.js @@ -0,0 +1,40 @@ + +t = db.indexapi; +t.drop(); + +key = { x : 1 }; + +c = { ns : t._fullName , key : key , name : t._genIndexName( key ) }; +assert.eq( c , t._indexSpec( { x : 1 } ) , "A" ); + +c.name = "bob"; +assert.eq( c , t._indexSpec( { x : 1 } , "bob" ) , "B" ); + +c.name = t._genIndexName( key ); +assert.eq( c , t._indexSpec( { x : 1 } ) , "C" ); + +c.unique = true; +assert.eq( c , t._indexSpec( { x : 1 } , true ) , "D" ); +assert.eq( c , t._indexSpec( { x : 1 } , [ true ] ) , "E" ); +assert.eq( c , t._indexSpec( { x : 1 } , { unique : true } ) , "F" ); + +c.dropDups = true; +assert.eq( c , t._indexSpec( { x : 1 } , [ true , true ] ) , "G" ); +assert.eq( c , t._indexSpec( { x : 1 } , { unique : true , dropDups : true } ) , "F" ); + +t.ensureIndex( { x : 1 } , { unique : true } ); +idx = t.getIndexes(); +assert.eq( 2 , idx.length , "M1" ); +assert.eq( key , idx[1].key , "M2" ); +assert( idx[1].unique , "M3" ); + +t.drop(); +t.ensureIndex( { x : 1 } , { unique : 1 } ); +idx = t.getIndexes(); +assert.eq( 2 , idx.length , "M1" ); +assert.eq( key , idx[1].key , "M2" ); +assert( idx[1].unique , "M3" ); +printjson( idx ); + +db.system.indexes.insert( { ns : "test" , key : { x : 1 } , name : "x" } ); +assert( db.getLastError().indexOf( "invalid" ) >= 0 , "Z1" ); diff --git a/jstests/indexb.js b/jstests/indexb.js new file mode 100644 index 0000000..5507fee --- /dev/null +++ b/jstests/indexb.js @@ -0,0 +1,30 @@ +// unique index test for a case where the object grows +// and must move + +// see indexa.js for the test case for an update with dup id check +// when it doesn't move + + +t = db.indexb;t = db.indexb; +db.dropDatabase(); +t.drop(); +t.ensureIndex({a:1},true); + +t.insert({a:1}); + +x = { a : 2 }; +t.save(x); + +{ + + assert( t.count() == 2, "count wrong B"); + + x.a = 1; + x.filler = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + t.save(x); // should fail, not unique. + + assert( t.count() == 2,"count wrong" ); + assert( t.find({a:1}).count() == 1,"bfail1" ); + assert( t.find({a:2}).count() == 1,"bfail2" ); + +} diff --git a/jstests/indexc.js b/jstests/indexc.js new file mode 100644 index 0000000..b099e2d --- /dev/null +++ b/jstests/indexc.js @@ -0,0 +1,20 @@ + +t = db.indexc; +t.drop(); + +for ( var i=1; i<100; i++ ){ + var d = new Date( ( new Date() ).getTime() + i ); + t.save( { a : i , ts : d , cats : [ i , i + 1 , i + 2 ] } ); + if ( i == 51 ) + mid = d; +} + +assert.eq( 50 , t.find( { ts : { $lt : mid } } ).itcount() , "A" ); +assert.eq( 50 , t.find( { ts : { $lt : mid } } ).sort( { ts : 1 } ).itcount() , "B" ); + +t.ensureIndex( { ts : 1 , cats : 1 } ); +t.ensureIndex( { cats : 1 } ); + +// multi-key bug was firing here (related to getsetdup()): +assert.eq( 50 , t.find( { ts : { $lt : mid } } ).itcount() , "C" ); +assert.eq( 50 , t.find( { ts : { $lt : mid } } ).sort( { ts : 1 } ).itcount() , "D" ); diff --git a/jstests/indexd.js b/jstests/indexd.js new file mode 100644 index 0000000..33246ad --- /dev/null +++ b/jstests/indexd.js @@ -0,0 +1,10 @@ + +t = db.indexd; +t.drop(); + +t.save( { a : 1 } ); +t.ensureIndex( { a : 1 } ); +assert.throws( function(){ db.indexd.$_id_.drop(); } ); +assert( t.drop() ); + +//db.indexd.$_id_.remove({}); diff --git a/jstests/indexe.js b/jstests/indexe.js new file mode 100644 index 0000000..3170757 --- /dev/null +++ b/jstests/indexe.js @@ -0,0 +1,21 @@ + +t = db.indexe; +t.drop(); + +num = 100000; + +for ( i=0; i<num; i++){ + t.insert( { a : "b" } ); +} + +assert.eq( num , t.find().count() ,"A1" ); +assert.eq( num , t.find( { a : "b" } ).count() , "B1" ); +assert.eq( num , t.find( { a : "b" } ).itcount() , "C1" ); + +t.ensureIndex( { a : "b" } ); + +assert.eq( num , t.find().count() ,"A2" ); +assert.eq( num , t.find().sort( { a : 1 } ).count() , "A2a" ); +assert.eq( num , t.find().sort( { a : "b" } ).itcount() , "A2b" ); +assert.eq( num , t.find( { a : "b" } ).count() , "B2" ); +assert.eq( num , t.find( { a : "b" } ).itcount() , "C3" ); diff --git a/jstests/indexf.js b/jstests/indexf.js new file mode 100644 index 0000000..d65e7b1 --- /dev/null +++ b/jstests/indexf.js @@ -0,0 +1,13 @@ + +t = db.indexf +t.drop(); + +t.ensureIndex( { x : 1 } ); + +t.save( { x : 2 } ); +t.save( { y : 3 } ); +t.save( { x : 4 } ); + +assert.eq( 2 , t.findOne( { x : 2 } ).x , "A1" ); +assert.eq( 3 , t.findOne( { x : null } ).y , "A2" ); +assert.eq( 4 , t.findOne( { x : 4 } ).x , "A3" ); diff --git a/jstests/jni1.js b/jstests/jni1.js new file mode 100644 index 0000000..9e33287 --- /dev/null +++ b/jstests/jni1.js @@ -0,0 +1,12 @@ + + +t = db.jni1; +t.remove( {} ); + +t.save( { z : 1 } ); +t.save( { z : 2 } ); +assert( 2 == t.find().length() ); +assert( 2 == t.find( { $where : function(){ return 1; } } ).length() ); +assert( 1 == t.find( { $where : function(){ return obj.z == 2; } } ).length() ); + +assert(t.validate().valid); diff --git a/jstests/jni2.js b/jstests/jni2.js new file mode 100644 index 0000000..221780d --- /dev/null +++ b/jstests/jni2.js @@ -0,0 +1,22 @@ + +t = db.jni2; +t.remove( {} ); + +db.jni2t.remove( {} ); + +assert.eq( 0 , db.jni2t.find().length() , "A" ); + +t.save( { z : 1 } ); +t.save( { z : 2 } ); +assert.throws( function(){ + t.find( { $where : + function(){ + db.jni2t.save( { y : 1 } ); + return 1; + } + } ).length(); +} , "can't save from $where" ); + +assert.eq( 0 , db.jni2t.find().length() , "B" ) + +assert(t.validate().valid , "E"); diff --git a/jstests/jni3.js b/jstests/jni3.js new file mode 100644 index 0000000..e0f0d10 --- /dev/null +++ b/jstests/jni3.js @@ -0,0 +1,74 @@ + +t = db.jni3; + +debug = function( s ){ + //printjson( s ); +} + +for( z = 0; z < 2; z++ ) { + debug(z); + + t.drop(); + + if( z > 0 ) { + t.ensureIndex({_id:1}); + t.ensureIndex({i:1}); + } + + for( i = 0; i < 1000; i++ ) + t.save( { i:i, z: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + + assert( 33 == db.dbEval(function() { return 33; } ) ); + + db.dbEval( function() { db.jni3.save({i:-1, z:"server side"}) } ); + + assert( db.jni3.findOne({i:-1}) ); + + assert( 2 == t.find( { $where : + function(){ + return obj.i == 7 || obj.i == 8; + } + } ).length() ); + + + // NPE test + var ok = false; + try { + var x = t.find( { $where : + function(){ + asdf.asdf.f.s.s(); + } + } ); + debug( x.length() ); + debug( tojson( x ) ); + } + catch(e) { + ok = true; + } + debug( ok ); + assert(ok); + + t.ensureIndex({z:1}); + t.ensureIndex({q:1}); + + debug( "before indexed find" ); + + arr = t.find( { $where : + function(){ + return obj.i == 7 || obj.i == 8; + } + } ).toArray(); + debug( arr ); + assert.eq( 2, arr.length ); + + debug( "after indexed find" ); + + for( i = 1000; i < 2000; i++ ) + t.save( { i:i, z: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + + assert( t.find().count() == 2001 ); + + assert( t.validate().valid ); + + debug( "done iter" ); +} diff --git a/jstests/jni4.js b/jstests/jni4.js new file mode 100644 index 0000000..b9f429e --- /dev/null +++ b/jstests/jni4.js @@ -0,0 +1,49 @@ +t = db.jni4; +t.drop(); + +real = { a : 1 , + b : "abc" , + c : /abc/i , + d : new Date(111911100111) , + e : null , + f : true + }; + +t.save( real ); + +assert.eq( "/abc/i" , real.c.toString() , "regex 1" ); + +var cursor = t.find( { $where : + function(){ + fullObject; + assert.eq( 7 , Object.keySet( obj ).length , "A" ) + assert.eq( 1 , obj.a , "B" ); + assert.eq( "abc" , obj.b , "C" ); + assert.eq( "/abc/i" , obj.c.toString() , "D" ); + assert.eq( 111911100111 , obj.d.getTime() , "E" ); + assert( obj.f , "F" ); + assert( ! obj.e , "G" ); + + return true; + } + } ); +assert.eq( 1 , cursor.toArray().length ); +assert.eq( "abc" , cursor[0].b ); + +// --- + +t.drop(); +t.save( { a : 2 , b : { c : 7 , d : "d is good" } } ); +var cursor = t.find( { $where : + function(){ + fullObject; + assert.eq( 3 , Object.keySet( obj ).length ) + assert.eq( 2 , obj.a ); + assert.eq( 7 , obj.b.c ); + assert.eq( "d is good" , obj.b.d ); + return true; + } + } ); +assert.eq( 1 , cursor.toArray().length ); + +assert(t.validate().valid); diff --git a/jstests/jni5.js b/jstests/jni5.js new file mode 100644 index 0000000..c6e6b54 --- /dev/null +++ b/jstests/jni5.js @@ -0,0 +1,10 @@ + +t = db.jni5 +t.drop(); + +t.save( { a : 1 } ) +t.save( { a : 2 } ) + +assert.eq( 2 , t.find( { "$where" : "this.a" } ).count() , "A" ); +assert.eq( 0 , t.find( { "$where" : "this.b" } ).count() , "B" ); +assert.eq( 0 , t.find( { "$where" : "this.b > 45" } ).count() , "C" ); diff --git a/jstests/jni7.js b/jstests/jni7.js new file mode 100644 index 0000000..2685dce --- /dev/null +++ b/jstests/jni7.js @@ -0,0 +1,7 @@ +t = db.jni7; +t.drop(); + +assert.eq( 17 , db.eval( function(){ return args[0]; } , 17 ) ); + +assert.eq( 17 , db.eval( function( foo ){ return foo; } , 17 ) ); + diff --git a/jstests/jni8.js b/jstests/jni8.js new file mode 100644 index 0000000..afc7d83 --- /dev/null +++ b/jstests/jni8.js @@ -0,0 +1,14 @@ +t = db.jni8; +t.drop(); + +t.save( { a : 1 , b : [ 2 , 3 , 4 ] } ); + +assert.eq( 1 , t.find().length() , "A" ); +assert.eq( 1 , t.find( function(){ return this.a == 1; } ).length() , "B" ); +assert.eq( 1 , t.find( function(){ if ( ! this.b.length ) return true; return this.b.length == 3; } ).length() , "B2" ); +assert.eq( 1 , t.find( function(){ return this.b[0] == 2; } ).length() , "C" ); +assert.eq( 0 , t.find( function(){ return this.b[0] == 3; } ).length() , "D" ); +assert.eq( 1 , t.find( function(){ return this.b[1] == 3; } ).length() , "E" ); + + +assert(t.validate().valid); diff --git a/jstests/jni9.js b/jstests/jni9.js new file mode 100644 index 0000000..940e36a --- /dev/null +++ b/jstests/jni9.js @@ -0,0 +1,24 @@ +c = db.jni9; +c.drop(); + +c.save( { a : 1 } ); +c.save( { a : 2 } ); + + +assert.eq( 2 , c.find().length() ); +assert.eq( 2 , c.find().count() ); + + +assert.eq( 2 , + db.eval( + function(){ + num = 0; + db.jni9.find().forEach( + function(z){ + num++; + } + ); + return num; + } + ) + ) diff --git a/jstests/json1.js b/jstests/json1.js new file mode 100644 index 0000000..a3dc820 --- /dev/null +++ b/jstests/json1.js @@ -0,0 +1,20 @@ + +x = { quotes:"a\"b" , nulls:null }; +eval( "y = " + tojson( x ) ); +assert.eq( tojson( x ) , tojson( y ) , "A" ); +assert.eq( typeof( x.nulls ) , typeof( y.nulls ) , "B" ); + +// each type is parsed properly +x = {"x" : null, "y" : true, "z" : 123, "w" : "foo"}; +assert.eq(tojson(x,"",false), '{\n\t"x" : null,\n\t"y" : true,\n\t"z" : 123,\n\t"w" : "foo"\n}' , "C" ); + +x = {"x" : [], "y" : {}}; +assert.eq(tojson(x,"",false), '{\n\t"x" : [ ],\n\t"y" : {\n\t\t\n\t}\n}' , "D" ); + +// nested +x = {"x" : [{"x" : [1,2,[]], "z" : "ok", "y" : [[]]}, {"foo" : "bar"}], "y" : null}; +assert.eq(tojson(x), '{\n\t"x" : [\n\t\t{\n\t\t\t"x" : [\n\t\t\t\t1,\n\t\t\t\t2,\n\t\t\t\t[ ]\n\t\t\t],\n\t\t\t"z" : "ok",\n\t\t\t"y" : [\n\t\t\t\t[ ]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"foo" : "bar"\n\t\t}\n\t],\n\t"y" : null\n}' , "E" ); + +// special types +x = {"x" : ObjectId("4ad35a73d2e34eb4fc43579a"), 'z' : /xd?/ig}; +assert.eq(tojson(x,"",false), '{\n\t"x" : ObjectId("4ad35a73d2e34eb4fc43579a"),\n\t"z" : /xd?/gi\n}' , "F" ); diff --git a/jstests/map1.js b/jstests/map1.js new file mode 100644 index 0000000..1db53cd --- /dev/null +++ b/jstests/map1.js @@ -0,0 +1,24 @@ + +function basic1( key , lookup , shouldFail){ + var m = new Map(); + m.put( key , 17 ); + + var out = m.get( lookup || key ); + + if ( ! shouldFail ){ + assert.eq( 17 , out , "basic1 missing: " + tojson( key ) ); + } + else { + assert.isnull( out , "basic1 not missing: " + tojson( key ) ); + } + +} + +basic1( 6 ) +basic1( new Date() ) +basic1( "eliot" ) +basic1( { a : 1 } ); +basic1( { a : 1 , b : 1 } ) +basic1( { a : 1 } , { b : 1 } , true ) +basic1( { a : 1 , b : 1 } , { b : 1 , a : 1 } , true ) +basic1( { a : 1 } , { a : 2 } , true ); diff --git a/jstests/median.js b/jstests/median.js new file mode 100644 index 0000000..b6ef7c4 --- /dev/null +++ b/jstests/median.js @@ -0,0 +1,74 @@ +f = db.jstests_median; +f.drop(); + +f.ensureIndex( {i:1} ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:0} } ).ok ); + +f.save( {i:0} ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:1} } ).median.i ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:10} } ).median.i ); + +f.save( {i:1} ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:1} } ).median.i ); +assert.eq( 1, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:10} } ).median.i ); + +for( i = 2; i < 1000; ++i ) { + f.save( {i:i} ); +} + +assert.eq( 500, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:1000} } ).median.i ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:1} } ).median.i ); +assert.eq( 500, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:500}, max:{i:501} } ).median.i ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:1} } ).median.i ); +assert.eq( 1, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1}, min:{i:0}, max:{i:2} } ).median.i ); + +f.drop(); +f.ensureIndex( {i:1,j:-1} ); +for( i = 0; i < 100; ++i ) { + for( j = 0; j < 100; ++j ) { + f.save( {i:i,j:j} ); + } +} + +assert.eq( 50, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:0}, max:{i:99,j:0} } ).median.i ); +assert.eq( 0, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:1}, max:{i:0,j:0} } ).median.i ); +assert.eq( 50, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:50,j:1}, max:{i:50,j:0} } ).median.i ); +assert.eq( 1, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:0}, max:{i:1,j:0} } ).median.i ); +assert.eq( 1, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:0}, max:{i:2,j:0} } ).median.i ); + +assert.eq( 50, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:99}, max:{i:0,j:0} } ).median.j ); +assert.eq( 45, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:0,j:49}, max:{i:0,j:40} } ).median.j ); + +assert.eq( 10, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:10,j:50}, max:{i:11,j:75} } ).median.i ); +assert.eq( 13, db.runCommand( {medianKey:"test.jstests_median", keyPattern:{i:1,j:-1}, min:{i:10,j:50}, max:{i:11,j:75} } ).median.j ); + +f.drop(); +f.ensureIndex( {i:1,j:1} ); +f.save( {i:0,j:0} ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0}, max:{i:0} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0}, max:{i:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:1}, max:{i:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:0} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:1,j:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:1,j:1}, max:{i:0,j:0} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:1}, max:{i:0,j:0} } ).ok ); + +f.drop(); +f.ensureIndex( {i:1,j:-1} ); +f.save( {i:0,j:0} ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:0} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:1,j:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:1,j:1}, max:{i:0,j:0} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:1} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:1}, max:{i:0,j:-1} } ).ok ); + +f.drop(); +f.ensureIndex( {i:1,j:1} ); +f.ensureIndex( {i:1,j:-1} ); +f.save( {i:0,j:0} ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:0} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:1,j:1} } ).ok ); +assert.eq( false, db.runCommand( {medianKey:"test.jstests_median", min:{i:1,j:1}, max:{i:-1,j:-1} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:0}, max:{i:0,j:1} } ).ok ); +assert.eq( true, db.runCommand( {medianKey:"test.jstests_median", min:{i:0,j:1}, max:{i:0,j:-1} } ).ok ); diff --git a/jstests/minmax.js b/jstests/minmax.js new file mode 100644 index 0000000..3723e33 --- /dev/null +++ b/jstests/minmax.js @@ -0,0 +1,40 @@ +// test min / max query parameters + +addData = function() { + t.save( { a: 1, b: 1 } ); + t.save( { a: 1, b: 2 } ); + t.save( { a: 2, b: 1 } ); + t.save( { a: 2, b: 2 } ); +} + +t = db.jstests_minmax; +t.drop(); +t.ensureIndex( { a: 1, b: 1 } ); +addData(); + +assert.eq( 1, t.find().min( { a: 1, b: 2 } ).max( { a: 2, b: 1 } ).toArray().length ); +assert.eq( 2, t.find().min( { a: 1, b: 2 } ).max( { a: 2, b: 1.5 } ).toArray().length ); +assert.eq( 2, t.find().min( { a: 1, b: 2 } ).max( { a: 2, b: 2 } ).toArray().length ); + +// just one bound +assert.eq( 3, t.find().min( { a: 1, b: 2 } ).toArray().length ); +assert.eq( 3, t.find().max( { a: 2, b: 1.5 } ).toArray().length ); +assert.eq( 3, t.find().min( { a: 1, b: 2 } ).hint( { a: 1, b: 1 } ).toArray().length ); +assert.eq( 3, t.find().max( { a: 2, b: 1.5 } ).hint( { a: 1, b: 1 } ).toArray().length ); + +t.drop(); +t.ensureIndex( { a: 1, b: -1 } ); +addData(); +assert.eq( 4, t.find().min( { a: 1, b: 2 } ).toArray().length ); +assert.eq( 4, t.find().max( { a: 2, b: 0.5 } ).toArray().length ); +assert.eq( 1, t.find().min( { a: 2, b: 1 } ).toArray().length ); +assert.eq( 1, t.find().max( { a: 1, b: 1.5 } ).toArray().length ); +assert.eq( 4, t.find().min( { a: 1, b: 2 } ).hint( { a: 1, b: -1 } ).toArray().length ); +assert.eq( 4, t.find().max( { a: 2, b: 0.5 } ).hint( { a: 1, b: -1 } ).toArray().length ); +assert.eq( 1, t.find().min( { a: 2, b: 1 } ).hint( { a: 1, b: -1 } ).toArray().length ); +assert.eq( 1, t.find().max( { a: 1, b: 1.5 } ).hint( { a: 1, b: -1 } ).toArray().length ); + +// hint doesn't match +assert.throws( function() { t.find().min( { a: 1 } ).hint( { a: 1, b: -1 } ).toArray() } ); +assert.throws( function() { t.find().min( { a: 1, b: 1 } ).max( { a: 1 } ).hint( { a: 1, b: -1 } ).toArray() } ); +assert.throws( function() { t.find().min( { b: 1 } ).max( { a: 1, b: 2 } ).hint( { a: 1, b: -1 } ).toArray() } );
\ No newline at end of file diff --git a/jstests/mod1.js b/jstests/mod1.js new file mode 100644 index 0000000..eca35b7 --- /dev/null +++ b/jstests/mod1.js @@ -0,0 +1,24 @@ + +t = db.mod1; +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 2 } ); +t.save( { a : 11 } ); +t.save( { a : 20 } ); +t.save( { a : "asd" } ); +t.save( { a : "adasdas" } ); + +assert.eq( 2 , t.find( "this.a % 10 == 1" ).itcount() , "A1" ); +assert.eq( 2 , t.find( { a : { $mod : [ 10 , 1 ] } } ).itcount() , "A2" ); +assert.eq( 6 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain().nscanned , "A3" ); + +t.ensureIndex( { a : 1 } ); + +assert.eq( 2 , t.find( "this.a % 10 == 1" ).itcount() , "B1" ); +assert.eq( 2 , t.find( { a : { $mod : [ 10 , 1 ] } } ).itcount() , "B2" ); + +assert.eq( 1 , t.find( "this.a % 10 == 0" ).itcount() , "B3" ); +assert.eq( 1 , t.find( { a : { $mod : [ 10 , 0 ] } } ).itcount() , "B4" ); +assert.eq( 4 , t.find( { a : { $mod : [ 10 , 1 ] } } ).explain().nscanned , "B5" ); + diff --git a/jstests/mr1.js b/jstests/mr1.js new file mode 100644 index 0000000..aacd69b --- /dev/null +++ b/jstests/mr1.js @@ -0,0 +1,176 @@ + +t = db.mr1; +t.drop(); + +t.save( { x : 1 , tags : [ "a" , "b" ] } ); +t.save( { x : 2 , tags : [ "b" , "c" ] } ); +t.save( { x : 3 , tags : [ "c" , "a" ] } ); +t.save( { x : 4 , tags : [ "b" , "c" ] } ); + +emit = printjson; + +function d( x ){ + printjson( x ); +} + +ks = "_id"; +if ( db.version() == "1.1.1" ) + ks = "key"; + + +m = function(){ + this.tags.forEach( + function(z){ + emit( z , { count : 1 } ); + } + ); +}; + +m2 = function(){ + for ( var i=0; i<this.tags.length; i++ ){ + emit( this.tags[i] , 1 ); + } +}; + + +r = function( key , values ){ + var total = 0; + for ( var i=0; i<values.length; i++ ){ + total += values[i].count; + } + return { count : total }; +}; + +r2 = function( key , values ){ + var total = 0; + for ( var i=0; i<values.length; i++ ){ + total += values[i]; + } + return total; +}; + +res = db.runCommand( { mapreduce : "mr1" , map : m , reduce : r } ); +d( res ); +if ( ks == "_id" ) assert( res.ok , "not ok" ); +assert.eq( 4 , res.counts.input , "A" ); +x = db[res.result]; + +assert.eq( 3 , x.find().count() , "B" ); +x.find().forEach( d ); +z = {}; +x.find().forEach( function(a){ z[a[ks]] = a.value.count; } ); +d( z ); +assert.eq( 3 , Object.keySet( z ).length , "C" ); +assert.eq( 2 , z.a , "D" ); +assert.eq( 3 , z.b , "E" ); +assert.eq( 3 , z.c , "F" ); +x.drop(); + +res = db.runCommand( { mapreduce : "mr1" , map : m , reduce : r , query : { x : { "$gt" : 2 } } } ); +d( res ); +assert.eq( 2 , res.counts.input , "B" ); +x = db[res.result]; +z = {}; +x.find().forEach( function(a){ z[a[ks]] = a.value.count; } ); +assert.eq( 1 , z.a , "C1" ); +assert.eq( 1 , z.b , "C2" ); +assert.eq( 2 , z.c , "C3" ); +x.drop(); + +res = db.runCommand( { mapreduce : "mr1" , map : m2 , reduce : r2 , query : { x : { "$gt" : 2 } } } ); +d( res ); +assert.eq( 2 , res.counts.input , "B" ); +x = db[res.result]; +z = {}; +x.find().forEach( function(a){ z[a[ks]] = a.value; } ); +assert.eq( 1 , z.a , "C1z" ); +assert.eq( 1 , z.b , "C2z" ); +assert.eq( 2 , z.c , "C3z" ); +x.drop(); + +res = db.runCommand( { mapreduce : "mr1" , out : "mr1_foo" , map : m , reduce : r , query : { x : { "$gt" : 2 } } } ); +d( res ); +assert.eq( 2 , res.counts.input , "B2" ); +assert.eq( "mr1_foo" , res.result , "B2-c" ); +x = db[res.result]; +z = {}; +x.find().forEach( function(a){ z[a[ks]] = a.value.count; } ); +assert.eq( 1 , z.a , "C1a" ); +assert.eq( 1 , z.b , "C2a" ); +assert.eq( 2 , z.c , "C3a" ); +x.drop(); + +for ( i=5; i<1000; i++ ){ + t.save( { x : i , tags : [ "b" , "d" ] } ); +} + +res = db.runCommand( { mapreduce : "mr1" , map : m , reduce : r } ); +d( res ); +assert.eq( 999 , res.counts.input , "Z1" ); +x = db[res.result]; +x.find().forEach( d ) +assert.eq( 4 , x.find().count() , "Z2" ); +assert.eq( "a,b,c,d" , x.distinct( ks ) , "Z3" ); + +function getk( k ){ + var o = {}; + o[ks] = k; + return x.findOne( o ); +} + +assert.eq( 2 , getk( "a" ).value.count , "ZA" ); +assert.eq( 998 , getk( "b" ).value.count , "ZB" ); +assert.eq( 3 , getk( "c" ).value.count , "ZC" ); +assert.eq( 995 , getk( "d" ).value.count , "ZD" ); +x.drop(); + +if ( true ){ + printjson( db.runCommand( { mapreduce : "mr1" , map : m , reduce : r , verbose : true } ) ); +} + +print( "t1: " + Date.timeFunc( + function(){ + var out = db.runCommand( { mapreduce : "mr1" , map : m , reduce : r } ); + if ( ks == "_id" ) assert( out.ok , "XXX : " + tojson( out ) ); + db[out.result].drop(); + } , 10 ) + " (~500 on 2.8ghz) - itcount: " + Date.timeFunc( function(){ db.mr1.find().itcount(); } , 10 ) ); + + + +// test doesn't exist +res = db.runCommand( { mapreduce : "lasjdlasjdlasjdjasldjalsdj12e" , map : m , reduce : r } ); +assert( ! res.ok , "should be not ok" ); + +if ( true ){ + correct = {}; + + for ( i=0; i<20000; i++ ){ + k = "Z" + i % 10000; + if ( correct[k] ) + correct[k]++; + else + correct[k] = 1; + t.save( { x : i , tags : [ k ] } ); + } + + res = db.runCommand( { mapreduce : "mr1" , out : "mr1_foo" , map : m , reduce : r } ); + d( res ); + print( "t2: " + res.timeMillis + " (~3500 on 2.8ghz) - itcount: " + Date.timeFunc( function(){ db.mr1.find().itcount(); } ) ); + x = db[res.result]; + z = {}; + x.find().forEach( function(a){ z[a[ks]] = a.value.count; } ); + for ( zz in z ){ + if ( zz.indexOf( "Z" ) == 0 ){ + assert.eq( correct[zz] , z[zz] , "ZZ : " + zz ); + } + } + x.drop(); + + res = db.runCommand( { mapreduce : "mr1" , out : "mr1_foo" , map : m2 , reduce : r2 } ); + d(res); + print( "t3: " + res.timeMillis + " (~3500 on 2.8ghz)" ); +} + + +res = db.runCommand( { mapreduce : "mr1" , map : m , reduce : r } ); +assert( res.ok , "should be ok" ); diff --git a/jstests/mr2.js b/jstests/mr2.js new file mode 100644 index 0000000..0a8e9d6 --- /dev/null +++ b/jstests/mr2.js @@ -0,0 +1,50 @@ + + +t = db.mr2; +t.drop(); + +t.save( { comments : [ { who : "a" , txt : "asdasdasd" } , + { who : "b" , txt : "asdasdasdasdasdasdas" } ] } ); + +t.save( { comments : [ { who : "b" , txt : "asdasdasdaaa" } , + { who : "c" , txt : "asdasdasdaasdasdas" } ] } ); + + + +function m(){ + for ( var i=0; i<this.comments.length; i++ ){ + var c = this.comments[i]; + emit( c.who , { totalSize : c.txt.length , num : 1 } ); + } +} + +function r( who , values ){ + var n = { totalSize : 0 , num : 0 }; + for ( var i=0; i<values.length; i++ ){ + n.totalSize += values[i].totalSize; + n.num += values[i].num; + } + return n; +} + +function reformat( r ){ + var x = {}; + r.find().forEach( + function(z){ + x[z._id] = z.value; + } + ); + return x; +} + +function f( who , res ){ + res.avg = res.totalSize / res.num; + return res; +} +res = t.mapReduce( m , r , { finalize : f } ); +x = reformat( res ); +assert.eq( 9 , x.a.avg , "A" ); +assert.eq( 16 , x.b.avg , "B" ); +assert.eq( 18 , x.c.avg , "C" ); +res.drop(); + diff --git a/jstests/mr3.js b/jstests/mr3.js new file mode 100644 index 0000000..e7d1f2c --- /dev/null +++ b/jstests/mr3.js @@ -0,0 +1,73 @@ + +t = db.mr3; +t.drop(); + +t.save( { x : 1 , tags : [ "a" , "b" ] } ); +t.save( { x : 2 , tags : [ "b" , "c" ] } ); +t.save( { x : 3 , tags : [ "c" , "a" ] } ); +t.save( { x : 4 , tags : [ "b" , "c" ] } ); + +m = function( n , x ){ + x = x || 1; + this.tags.forEach( + function(z){ + for ( var i=0; i<x; i++ ) + emit( z , { count : n || 1 } ); + } + ); +}; + +r = function( key , values ){ + var total = 0; + for ( var i=0; i<values.length; i++ ){ + total += values[i].count; + } + return { count : total }; +}; + +res = t.mapReduce( m , r ); +z = res.convertToSingleObject() + +assert.eq( 3 , Object.keySet( z ).length , "A1" ); +assert.eq( 2 , z.a.count , "A2" ); +assert.eq( 3 , z.b.count , "A3" ); +assert.eq( 3 , z.c.count , "A4" ); + +res.drop(); + +res = t.mapReduce( m , r , { mapparams : [ 2 , 2 ] } ); +z = res.convertToSingleObject() + +assert.eq( 3 , Object.keySet( z ).length , "B1" ); +assert.eq( 8 , z.a.count , "B2" ); +assert.eq( 12 , z.b.count , "B3" ); +assert.eq( 12 , z.c.count , "B4" ); + +res.drop(); + +// -- just some random tests + +realm = m; + +m = function(){ + emit( this._id , 1 ); +} +res = t.mapReduce( m , r ); +res.drop(); + +m = function(){ + emit( this._id , this.xzz.a ); +} + +before = db.getCollectionNames().length; +assert.throws( function(){ t.mapReduce( m , r ); } ); +assert.eq( before , db.getCollectionNames().length , "after throw crap" ); + + +m = realm; +r = function( k , v ){ + return v.x.x.x; +} +before = db.getCollectionNames().length; +assert.throws( function(){ t.mapReduce( m , r ); } ); +assert.eq( before , db.getCollectionNames().length , "after throw crap" ); diff --git a/jstests/mr4.js b/jstests/mr4.js new file mode 100644 index 0000000..b14cdfe --- /dev/null +++ b/jstests/mr4.js @@ -0,0 +1,45 @@ + +t = db.mr4; +t.drop(); + +t.save( { x : 1 , tags : [ "a" , "b" ] } ); +t.save( { x : 2 , tags : [ "b" , "c" ] } ); +t.save( { x : 3 , tags : [ "c" , "a" ] } ); +t.save( { x : 4 , tags : [ "b" , "c" ] } ); + +m = function(){ + this.tags.forEach( + function(z){ + emit( z , { count : xx } ); + } + ); +}; + +r = function( key , values ){ + var total = 0; + for ( var i=0; i<values.length; i++ ){ + total += values[i].count; + } + return { count : total }; +}; + +res = t.mapReduce( m , r , { scope : { xx : 1 } } ); +z = res.convertToSingleObject() + +assert.eq( 3 , Object.keySet( z ).length , "A1" ); +assert.eq( 2 , z.a.count , "A2" ); +assert.eq( 3 , z.b.count , "A3" ); +assert.eq( 3 , z.c.count , "A4" ); + +res.drop(); + + +res = t.mapReduce( m , r , { scope : { xx : 2 } } ); +z = res.convertToSingleObject() + +assert.eq( 3 , Object.keySet( z ).length , "A1" ); +assert.eq( 4 , z.a.count , "A2" ); +assert.eq( 6 , z.b.count , "A3" ); +assert.eq( 6 , z.c.count , "A4" ); + +res.drop(); diff --git a/jstests/mr5.js b/jstests/mr5.js new file mode 100644 index 0000000..50eb366 --- /dev/null +++ b/jstests/mr5.js @@ -0,0 +1,39 @@ + +t = db.mr5; +t.drop(); + +t.save( { "partner" : 1, "visits" : 9 } ) +t.save( { "partner" : 2, "visits" : 9 } ) +t.save( { "partner" : 1, "visits" : 11 } ) +t.save( { "partner" : 1, "visits" : 30 } ) +t.save( { "partner" : 2, "visits" : 41 } ) +t.save( { "partner" : 2, "visits" : 41 } ) + +m = function(){ + emit( this.partner , { stats : [ this.visits ] } ) +} + +r = function( k , v ){ + var stats = []; + var total = 0; + for ( var i=0; i<v.length; i++ ){ + for ( var j in v[i].stats ) { + stats.push( v[i].stats[j] ) + total += v[i].stats[j]; + } + } + return { stats : stats , total : total } +} + +res = t.mapReduce( m , r , { scope : { xx : 1 } } ); +res.find().forEach( printjson ) + +z = res.convertToSingleObject() +assert.eq( 2 , Object.keySet( z ).length , "A" ) +assert.eq( [ 9 , 11 , 30 ] , z["1"].stats , "B" ) +assert.eq( [ 9 , 41 , 41 ] , z["2"].stats , "B" ) + + +res.drop() + + diff --git a/jstests/multi.js b/jstests/multi.js new file mode 100644 index 0000000..eb6cad3 --- /dev/null +++ b/jstests/multi.js @@ -0,0 +1,24 @@ +t = db.jstests_multi; +t.drop(); + +t.ensureIndex( { a: 1 } ); +t.save( { a: [ 1, 2 ] } ); +assert.eq( 1, t.find( { a: { $gt: 0 } } ).count() , "A" ); +assert.eq( 1, t.find( { a: { $gt: 0 } } ).toArray().length , "B" ); + +t.drop(); +t.save( { a: [ [ [ 1 ] ] ] } ); +assert.eq( 0, t.find( { a:1 } ).count() , "C" ); +assert.eq( 0, t.find( { a: [ 1 ] } ).count() , "D" ); +assert.eq( 1, t.find( { a: [ [ 1 ] ] } ).count() , "E" ); +assert.eq( 1, t.find( { a: [ [ [ 1 ] ] ] } ).count() , "F" ); + +t.drop(); +t.save( { a: [ 1, 2 ] } ); +assert.eq( 0, t.find( { a: { $ne: 1 } } ).count() , "G" ); + +t.drop(); +t.save( { a: [ { b: 1 }, { b: 2 } ] } ); +assert.eq( 0, t.find( { 'a.b': { $ne: 1 } } ).count() , "H" ); + +// TODO - run same tests with an index on a diff --git a/jstests/multi2.js b/jstests/multi2.js new file mode 100644 index 0000000..7c72722 --- /dev/null +++ b/jstests/multi2.js @@ -0,0 +1,23 @@ + +t = db.multi2; +t.drop(); + +t.save( { x : 1 , a : [ 1 ] } ); +t.save( { x : 1 , a : [] } ); +t.save( { x : 1 , a : null } ); +t.save( {} ); + +assert.eq( 3 , t.find( { x : 1 } ).count() , "A" ); + +t.ensureIndex( { x : 1 } ); +assert.eq( 3 , t.find( { x : 1 } ).count() , "B" ); +assert.eq( 4 , t.find().sort( { x : 1 , a : 1 } ).count() , "s1" ); +assert.eq( 1 , t.find( { x : 1 , a : null } ).count() , "B2" ); + +t.dropIndex( { x : 1 } ); +t.ensureIndex( { x : 1 , a : 1 } ); +assert.eq( 3 , t.find( { x : 1 } ).count() , "C" ); // SERVER-279 +assert.eq( 4 , t.find().sort( { x : 1 , a : 1 } ).count() , "s2" ); +assert.eq( 1 , t.find( { x : 1 , a : null } ).count() , "C2" ); + + diff --git a/jstests/ne1.js b/jstests/ne1.js new file mode 100644 index 0000000..e1c5656 --- /dev/null +++ b/jstests/ne1.js @@ -0,0 +1,11 @@ + +t = db.ne1; +t.drop(); + +t.save( { x : 1 } ); +t.save( { x : 2 } ); +t.save( { x : 3 } ); + +assert.eq( 2 , t.find( { x : { $ne : 2 } } ).itcount() , "A" ); +t.ensureIndex( { x : 1 } ); +assert.eq( 2 , t.find( { x : { $ne : 2 } } ).itcount() , "B" ); diff --git a/jstests/nin.js b/jstests/nin.js new file mode 100644 index 0000000..4afd344 --- /dev/null +++ b/jstests/nin.js @@ -0,0 +1,57 @@ +t = db.jstests_nin; +t.drop(); + +function checkEqual( name , key , value ){ + var o = {}; + o[key] = { $in : [ value ] }; + var i = t.find( o ).count(); + o[key] = { $nin : [ value ] }; + var n = t.find( o ).count(); + + assert.eq( t.find().count() , i + n , + "checkEqual " + name + " $in + $nin != total | " + i + " + " + n + " != " + t.find().count() ); +} + +doTest = function( n ) { + + t.save( { a:[ 1,2,3 ] } ); + t.save( { a:[ 1,2,4 ] } ); + t.save( { a:[ 1,8,5 ] } ); + t.save( { a:[ 1,8,6 ] } ); + t.save( { a:[ 1,9,7 ] } ); + + assert.eq( 5, t.find( { a: { $nin: [ 10 ] } } ).count() , n + " A" ); + assert.eq( 0, t.find( { a: { $ne: 1 } } ).count() , n + " B" ); + assert.eq( 0, t.find( { a: { $nin: [ 1 ] } } ).count() , n + " C" ); + assert.eq( 0, t.find( { a: { $nin: [ 1, 2 ] } } ).count() , n + " D" ); + assert.eq( 3, t.find( { a: { $nin: [ 2 ] } } ).count() , n + " E" ); + assert.eq( 3, t.find( { a: { $nin: [ 8 ] } } ).count() , n + " F" ); + assert.eq( 4, t.find( { a: { $nin: [ 9 ] } } ).count() , n + " G" ); + assert.eq( 4, t.find( { a: { $nin: [ 3 ] } } ).count() , n + " H" ); + assert.eq( 3, t.find( { a: { $nin: [ 2, 3 ] } } ).count() , n + " I" ); + + checkEqual( n + " A" , "a" , 5 ); + + t.save( { a: [ 2, 2 ] } ); + assert.eq( 3, t.find( { a: { $nin: [ 2, 2 ] } } ).count() , n + " J" ); + + t.save( { a: [ [ 2 ] ] } ); + assert.eq( 4, t.find( { a: { $nin: [ 2 ] } } ).count() , n + " K" ); + + t.save( { a: [ { b: [ 10, 11 ] }, 11 ] } ); + checkEqual( n + " B" , "a" , 5 ); + checkEqual( n + " C" , "a.b" , 5 ); + + assert.eq( 7, t.find( { 'a.b': { $nin: [ 10 ] } } ).count() , n + " L" ); + assert.eq( 8, t.find( { 'a.b': { $nin: [ [ 10, 11 ] ] } } ).count() , n + " M" ); + assert.eq( 7, t.find( { a: { $nin: [ 11 ] } } ).count() , n + " N" ); + + t.save( { a: { b: [ 20, 30 ] } } ); + assert.eq( 1, t.find( { 'a.b': { $all: [ 20 ] } } ).count() , n + " O" ); + assert.eq( 1, t.find( { 'a.b': { $all: [ 20, 30 ] } } ).count() , n + " P" ); +} + +doTest( "no index" ); +t.drop(); +t.ensureIndex( {a:1} ); +doTest( "with index" ); diff --git a/jstests/not1.js b/jstests/not1.js new file mode 100644 index 0000000..f99a849 --- /dev/null +++ b/jstests/not1.js @@ -0,0 +1,20 @@ + +t = db.not1; +t.drop(); + + +t.insert({a:1}) +t.insert({a:2}) +t.insert({}) + +function test( name ){ + assert.eq( 3 , t.find().count() , name + "A" ); + assert.eq( 1 , t.find( { a : 1 } ).count() , name + "B" ); + assert.eq( 2 , t.find( { a : { $ne : 1 } } ).count() , name + "C" ); // SERVER-198 + assert.eq( 1 , t.find({a:{$in:[1]}}).count() , name + "D" ); + assert.eq( 2 , t.find({a:{$nin:[1]}}).count() , name + "E" ); // SERVER-198 +} + +test( "no index" ); +t.ensureIndex( { a : 1 } ); +test( "with index" ); diff --git a/jstests/null.js b/jstests/null.js new file mode 100644 index 0000000..4fb663e --- /dev/null +++ b/jstests/null.js @@ -0,0 +1,14 @@ + +t = db.null1; +t.drop(); + +t.save( { x : 1 } ); +t.save( { x : null } ); + +assert.eq( 1 , t.find( { x : null } ).count() , "A" ); +assert.eq( 1 , t.find( { x : { $ne : null } } ).count() , "B" ); + +t.ensureIndex( { x : 1 } ); + +assert.eq( 1 , t.find( { x : null } ).count() , "C" ); +assert.eq( 1 , t.find( { x : { $ne : null } } ).count() , "D" ); diff --git a/jstests/objid1.js b/jstests/objid1.js new file mode 100644 index 0000000..dea31ee --- /dev/null +++ b/jstests/objid1.js @@ -0,0 +1,16 @@ +t = db.objid1; +t.drop(); + +b = new ObjectId(); +assert( b.str , "A" ); + +a = new ObjectId( b.str ); +assert.eq( a.str , b.str , "B" ); + +t.save( { a : a } ) +assert( t.findOne().a.isObjectId , "C" ); +assert.eq( a.str , t.findOne().a.str , "D" ); + +x = { a : new ObjectId() }; +eval( " y = " + tojson( x ) ); +assert.eq( x.a.str , y.a.str , "E" ); diff --git a/jstests/objid2.js b/jstests/objid2.js new file mode 100644 index 0000000..a28c18f --- /dev/null +++ b/jstests/objid2.js @@ -0,0 +1,7 @@ +t = db.objid2; +t.drop(); + +t.save( { _id : 517 , a : "hello" } ) + +assert.eq( t.findOne().a , "hello" ); +assert.eq( t.findOne()._id , 517 ); diff --git a/jstests/objid3.js b/jstests/objid3.js new file mode 100644 index 0000000..ddf20d9 --- /dev/null +++ b/jstests/objid3.js @@ -0,0 +1,9 @@ +t = db.objid3; +t.drop(); + +t.save( { a : "bob" , _id : 517 } ); +for ( var k in t.findOne() ){ + assert.eq( k , "_id" , "keys out of order" ); + break; +} + diff --git a/jstests/objid4.js b/jstests/objid4.js new file mode 100644 index 0000000..23986b9 --- /dev/null +++ b/jstests/objid4.js @@ -0,0 +1,16 @@ + + + +o = new ObjectId(); +assert( o.str ); + +a = new ObjectId( o.str ); +assert.eq( o.str , a.str ); +assert.eq( a.str , a.str.toString() ) + +b = ObjectId( o.str ); +assert.eq( o.str , b.str ); +assert.eq( b.str , b.str.toString() ) + +assert.throws( function(z){ return new ObjectId( "a" ); } ); +assert.throws( function(z){ return new ObjectId( "12345678901234567890123z" ); } ); diff --git a/jstests/objid5.js b/jstests/objid5.js new file mode 100644 index 0000000..ab883bc --- /dev/null +++ b/jstests/objid5.js @@ -0,0 +1,6 @@ + +t = db.objid5; +t.drop(); + +t.save( { _id : 5.5 } ); +assert.eq( 18 , Object.bsonsize( t.findOne() ) , "A" ); diff --git a/jstests/parallel/allops.js b/jstests/parallel/allops.js new file mode 100644 index 0000000..7eb0cb2 --- /dev/null +++ b/jstests/parallel/allops.js @@ -0,0 +1,40 @@ +// test all operations in parallel + +f = db.jstests_parallel_allops; +f.drop(); + +Random.setRandomSeed(); + +t = new ParallelTester(); + +for( id = 0; id < 10; ++id ) { + var g = new EventGenerator( id, "jstests_parallel_allops", Random.randInt( 20 ) ); + for( var j = 0; j < 1000; ++j ) { + var op = Random.randInt( 3 ); + switch( op ) { + case 0: // insert + g.addInsert( { _id:Random.randInt( 1000 ) } ); + break; + case 1: // remove + g.addRemove( { _id:Random.randInt( 1000 ) } ); + break; + case 2: // update + g.addUpdate( {_id:{$lt:1000}}, { _id:Random.randInt( 1000 ) } ); + break; + default: + assert( false, "Invalid op code" ); + } + } + t.add( EventGenerator.dispatch, g.getEvents() ); +} + +var g = new EventGenerator( id, "jstests_parallel_allops", Random.randInt( 5 ) ); +for( var j = 1000; j < 3000; ++j ) { + g.addCheckCount( j - 1000, { _id: {$gte:1000} }, j % 100 == 0, j % 500 == 0 ); + g.addInsert( {_id:j} ); +} +t.add( EventGenerator.dispatch, g.getEvents() ); + +t.run( "one or more tests failed" ); + +assert( f.validate().valid ); diff --git a/jstests/parallel/basic.js b/jstests/parallel/basic.js new file mode 100644 index 0000000..9c10306 --- /dev/null +++ b/jstests/parallel/basic.js @@ -0,0 +1,11 @@ +// perform basic js tests in parallel + +Random.setRandomSeed(); + +var params = ParallelTester.createJstestsLists( 4 ); +var t = new ParallelTester(); +for( i in params ) { + t.add( ParallelTester.fileTester, params[ i ] ); +} + +t.run( "one or more tests failed", true ); diff --git a/jstests/parallel/basicPlus.js b/jstests/parallel/basicPlus.js new file mode 100644 index 0000000..d6f9a4d --- /dev/null +++ b/jstests/parallel/basicPlus.js @@ -0,0 +1,26 @@ +// perform basic js tests in parallel & some other tasks as well + +var c = db.jstests_parallel_basicPlus; +c.drop(); + +Random.setRandomSeed(); + +var params = ParallelTester.createJstestsLists( 4 ); +var t = new ParallelTester(); +for( i in params ) { + t.add( ParallelTester.fileTester, params[ i ] ); +} + +for( var i = 4; i < 8; ++i ) { + var g = new EventGenerator( i, "jstests_parallel_basicPlus", Random.randInt( 20 ) ); + for( var j = ( i - 4 ) * 3000; j < ( i - 3 ) * 3000; ++j ) { + var expected = j - ( ( i - 4 ) * 3000 ); + g.addCheckCount( expected, {_id:{$gte:((i-4)*3000),$lt:((i-3)*3000)}}, expected % 1000 == 0, expected % 500 == 0 ); + g.addInsert( {_id:j} ); + } + t.add( EventGenerator.dispatch, g.getEvents() ); +} + +t.run( "one or more tests failed", true ); + +assert( c.validate().valid, "validate failed" );
\ No newline at end of file diff --git a/jstests/parallel/insert.js b/jstests/parallel/insert.js new file mode 100644 index 0000000..fc1c750 --- /dev/null +++ b/jstests/parallel/insert.js @@ -0,0 +1,24 @@ +// perform inserts in parallel from several clients + +f = db.jstests_parallel_insert; +f.drop(); +f.ensureIndex( {who:1} ); + +Random.setRandomSeed(); + +t = new ParallelTester(); + +for( id = 0; id < 10; ++id ) { + var g = new EventGenerator( id, "jstests_parallel_insert", Random.randInt( 20 ) ); + for( j = 0; j < 1000; ++j ) { + if ( j % 50 == 0 ) { + g.addCheckCount( j, {who:id} ); + } + g.addInsert( { i:j, who:id } ); + } + t.add( EventGenerator.dispatch, g.getEvents() ); +} + +t.run( "one or more tests failed" ); + +assert( f.validate().valid ); diff --git a/jstests/parallel/manyclients.js b/jstests/parallel/manyclients.js new file mode 100644 index 0000000..14cdec5 --- /dev/null +++ b/jstests/parallel/manyclients.js @@ -0,0 +1,26 @@ +// perform inserts in parallel from a large number of clients + +f = db.jstests_parallel_manyclients; +f.drop(); +f.ensureIndex( {who:1} ); + +Random.setRandomSeed(); + +t = new ParallelTester(); + +for( id = 0; id < 200; ++id ) { + var g = new EventGenerator( id, "jstests_parallel_manyclients", Random.randInt( 20 ) ); + for( j = 0; j < 1000; ++j ) { + if ( j % 50 == 0 ) { + g.addCheckCount( j, {who:id}, true ); + } + g.addInsert( { i:j, who:id } ); + } + t.add( EventGenerator.dispatch, g.getEvents() ); +} + +print( "done preparing test" ); + +t.run( "one or more tests failed" ); + +assert( f.validate().valid ); diff --git a/jstests/parallel/shellfork.js b/jstests/parallel/shellfork.js new file mode 100644 index 0000000..20a1d3d --- /dev/null +++ b/jstests/parallel/shellfork.js @@ -0,0 +1,33 @@ +a = fork( function( a, b ) { return a / b; }, 10, 2 ); +a.start(); +b = fork( function( a, b, c ) { return a + b + c; }, 18, " is a ", "multiple of 3" ); +makeFunny = function( text ) { + return text + " ha ha!"; +} +c = fork( makeFunny, "paisley" ); +c.start(); +b.start(); +b.join(); +assert.eq( 5, a.returnData() ); +assert.eq( "18 is a multiple of 3", b.returnData() ); +assert.eq( "paisley ha ha!", c.returnData() ); + +z = fork( function( a ) { + var y = fork( function( a ) { + return a + 1; }, 5 ); + y.start(); + return y.returnData() + a; + }, 1 ); +z.start(); +assert.eq( 7, z.returnData() ); + + +t = 1; +z = new ScopedThread( function() { + assert( typeof( t ) == "undefined", "t not undefined" ); + t = 5; + return t; + } ); +z.start(); +assert.eq( 5, z.returnData() ); +assert.eq( 1, t );
\ No newline at end of file diff --git a/jstests/perf/find1.js b/jstests/perf/find1.js new file mode 100644 index 0000000..ecd94e5 --- /dev/null +++ b/jstests/perf/find1.js @@ -0,0 +1,90 @@ +/** + * Performance tests for various finders + */ + +var calls = 100; +var size = 500000; +var collection_name = "sort2"; + +function testSetup(dbConn) { + var t = dbConn[collection_name]; + t.drop(); + + for (var i=0; i<size; i++){ + t.save({ num : i }); + if (i == 0 ) + t.ensureIndex( { num : 1 } ); + } +} + +function resetQueryCache( db ) { + db[ collection_name ].createIndex( { a: 1 }, "dumbIndex" ); + db[ collection_name ].dropIndex( "dumbIndex" ); +} + +function between( low, high, val, msg ) { + assert( low < val, msg ); + assert( val < high, msg ); +} + +/** + * Tests fetching a set of 10 objects in sorted order, comparing getting + * from front of collection vs end, using $lt + */ +function testFindLTFrontBack(dbConn) { + + var results = {}; + var t = dbConn[collection_name]; + + resetQueryCache( dbConn ); + results.oneInOrderLTFirst = Date.timeFunc( + function(){ + assert( t.find( { num : {$lt : 20} } ).sort( { num : 1 } ).limit(10).toArray().length == 10); + } , calls ); + + resetQueryCache( dbConn ); + results.oneInOrderLTLast = Date.timeFunc( + function(){ + assert( t.find( { num : {$lt : size-20 }} ).sort( { num : 1 } ).limit(10).toArray().length == 10); + } , calls ); + + + between( 0.9, 1.1, results.oneInOrderLTFirst / results.oneInOrderLTLast, + "first / last (" + results.oneInOrderLTFirst + " / " + results.oneInOrderLTLast + " ) = " + + results.oneInOrderLTFirst / results.oneInOrderLTLast + " not in [0.9, 1.1]" ); +} + + + +/** + * Tests fetching a set of 10 objects in sorted order, comparing getting + * from front of collection vs end + */ +function testFindGTFrontBack(dbConn) { + + var results = {}; + var t = dbConn[collection_name]; + + resetQueryCache( dbConn ); + results.oneInOrderGTFirst = Date.timeFunc( + function(){ + assert( t.find( { num : {$gt : 5} } ).sort( { num : 1 } ).limit(10).toArray().length == 10); + } , calls ); + + resetQueryCache( dbConn ); + results.oneInOrderGTLast = Date.timeFunc( + function(){ + assert( t.find( { num : {$gt : size-20 }} ).sort( { num : 1 } ).limit(10).toArray().length == 10); + } , calls ); + + + between( 0.25, 4.0, results.oneInOrderGTFirst / results.oneInOrderGTLast, + "first / last (" + results.oneInOrderGTFirst + " / " + results.oneInOrderGTLast + " ) = " + + results.oneInOrderGTFirst / results.oneInOrderGTLast + " not in [0.25, 4.0]" ); + +} + +testSetup(db); + +testFindLTFrontBack(db); +testFindGTFrontBack(db);
\ No newline at end of file diff --git a/jstests/perf/index1.js b/jstests/perf/index1.js new file mode 100644 index 0000000..7bcf4b7 --- /dev/null +++ b/jstests/perf/index1.js @@ -0,0 +1,20 @@ + +t = db.perf.index1; +t.drop(); + +for ( var i=0; i<100000; i++ ){ + t.save( { x : i } ); +} + +t.findOne(); + +printjson( db.serverStatus().mem ); + +for ( var i=0; i<5; i++ ){ + nonu = Date.timeFunc( function(){ t.ensureIndex( { x : 1 } ); } ); + t.dropIndex( { x : 1 } ); + u = Date.timeFunc( function(){ t.ensureIndex( { x : 1 }, { unique : 1 } ); } ); + t.dropIndex( { x : 1 } ); + print( "non unique: " + nonu + " unique: " + u ); + printjson( db.serverStatus().mem ); +} diff --git a/jstests/perf/remove1.js b/jstests/perf/remove1.js new file mode 100644 index 0000000..3e1a1a6 --- /dev/null +++ b/jstests/perf/remove1.js @@ -0,0 +1,68 @@ +/** + * Performance tests for removing ojects + */ + +var removals = 100; +var size = 500000; +var collection_name = "remove_test"; +var msg = "Hello from remove test"; + +function testSetup(dbConn) { + var t = dbConn[collection_name]; + t.drop(); + t.ensureIndex( { num : 1 } ); + + for (var i=0; i<size; i++){ + t.save({ num : i, msg : msg }); + } +} + +function between( low, high, val, msg ) { + assert( low < val, msg ); + assert( val < high, msg ); +} + +/** + * Compares difference of removing objects from a collection if only includes + * field that's indexed, vs w/ additional other fields + * + * @param dbConn + */ +function testRemoveWithMultiField(dbConn) { + + var results = {}; + var t = dbConn[collection_name]; + + testSetup(dbConn); + + t.remove( {num:0 } ); + results.indexOnly = Date.timeFunc( + function(){ + for (var i = 1; i < removals; i++) { + t.remove({num : i}); + } + + t.findOne(); + } + ); + + testSetup(dbConn); + + t.remove( {num: 0, msg: msg } ); + results.withAnother = Date.timeFunc( + function(){ + for (var i = 1; i < removals; i++) { + t.remove({num : i, msg : msg}); + } + + t.findOne(); + } + ); + + + between( 0.65, 1.35, (results.indexOnly / results.withAnother), + "indexOnly / withAnother (" + results.indexOnly + " / " + results.withAnother + " ) = " + + results.indexOnly / results.withAnother + " not in [0.65, 1.35]" ); +} + +testRemoveWithMultiField(db); diff --git a/jstests/profile1.js b/jstests/profile1.js new file mode 100644 index 0000000..ea53b09 --- /dev/null +++ b/jstests/profile1.js @@ -0,0 +1,40 @@ + +/* With pre-created system.profile (capped) */ +db.runCommand({profile: 0}); +db.getCollection("system.profile").drop(); +assert(!db.getLastError(), "Z"); +assert.eq(0, db.runCommand({profile: -1}).was, "A"); + +db.createCollection("system.profile", {capped: true, size: 1000}); +db.runCommand({profile: 2}); +assert.eq(2, db.runCommand({profile: -1}).was, "B"); +assert.eq(1, db.system.profile.stats().capped, "C"); +var capped_size = db.system.profile.storageSize(); +assert.gt(capped_size, 999, "D"); +assert.lt(capped_size, 2000, "E"); + +assert.eq( 4 , db.system.profile.find().count() , "E2" ); + +/* Make sure we can't drop if profiling is still on */ +assert.throws( function(z){ db.getCollection("system.profile").drop(); } ) + +/* With pre-created system.profile (un-capped) */ +db.runCommand({profile: 0}); +db.getCollection("system.profile").drop(); +assert.eq(0, db.runCommand({profile: -1}).was, "F"); + +db.createCollection("system.profile"); +db.runCommand({profile: 2}); +assert.eq(2, db.runCommand({profile: -1}).was, "G"); +assert.eq(null, db.system.profile.stats().capped, "G1"); + +/* With no system.profile collection */ +db.runCommand({profile: 0}); +db.getCollection("system.profile").drop(); +assert.eq(0, db.runCommand({profile: -1}).was, "H"); + +db.runCommand({profile: 2}); +assert.eq(2, db.runCommand({profile: -1}).was, "I"); +assert.eq(1, db.system.profile.stats().capped, "J"); +var auto_size = db.system.profile.storageSize(); +assert.gt(auto_size, capped_size, "K"); diff --git a/jstests/pull.js b/jstests/pull.js new file mode 100644 index 0000000..cf8147a --- /dev/null +++ b/jstests/pull.js @@ -0,0 +1,19 @@ +t = db.jstests_pull; +t.drop(); + +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pull: { a: 2 } } ); +t.update( {}, { $pull: { a: 6 } } ); +assert.eq( [ 1, 3 ], t.findOne().a ); + +t.drop(); +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pull: { a: 2 } } ); +t.update( {}, { $pull: { a: 2 } } ); +assert.eq( [ 1, 3 ], t.findOne().a ); + +t.drop(); +t.save( { a: [ 2 ] } ); +t.update( {}, { $pull: { a: 2 } } ); +t.update( {}, { $pull: { a: 6 } } ); +assert.eq( [], t.findOne().a ); diff --git a/jstests/pull2.js b/jstests/pull2.js new file mode 100644 index 0000000..ca13fc2 --- /dev/null +++ b/jstests/pull2.js @@ -0,0 +1,31 @@ + +t = db.pull2; +t.drop(); + +t.save( { a : [ { x : 1 } , { x : 1 , b : 2 } ] } ); +assert.eq( 2 , t.findOne().a.length , "A" ); + +t.update( {} , { $pull : { a : { x : 1 } } } ); +assert.eq( 0 , t.findOne().a.length , "B" ); + +assert.eq( 1 , t.find().count() , "C1" ) + +t.update( {} , { $push : { a : { x : 1 } } } ) +t.update( {} , { $push : { a : { x : 1 , b : 2 } } } ) +assert.eq( 2 , t.findOne().a.length , "C" ); + +t.update( {} , { $pullAll : { a : [ { x : 1 } ] } } ); +assert.eq( 1 , t.findOne().a.length , "D" ); + +t.update( {} , { $push : { a : { x : 2 , b : 2 } } } ) +t.update( {} , { $push : { a : { x : 3 , b : 2 } } } ) +t.update( {} , { $push : { a : { x : 4 , b : 2 } } } ) +assert.eq( 4 , t.findOne().a.length , "E" ); + +assert.eq( 1 , t.find().count() , "C2" ) + + +t.update( {} , { $pull : { a : { x : { $lt : 3 } } } } ); +assert.eq( 2 , t.findOne().a.length , "F" ); +assert.eq( [ 3 , 4 ] , t.findOne().a.map( function(z){ return z.x; } ) , "G" ) + diff --git a/jstests/pullall.js b/jstests/pullall.js new file mode 100644 index 0000000..b720ce5 --- /dev/null +++ b/jstests/pullall.js @@ -0,0 +1,18 @@ +t = db.jstests_pushall; +t.drop(); + +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pullAll: { a: [ 3 ] } } ); +assert.eq( [ 1, 2 ], t.findOne().a ); +t.update( {}, { $pullAll: { a: [ 3 ] } } ); +assert.eq( [ 1, 2 ], t.findOne().a ); + +t.drop(); +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pullAll: { a: [ 2, 3 ] } } ); +assert.eq( [ 1 ], t.findOne().a ); +t.update( {}, { $pullAll: { a: [] } } ); +assert.eq( [ 1 ], t.findOne().a ); +t.update( {}, { $pullAll: { a: [ 1, 5 ] } } ); +assert.eq( [], t.findOne().a ); + diff --git a/jstests/push.js b/jstests/push.js new file mode 100644 index 0000000..2cdd91c --- /dev/null +++ b/jstests/push.js @@ -0,0 +1,22 @@ + +t = db.push +t.drop(); + +t.save( { _id : 2 , a : [ 1 ] } ); +t.update( { _id : 2 } , { $push : { a : 2 } } ); +assert.eq( "1,2" , t.findOne().a.toString() , "A" ); +t.update( { _id : 2 } , { $push : { a : 3 } } ); +assert.eq( "1,2,3" , t.findOne().a.toString() , "B" ); + +t.update( { _id : 2 } , { $pop : { a : 1 } } ); +assert.eq( "1,2" , t.findOne().a.toString() , "C" ); +t.update( { _id : 2 } , { $pop : { a : -1 } } ); +assert.eq( "2" , t.findOne().a.toString() , "D" ); + + +t.update( { _id : 2 } , { $push : { a : 3 } } ); +t.update( { _id : 2 } , { $push : { a : 4 } } ); +t.update( { _id : 2 } , { $push : { a : 5 } } ); +assert.eq( "2,3,4,5" , t.findOne().a.toString() , "D" ); +t.update( { _id : 2 } , { $pop : { a : -1 } } ); +assert.eq( "3,4,5" , t.findOne().a.toString() , "D" ); diff --git a/jstests/push2.js b/jstests/push2.js new file mode 100644 index 0000000..943ec11 --- /dev/null +++ b/jstests/push2.js @@ -0,0 +1,20 @@ + +t = db.push2 +t.drop() + +t.save( { _id : 1 , a : [] } ) + +var s = ""; +while ( s.length < 100000 ) + s += "asdasdasdasdasdasdasasdasdasdasdasdasdasasdasdasdasdasdasdasasdasdasdasdasdasdasasdasdasdasdasdasdas"; + +gotError = null; + +for ( x=0; x<200; x++ ){ + t.update( {} , { $push : { a : s } } ) + gotError = db.getLastError(); + if ( gotError ) + break; +} + +assert( gotError , "should have gotten error" ); diff --git a/jstests/pushall.js b/jstests/pushall.js new file mode 100644 index 0000000..eda6820 --- /dev/null +++ b/jstests/pushall.js @@ -0,0 +1,20 @@ +t = db.jstests_pushall; +t.drop(); + +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pushAll: { a: [ 4 ] } } ); +assert.eq( [ 1, 2, 3, 4 ], t.findOne().a ); +t.update( {}, { $pushAll: { a: [ 4 ] } } ); +assert.eq( [ 1, 2, 3, 4, 4 ], t.findOne().a ); + +t.drop(); +t.save( { a: [ 1, 2, 3 ] } ); +t.update( {}, { $pushAll: { a: [ 4, 5 ] } } ); +assert.eq( [ 1, 2, 3, 4, 5 ], t.findOne().a ); +t.update( {}, { $pushAll: { a: [] } } ); +assert.eq( [ 1, 2, 3, 4, 5 ], t.findOne().a ); + +t.drop(); +t.save( {} ); +t.update( {}, { $pushAll: { a: [ 1, 2 ] } } ); +assert.eq( [ 1, 2 ], t.findOne().a ); diff --git a/jstests/query1.js b/jstests/query1.js new file mode 100644 index 0000000..9b40054 --- /dev/null +++ b/jstests/query1.js @@ -0,0 +1,20 @@ + +t = db.query1; +t.drop(); + +t.save( { num : 1 } ); +t.save( { num : 3 } ) +t.save( { num : 4 } ); + +num = 0; +total = 0; + +t.find().forEach( + function(z){ + num++; + total += z.num; + } +); + +assert.eq( num , 3 , "num" ) +assert.eq( total , 8 , "total" ) diff --git a/jstests/queryoptimizer1.js b/jstests/queryoptimizer1.js new file mode 100644 index 0000000..d65d4d2 --- /dev/null +++ b/jstests/queryoptimizer1.js @@ -0,0 +1,26 @@ + +t = db.queryoptimizer1; +t.drop() + +for ( i=0; i<1000; i++ ) + for ( j=0; j<20; j++ ) + t.save( { a : i , b : i , c : j } ) + + +t.ensureIndex( { a : 1 } ) +t.ensureIndex( { b : 1 } ) + +for ( ; i<2000; i++ ) + for ( j=0; j<20; j++ ) + t.save( { a : i , b : i , c : j } ) + + +printjson( t.find( { a : 50 , b : 50 , c : 6 } ).explain() ); + +for ( var i=0; i<10000; i++ ){ + a = t.find( { a : 50 , b : 50 , c : i % 20 } ).toArray(); +} + +printjson( t.find( { a : 50 , b : 50 , c : 6 } ).explain() ); +assert.eq( 1 , t.find( { a : 50 , b : 50 , c : 6 } ).count() ) + diff --git a/jstests/quota/quota1.js b/jstests/quota/quota1.js new file mode 100644 index 0000000..d8f4c42 --- /dev/null +++ b/jstests/quota/quota1.js @@ -0,0 +1,48 @@ +t = db.quota1; + +print( "starting quota1.a" ); +assert.throws( + function(z){ + db.eval( + function(){ + db.quota1a.save( { a : 1 } ); + var a = 5; + while ( true ){ + a += 2; + } + } + ) + } +); +print( "done quota1.a" ); + +//print( "starting quota1.b" ); +//assert.throws( +// function(z){ +// db.eval( +// function(){ +// db.quota1b.save( { a : 1 } ); +// var a = 5; +// assert( sleep( 150000 ) ); +// } +// ) +// } +//); +//print( "done quota1.b" ); +// +//print( "starting quota1.c" ); +//assert.throws( +// function(z){ +// db.eval( +// function(){ +// db.quota1c.save( { a : 1 } ); +// var a = 1; +// while ( true ){ +// a += 1; +// assert( sleep( 1000 ) ); +// } +// } +// ) +// } +//); +//print( "done quota1.c" ); diff --git a/jstests/recstore.js b/jstests/recstore.js new file mode 100644 index 0000000..f2e78e2 --- /dev/null +++ b/jstests/recstore.js @@ -0,0 +1,24 @@ +// recstore.js +// this is a simple test for new recstores (see reci.h) +// it is probably redundant with other tests but is a convenient starting point +// for testing such things. + +t = db.storetest; + +t.drop(); + +t.save({z:3}); +t.save({z:2}); + +t.ensureIndex({z:1}); +t.ensureIndex({q:1}); +assert( t.find().sort({z:1})[0].z == 2 ); + +t.dropIndexes(); + +assert( t.find().sort({z:1})[0].z == 2 ); + +t.ensureIndex({z:1}); +t.ensureIndex({q:1}); + +db.getSisterDB('admin').$cmd.findOne({closeAllDatabases:1}); diff --git a/jstests/ref.js b/jstests/ref.js new file mode 100644 index 0000000..20fd6ca --- /dev/null +++ b/jstests/ref.js @@ -0,0 +1,19 @@ +// to run: +// ./mongo jstests/ref.js + +db.otherthings.drop(); +db.things.drop(); + +var other = { s : "other thing", n : 1}; +db.otherthings.save(other); + +db.things.save( { name : "abc" } ); +x = db.things.findOne(); +x.o = new DBPointer( "otherthings" , other._id ); +db.things.save(x); + +assert( db.things.findOne().o.fetch().n == 1, "dbref broken 2" ); + +other.n++; +db.otherthings.save(other); +assert( db.things.findOne().o.fetch().n == 2, "dbrefs broken" ); diff --git a/jstests/ref2.js b/jstests/ref2.js new file mode 100644 index 0000000..29640cd --- /dev/null +++ b/jstests/ref2.js @@ -0,0 +1,14 @@ + +t = db.ref2; +t.drop(); + +a = { $ref : "foo" , $id : 1 }; +b = { $ref : "foo" , $id : 2 }; + + +t.save( { name : "a" , r : a } ); +t.save( { name : "b" , r : b } ); + +assert.eq( 2 , t.find().count() , "A" ); +assert.eq( 1 , t.find( { r : a } ).count() , "B" ); +assert.eq( 1 , t.find( { r : b } ).count() , "C" ); diff --git a/jstests/ref3.js b/jstests/ref3.js new file mode 100644 index 0000000..77d6038 --- /dev/null +++ b/jstests/ref3.js @@ -0,0 +1,19 @@ +// to run: +// ./mongo jstests/ref.js + +db.otherthings.drop(); +db.things.drop(); + +var other = { s : "other thing", n : 1}; +db.otherthings.save(other); + +db.things.save( { name : "abc" } ); +x = db.things.findOne(); +x.o = new DBRef( "otherthings" , other._id ); +db.things.save(x); + +assert( db.things.findOne().o.fetch().n == 1, "dbref broken 2" ); + +other.n++; +db.otherthings.save(other); +assert( db.things.findOne().o.fetch().n == 2, "dbrefs broken" ); diff --git a/jstests/ref4.js b/jstests/ref4.js new file mode 100644 index 0000000..6e4cd95 --- /dev/null +++ b/jstests/ref4.js @@ -0,0 +1,23 @@ + +a = db.ref4a; +b = db.ref4b; + +a.drop(); +b.drop(); + +db.otherthings.drop(); +db.things.drop(); + +var other = { s : "other thing", n : 17 }; +b.save(other); + +a.save( { name : "abc" , others : [ new DBRef( "ref4b" , other._id ) , new DBPointer( "ref4b" , other._id ) ] } ); +assert( a.findOne().others[0].fetch().n == 17 , "dbref broken 1" ); + +x = Array.fetchRefs( a.findOne().others ); +assert.eq( 2 , x.length , "A" ); +assert.eq( 17 , x[0].n , "B" ); +assert.eq( 17 , x[1].n , "C" ); + + +assert.eq( 0 , Array.fetchRefs( a.findOne().others , "z" ).length , "D" ); diff --git a/jstests/regex.js b/jstests/regex.js new file mode 100644 index 0000000..f431d50 --- /dev/null +++ b/jstests/regex.js @@ -0,0 +1,24 @@ +t = db.jstests_regex; + +t.drop(); +t.save( { a: "bcd" } ); +assert.eq( 1, t.count( { a: /b/ } ) , "A" ); +assert.eq( 1, t.count( { a: /bc/ } ) , "B" ); +assert.eq( 1, t.count( { a: /bcd/ } ) , "C" ); +assert.eq( 0, t.count( { a: /bcde/ } ) , "D" ); + +t.drop(); +t.save( { a: { b: "cde" } } ); +assert.eq( 1, t.count( { 'a.b': /de/ } ) , "E" ); + +t.drop(); +t.save( { a: { b: [ "cde" ] } } ); +assert.eq( 1, t.count( { 'a.b': /de/ } ) , "F" ); + +t.drop(); +t.save( { a: [ { b: "cde" } ] } ); +assert.eq( 1, t.count( { 'a.b': /de/ } ) , "G" ); + +t.drop(); +t.save( { a: [ { b: [ "cde" ] } ] } ); +assert.eq( 1, t.count( { 'a.b': /de/ } ) , "H" ); diff --git a/jstests/regex2.js b/jstests/regex2.js new file mode 100644 index 0000000..b6a21f5 --- /dev/null +++ b/jstests/regex2.js @@ -0,0 +1,62 @@ + +t = db.regex2; +t.drop(); + +t.save( { a : "test" } ); +t.save( { a : "Test" } ); + +assert.eq( 2 , t.find().count() , "A" ); +assert.eq( 1 , t.find( { a : "Test" } ).count() , "B" ); +assert.eq( 1 , t.find( { a : "test" } ).count() , "C" ); +assert.eq( 1 , t.find( { a : /Test/ } ).count() , "D" ); +assert.eq( 1 , t.find( { a : /test/ } ).count() , "E" ); +assert.eq( 2 , t.find( { a : /test/i } ).count() , "F" ); + + +t.drop(); + +a = "\u0442\u0435\u0441\u0442"; +b = "\u0422\u0435\u0441\u0442"; + +assert( ( new RegExp( a ) ).test( a ) , "B 1" ); +assert( ! ( new RegExp( a ) ).test( b ) , "B 2" ); +assert( ( new RegExp( a , "i" ) ).test( b ) , "B 3 " ); + +t.save( { a : a } ); +t.save( { a : b } ); + + +assert.eq( 2 , t.find().count() , "C A" ); +assert.eq( 1 , t.find( { a : a } ).count() , "C B" ); +assert.eq( 1 , t.find( { a : b } ).count() , "C C" ); +assert.eq( 1 , t.find( { a : new RegExp( a ) } ).count() , "C D" ); +assert.eq( 1 , t.find( { a : new RegExp( b ) } ).count() , "C E" ); +assert.eq( 2 , t.find( { a : new RegExp( a , "i" ) } ).count() , "C F is spidermonkey built with UTF-8 support?" ); + + +// same tests as above but using {$regex: "a|b", $options: "imx"} syntax. +t.drop(); + +t.save( { a : "test" } ); +t.save( { a : "Test" } ); + +assert.eq( 2 , t.find().count() , "obj A" ); +assert.eq( 1 , t.find( { a : {$regex:"Test"} } ).count() , "obj D" ); +assert.eq( 1 , t.find( { a : {$regex:"test"} } ).count() , "obj E" ); +assert.eq( 2 , t.find( { a : {$regex:"test", $options:"i"} } ).count() , "obj F" ); +assert.eq( 2 , t.find( { a : {$options:"i", $regex:"test"} } ).count() , "obj F rev" ); // both orders should work + + +t.drop(); + +a = "\u0442\u0435\u0441\u0442"; +b = "\u0422\u0435\u0441\u0442"; + +t.save( { a : a } ); +t.save( { a : b } ); + + +assert.eq( 1 , t.find( { a : {$regex: a} } ).count() , "obj C D" ); +assert.eq( 1 , t.find( { a : {$regex: b} } ).count() , "obj C E" ); +assert.eq( 2 , t.find( { a : {$regex: a , $options: "i" } } ).count() , "obj C F is spidermonkey built with UTF-8 support?" ); + diff --git a/jstests/regex3.js b/jstests/regex3.js new file mode 100644 index 0000000..ee8d9cf --- /dev/null +++ b/jstests/regex3.js @@ -0,0 +1,36 @@ + +t = db.regex3; +t.drop(); + +t.save( { name : "eliot" } ); +t.save( { name : "emily" } ); +t.save( { name : "bob" } ); +t.save( { name : "aaron" } ); + +assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "no index count" ); +assert.eq( 4 , t.find( { name : /^e.*/ } ).explain().nscanned , "no index explain" ); +t.ensureIndex( { name : 1 } ); +assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "index count" ); +assert.eq( 2 , t.find( { name : /^e.*/ } ).explain().nscanned , "index explain" ); // SERVER-239 + +t.drop(); + +t.save( { name : "aa" } ); +t.save( { name : "ab" } ); +t.save( { name : "ac" } ); +t.save( { name : "c" } ); + +assert.eq( 3 , t.find( { name : /^aa*/ } ).count() , "B ni" ); +t.ensureIndex( { name : 1 } ); +assert.eq( 3 , t.find( { name : /^aa*/ } ).count() , "B i 1" ); +assert.eq( 3 , t.find( { name : /^aa*/ } ).explain().nscanned , "B i 1 e" ); + +assert.eq( 2 , t.find( { name : /^a[ab]/ } ).count() , "B i 2" ); +assert.eq( 2 , t.find( { name : /^a[bc]/ } ).count() , "B i 3" ); + +t.drop(); + +t.save( { name: "" } ); +assert.eq( 1, t.count( { name: /^a?/ } ) , "C 1" ); +t.ensureIndex( { name: 1 } ); +assert.eq( 1, t.count( { name: /^a?/ } ) , "C 2"); diff --git a/jstests/regex4.js b/jstests/regex4.js new file mode 100644 index 0000000..568c937 --- /dev/null +++ b/jstests/regex4.js @@ -0,0 +1,18 @@ + +t = db.regex3; +t.drop(); + +t.save( { name : "eliot" } ); +t.save( { name : "emily" } ); +t.save( { name : "bob" } ); +t.save( { name : "aaron" } ); + +assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "no index count" ); +assert.eq( 4 , t.find( { name : /^e.*/ } ).explain().nscanned , "no index explain" ); +//assert.eq( 2 , t.find( { name : { $ne : /^e.*/ } } ).count() , "no index count ne" ); // SERVER-251 + +t.ensureIndex( { name : 1 } ); + +assert.eq( 2 , t.find( { name : /^e.*/ } ).count() , "index count" ); +assert.eq( 2 , t.find( { name : /^e.*/ } ).explain().nscanned , "index explain" ); // SERVER-239 +//assert.eq( 2 , t.find( { name : { $ne : /^e.*/ } } ).count() , "index count ne" ); // SERVER-251 diff --git a/jstests/regex5.js b/jstests/regex5.js new file mode 100644 index 0000000..7fe39d5 --- /dev/null +++ b/jstests/regex5.js @@ -0,0 +1,13 @@ + +t = db.regex5 +t.drop() + +t.save( { x : [ "abc" , "xyz" ] } ) +t.save( { x : [ "ac" , "xyz" ] } ) + +a = /.*b.*c/ +x = /.*y.*/ + +assert.eq( 1 , t.find( { x : a } ).count() , "A" ) +assert.eq( 2 , t.find( { x : x } ).count() , "B" ) +// assert.eq( 1 , t.find( { x : { $all : [ a , x ] } } ).count() , "C" ) // SERVER-505 diff --git a/jstests/regex6.js b/jstests/regex6.js new file mode 100644 index 0000000..d25367c --- /dev/null +++ b/jstests/regex6.js @@ -0,0 +1,19 @@ +// contributed by Andrew Kempe +t = db.regex6; +t.drop(); + +t.save( { name : "eliot" } ); +t.save( { name : "emily" } ); +t.save( { name : "bob" } ); +t.save( { name : "aaron" } ); + +t.ensureIndex( { name : 1 } ); + +assert.eq( 0 , t.find( { name : /^\// } ).count() , "index count" ); +assert.eq( 0 , t.find( { name : /^\// } ).explain().nscanned , "index explain" ); +assert.eq( 0 , t.find( { name : /^é/ } ).explain().nscanned , "index explain" ); +assert.eq( 0 , t.find( { name : /^\é/ } ).explain().nscanned , "index explain" ); +assert.eq( 0 , t.find( { name : /^\./ } ).explain().nscanned , "index explain" ); +assert.eq( 4 , t.find( { name : /^./ } ).explain().nscanned , "index explain" ); + +assert.eq( 4 , t.find( { name : /^\Qblah\E/ } ).explain().nscanned , "index explain" ); diff --git a/jstests/remove.js b/jstests/remove.js new file mode 100644 index 0000000..bec015c --- /dev/null +++ b/jstests/remove.js @@ -0,0 +1,25 @@ +// remove.js +// unit test for db remove + +t = db.removetest; + +function f(n,dir) { + t.ensureIndex({x:dir||1}); + for( i = 0; i < n; i++ ) t.save( { x:3, z:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + + assert.eq( n , t.find().count() ); + t.remove({x:3}); + + assert.eq( 0 , t.find().count() ); + + assert( t.findOne() == null , "A:" + tojson( t.findOne() ) ); + assert( t.validate().valid , "B" ); +} + +t.drop(); +f(300, 1); + +f(500, -1); + +assert(t.validate().valid , "C" ); + diff --git a/jstests/remove2.js b/jstests/remove2.js new file mode 100644 index 0000000..ff122a0 --- /dev/null +++ b/jstests/remove2.js @@ -0,0 +1,41 @@ +// remove2.js +// a unit test for db remove + +t = db.removetest2; + +function f() { + t.save( { x:[3,3,3,3,3,3,3,3,4,5,6], z:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + t.save( { x: 9 } ); + t.save( { x: 1 } ); + + t.remove({x:3}); + + assert( t.findOne({x:3}) == null ); + assert( t.validate().valid ); +} + +x = 0; + +function g() { + t.save( { x:[3,4,5,6], z:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + t.save( { x:[7,8,9], z:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ); + + t.remove( {x : {$gte:3}, $atomic:x++ } ); + + assert( t.findOne({x:3}) == null ); + assert( t.findOne({x:8}) == null ); + assert( t.validate().valid ); +} + +t.drop(); +f(); +t.drop(); +g(); + +t.ensureIndex({x:1}); +t.remove({}); +f(); +t.drop(); +t.ensureIndex({x:1}); +g(); + diff --git a/jstests/remove3.js b/jstests/remove3.js new file mode 100644 index 0000000..fe1a754 --- /dev/null +++ b/jstests/remove3.js @@ -0,0 +1,18 @@ + +t = db.remove3; +t.drop(); + +for ( i=1; i<=8; i++){ + t.save( { _id : i , x : i } ); +} + +assert.eq( 8 , t.count() , "A" ); + +t.remove( { x : { $lt : 5 } } ); +assert.eq( 4 , t.count() , "B" ); + +t.remove( { _id : 5 } ); +assert.eq( 3 , t.count() , "C" ); + +t.remove( { _id : { $lt : 8 } } , "D" ); +assert.eq( 1 , t.count() , "D" ); diff --git a/jstests/remove4.js b/jstests/remove4.js new file mode 100644 index 0000000..bd007ed --- /dev/null +++ b/jstests/remove4.js @@ -0,0 +1,10 @@ +t = db.remove4; +t.drop(); + +t.save ( { a : 1 , b : 1 } ); +t.save ( { a : 2 , b : 1 } ); +t.save ( { a : 3 , b : 1 } ); + +assert.eq( 3 , t.find().length() ); +t.remove( { b : 1 } ); +assert.eq( 0 , t.find().length() ); diff --git a/jstests/remove5.js b/jstests/remove5.js new file mode 100644 index 0000000..be4f0b4 --- /dev/null +++ b/jstests/remove5.js @@ -0,0 +1,24 @@ +f = db.jstests_remove5; +f.drop(); + +getLastError = function() { + return db.runCommand( { getlasterror : 1 } ); +} + +f.remove( {} ); +assert.eq( 0, getLastError().n ); +f.save( {a:1} ); +f.remove( {} ); +assert.eq( 1, getLastError().n ); +for( i = 0; i < 10; ++i ) { + f.save( {i:i} ); +} +f.remove( {} ); +assert.eq( 10, getLastError().n ); +assert.eq( 10, db.getPrevError().n ); +assert.eq( 1, db.getPrevError().nPrev ); + +f.findOne(); +assert.eq( 0, getLastError().n ); +assert.eq( 10, db.getPrevError().n ); +assert.eq( 2, db.getPrevError().nPrev ); diff --git a/jstests/remove6.js b/jstests/remove6.js new file mode 100644 index 0000000..d843aee --- /dev/null +++ b/jstests/remove6.js @@ -0,0 +1,38 @@ + +t = db.remove6; +t.drop(); + +N = 1000; + +function pop(){ + t.drop(); + for ( var i=0; i<N; i++ ){ + t.save( { x : 1 , tags : [ "a" , "b" , "c" ] } ); + } +} + +function del(){ + t.remove( { tags : { $in : [ "a" , "c" ] } } ); +} + +function test( n , idx ){ + pop(); + assert.eq( N , t.count() , n + " A " + idx ); + if ( idx ) + t.ensureIndex( idx ); + del(); + var e = db.getLastError(); + assert( e == null , "error deleting: " + e ); + assert.eq( 0 , t.count() , n + " B " + idx ); +} + +test( "a" ); +test( "b" , { x : 1 } ); +test( "c" , { tags : 1 } ); + +N = 5000 + +test( "a2" ); +test( "b2" , { x : 1 } ); +test( "c2" , { tags : 1 } ); + diff --git a/jstests/remove7.js b/jstests/remove7.js new file mode 100644 index 0000000..50c6ac1 --- /dev/null +++ b/jstests/remove7.js @@ -0,0 +1,35 @@ + +t = db.remove7 +t.drop(); + + + +function getTags( n ){ + n = n || 5; + var a = []; + for ( var i=0; i<n; i++ ){ + var v = Math.ceil( 20 * Math.random() ); + a.push( v ); + } + + return a; +} + +for ( i=0; i<1000; i++ ){ + t.save( { tags : getTags() } ); +} + +t.ensureIndex( { tags : 1 } ); + +for ( i=0; i<200; i++ ){ + for ( var j=0; j<10; j++ ) + t.save( { tags : getTags( 100 ) } ); + var q = { tags : { $in : getTags( 10 ) } }; + var before = t.find( q ).count(); + t.remove( q ); + var o = db.getLastErrorObj(); + var after = t.find( q ).count(); + assert.eq( 0 , after , "not zero after!" ); + assert.isnull( o.err , "error: " + tojson( o ) ); +} + diff --git a/jstests/remove8.js b/jstests/remove8.js new file mode 100644 index 0000000..3ab53f3 --- /dev/null +++ b/jstests/remove8.js @@ -0,0 +1,21 @@ + +t = db.remove8; +t.drop(); + +N = 1000; + +function fill(){ + for ( var i=0; i<N; i++ ){ + t.save( { x : i } ); + } +} + +fill(); +assert.eq( N , t.count() , "A" ); +t.remove( {} ) +assert.eq( 0 , t.count() , "B" ); + +fill(); +assert.eq( N , t.count() , "C" ); +db.eval( function(){ db.remove8.remove( {} ); } ) +assert.eq( 0 , t.count() , "D" ); diff --git a/jstests/rename.js b/jstests/rename.js new file mode 100644 index 0000000..3ace968 --- /dev/null +++ b/jstests/rename.js @@ -0,0 +1,48 @@ +admin = db.getMongo().getDB( "admin" ); + +a = db.jstests_rename_a; +b = db.jstests_rename_b; +c = db.jstests_rename_c; + +a.drop(); +b.drop(); +c.drop(); + +a.save( {a: 1} ); +a.save( {a: 2} ); +a.ensureIndex( {a:1} ); +a.ensureIndex( {b:1} ); + +c.save( {a: 100} ); +assert.commandFailed( admin.runCommand( {renameCollection:"test.jstests_rename_a", to:"test.jstests_rename_c"} ) ); + +assert.commandWorked( admin.runCommand( {renameCollection:"test.jstests_rename_a", to:"test.jstests_rename_b"} ) ); +assert.eq( 0, a.find().count() ); + +assert.eq( 2, b.find().count() ); +assert( db.system.namespaces.findOne( {name:"test.jstests_rename_b" } ) ); +assert( !db.system.namespaces.findOne( {name:"test.jstests_rename_a" } ) ); +assert.eq( 3, db.system.indexes.find( {ns:"test.jstests_rename_b"} ).count() ); +assert( b.find( {a:1} ).explain().cursor.match( /^BtreeCursor/ ) ); + +// now try renaming a capped collection + +a.drop(); +b.drop(); +c.drop(); + +db.createCollection( "jstests_rename_a", {capped:true,size:100} ); +for( i = 0; i < 10; ++i ) { + a.save( { i: i } ); +} +assert.commandWorked( admin.runCommand( {renameCollection:"test.jstests_rename_a", to:"test.jstests_rename_b"} ) ); +assert.eq( 1, b.count( {i:9} ) ); +for( i = 10; i < 20; ++i ) { + b.save( { i: i } ); +} +assert.eq( 0, b.count( {i:9} ) ); +assert.eq( 1, b.count( {i:19} ) ); + +assert( db.system.namespaces.findOne( {name:"test.jstests_rename_b" } ) ); +assert( !db.system.namespaces.findOne( {name:"test.jstests_rename_a" } ) ); +assert.eq( true, db.system.namespaces.findOne( {name:"test.jstests_rename_b"} ).options.capped ); diff --git a/jstests/rename2.js b/jstests/rename2.js new file mode 100644 index 0000000..a06268f --- /dev/null +++ b/jstests/rename2.js @@ -0,0 +1,19 @@ + + +a = db.rename2a; +b = db.rename2b; + +a.drop(); +b.drop(); + +a.save( { x : 1 } ) +a.save( { x : 2 } ) +a.save( { x : 3 } ) + +assert.eq( 3 , a.count() , "A" ) +assert.eq( 0 , b.count() , "B" ) + +assert( a.renameCollection( "rename2b" ) , "the command" ); + +assert.eq( 0 , a.count() , "C" ) +assert.eq( 3 , b.count() , "D" ) diff --git a/jstests/rename3.js b/jstests/rename3.js new file mode 100644 index 0000000..5e1005f --- /dev/null +++ b/jstests/rename3.js @@ -0,0 +1,25 @@ + + +a = db.rename3a +b = db.rename3b + +a.drop(); +b.drop() + +a.save( { x : 1 } ); +b.save( { x : 2 } ); + +assert.eq( 1 , a.findOne().x , "before 1a" ); +assert.eq( 2 , b.findOne().x , "before 2a" ); + +res = b.renameCollection( a._shortName ); +assert.eq( 0 , res.ok , "should fail: " + tojson( res ) ); + +assert.eq( 1 , a.findOne().x , "before 1b" ); +assert.eq( 2 , b.findOne().x , "before 2b" ); + +res = b.renameCollection( a._shortName , true ) +assert.eq( 1 , res.ok , "should succeed:" + tojson( res ) ); + +assert.eq( 2 , a.findOne().x , "after 1" ); +assert.isnull( b.findOne() , "after 2" ); diff --git a/jstests/repair.js b/jstests/repair.js new file mode 100644 index 0000000..5548c2b --- /dev/null +++ b/jstests/repair.js @@ -0,0 +1,6 @@ +t = db.jstests_repair; +t.drop(); +t.save( { i:1 } ); +db.repairDatabase(); +v = t.validate(); +assert( v.valid , "not valid! " + tojson( v ) ); diff --git a/jstests/repl/basic1.js b/jstests/repl/basic1.js new file mode 100644 index 0000000..9668a91 --- /dev/null +++ b/jstests/repl/basic1.js @@ -0,0 +1,59 @@ + +// test repl basics +// data on master/slave is the same + +var rt = new ReplTest( "basic1" ); + +m = rt.start( true ); +s = rt.start( false ); + +function hash( db ){ + var s = ""; + var a = db.getCollectionNames(); + a = a.sort(); + a.forEach( + function(cn){ + var c = db.getCollection( cn ); + s += cn + "\t" + c.find().count() + "\n"; + c.find().sort( { _id : 1 } ).forEach( + function(o){ + s += tojson( o , "" , true ) + "\n"; + } + ); + } + ); + return s; +} + +am = m.getDB( "foo" ); +as = s.getDB( "foo" ); + +function check( note ){ + var start = new Date(); + var x,y; + while ( (new Date()).getTime() - start.getTime() < 30000 ){ + x = hash( am ); + y = hash( as ); + if ( x == y ) + return; + sleep( 200 ); + } + assert.eq( x , y , note ); +} + +am.a.save( { x : 1 } ); +check( "A" ); + +am.a.save( { x : 5 } ); + +am.a.update( {} , { $inc : { x : 1 } } ); +check( "B" ); + +am.a.update( {} , { $inc : { x : 1 } } , false , true ); +check( "C" ); + +rt.stop(); + + + + diff --git a/jstests/repl/pair1.js b/jstests/repl/pair1.js new file mode 100644 index 0000000..7004048 --- /dev/null +++ b/jstests/repl/pair1.js @@ -0,0 +1,99 @@ +// Basic pairing test + +var baseName = "jstests_pair1test"; + +debug = function( p ) { +// print( p ); +} + +ismaster = function( n ) { + var im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); +// print( "ismaster: " + tojson( im ) ); + assert( im, "command ismaster failed" ); + return im.ismaster; +} + +var writeOneIdx = 0; + +writeOne = function( n ) { + n.getDB( baseName ).z.save( { _id: new ObjectId(), i: ++writeOneIdx } ); +} + +getCount = function( n ) { + return n.getDB( baseName ).z.find( { i: writeOneIdx } ).toArray().length; +} + +checkWrite = function( m, s ) { + writeOne( m ); + assert.eq( 1, getCount( m ) ); + check( s ); +} + +check = function( s ) { + s.setSlaveOk(); + assert.soon( function() { + return 1 == getCount( s ); + } ); +} + +// check that slave reads and writes are guarded +checkSlaveGuard = function( s ) { + var t = s.getDB( baseName + "-temp" ).temp; + assert.throws( t.find().count, {}, "not master" ); + assert.throws( t.find(), {}, "not master", "find did not assert" ); + + checkError = function() { + assert.eq( "not master", s.getDB( "admin" ).getLastError() ); + s.getDB( "admin" ).resetError(); + } + s.getDB( "admin" ).resetError(); + t.save( {x:1} ); + checkError(); + t.update( {}, {x:2}, true ); + checkError(); + t.remove( {x:0} ); + checkError(); +} + +doTest = function( signal ) { + + ports = allocatePorts( 3 ); + + a = new MongodRunner( ports[ 0 ], "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( ports[ 1 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 2 ], "127.0.0.1:" + ports[ 0 ] ); + r = new MongodRunner( ports[ 2 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 1 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( l, r, a ); + rp.start(); + rp.waitForSteadyState(); + + checkSlaveGuard( rp.slave() ); + + checkWrite( rp.master(), rp.slave() ); + + debug( "kill first" ); + rp.killNode( rp.master(), signal ); + rp.waitForSteadyState( [ 1, null ], rp.slave().host ); + writeOne( rp.master() ); + + debug( "restart first" ); + rp.start( true ); + rp.waitForSteadyState(); + check( rp.slave() ); + checkWrite( rp.master(), rp.slave() ); + + debug( "kill second" ); + rp.killNode( rp.master(), signal ); + rp.waitForSteadyState( [ 1, null ], rp.slave().host ); + + debug( "restart second" ); + rp.start( true ); + rp.waitForSteadyState( [ 1, 0 ], rp.master().host ); + checkWrite( rp.master(), rp.slave() ); + + ports.forEach( function( x ) { stopMongod( x ); } ); + +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/pair2.js b/jstests/repl/pair2.js new file mode 100644 index 0000000..2491fb2 --- /dev/null +++ b/jstests/repl/pair2.js @@ -0,0 +1,71 @@ +// Pairing resync + +var baseName = "jstests_pair2test"; + +ismaster = function( n ) { + im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); + assert( im ); + return im.ismaster; +} + +soonCount = function( m, count ) { + assert.soon( function() { +// print( "counting" ); +//// print( "counted: " + l.getDB( baseName ).z.find().count() ); + return m.getDB( baseName ).z.find().count() == count; + } ); +} + +doTest = function( signal ) { + + ports = allocatePorts( 3 ); + + a = new MongodRunner( ports[ 0 ], "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( ports[ 1 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 2 ], "127.0.0.1:" + ports[ 0 ] ); + r = new MongodRunner( ports[ 2 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 1 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( l, r, a ); + rp.start(); + rp.waitForSteadyState(); + + rp.slave().setSlaveOk(); + mz = rp.master().getDB( baseName ).z; + + mz.save( { _id: new ObjectId() } ); + soonCount( rp.slave(), 1 ); + assert.eq( 0, rp.slave().getDB( "admin" ).runCommand( { "resync" : 1 } ).ok ); + + sleep( 3000 ); // allow time to finish clone and save ReplSource + rp.killNode( rp.slave(), signal ); + rp.waitForSteadyState( [ 1, null ], rp.master().host ); + + big = new Array( 2000 ).toString(); + for( i = 0; i < 1000; ++i ) + mz.save( { _id: new ObjectId(), i: i, b: big } ); + + rp.start( true ); + rp.waitForSteadyState( [ 1, 0 ], rp.master().host ); + + sleep( 15000 ); + + rp.slave().setSlaveOk(); + assert.soon( function() { + ret = rp.slave().getDB( "admin" ).runCommand( { "resync" : 1 } ); +// printjson( ret ); + return 1 == ret.ok; + } ); + + sleep( 8000 ); + soonCount( rp.slave(), 1001 ); + sz = rp.slave().getDB( baseName ).z + assert.eq( 1, sz.find( { i: 0 } ).count() ); + assert.eq( 1, sz.find( { i: 999 } ).count() ); + + assert.eq( 0, rp.slave().getDB( "admin" ).runCommand( { "resync" : 1 } ).ok ); + + ports.forEach( function( x ) { stopMongod( x ); } ); + +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/pair3.js b/jstests/repl/pair3.js new file mode 100644 index 0000000..506e173 --- /dev/null +++ b/jstests/repl/pair3.js @@ -0,0 +1,235 @@ +// test arbitration + +var baseName = "jstests_pair3test"; + +ismaster = function( n ) { + var im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); + print( "ismaster: " + tojson( im ) ); + assert( im, "command ismaster failed" ); + return im.ismaster; +} + +// bring up node connections before arbiter connections so that arb can forward to node when expected +connect = function() { + if ( lp == null ) { + lp = startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + } + if ( rp == null ) { + rp = startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); + } + if ( al == null ) { + al = startMongoProgram( "mongobridge", "--port", alPort, "--dest", "localhost:" + aPort ); + } + if ( ar == null ) { + ar = startMongoProgram( "mongobridge", "--port", arPort, "--dest", "localhost:" + aPort ); + } +} + +disconnectNode = function( mongo ) { + if ( lp ) { + stopMongoProgram( lpPort ); + lp = null; + } + if ( rp ) { + stopMongoProgram( rpPort ); + rp = null; + } + if ( mongo.host.match( new RegExp( "^127.0.0.1:" + lPort + "$" ) ) ) { + stopMongoProgram( alPort ); + al = null; + } else if ( mongo.host.match( new RegExp( "^127.0.0.1:" + rPort + "$" ) ) ) { + stopMongoProgram( arPort ); + ar = null; + } else { + assert( false, "don't know how to disconnect node: " + mongo ); + } +} + +doTest1 = function() { + al = ar = lp = rp = null; + ports = allocatePorts( 7 ); + aPort = ports[ 0 ]; + alPort = ports[ 1 ]; + arPort = ports[ 2 ]; + lPort = ports[ 3 ]; + lpPort = ports[ 4 ]; + rPort = ports[ 5 ]; + rpPort = ports[ 6 ]; + + connect(); + + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + alPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + arPort ); + + pair = new ReplPair( l, r, a ); + + // normal startup + pair.start(); + pair.waitForSteadyState(); + + // disconnect slave + disconnectNode( pair.slave() ); + pair.waitForSteadyState( [ 1, -3 ], pair.master().host ); + + // disconnect master + disconnectNode( pair.master() ); + pair.waitForSteadyState( [ -3, -3 ] ); + + // reconnect + connect(); + pair.waitForSteadyState(); + + // disconnect master + disconnectNode( pair.master() ); + pair.waitForSteadyState( [ 1, -3 ], pair.slave().host, true ); + + // disconnect new master + disconnectNode( pair.master() ); + pair.waitForSteadyState( [ -3, -3 ] ); + + // reconnect + connect(); + pair.waitForSteadyState(); + + // disconnect slave + disconnectNode( pair.slave() ); + pair.waitForSteadyState( [ 1, -3 ], pair.master().host ); + + // reconnect slave + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.master().host ); + + // disconnect master + disconnectNode( pair.master() ); + pair.waitForSteadyState( [ 1, -3 ], pair.slave().host, true ); + + // reconnect old master + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.master().host ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); +} + +// this time don't start connected +doTest2 = function() { + al = ar = lp = rp = null; + ports = allocatePorts( 7 ); + aPort = ports[ 0 ]; + alPort = ports[ 1 ]; + arPort = ports[ 2 ]; + lPort = ports[ 3 ]; + lpPort = ports[ 4 ]; + rPort = ports[ 5 ]; + rpPort = ports[ 6 ]; + + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + alPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + arPort ); + + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState( [ -3, -3 ] ); + + startMongoProgram( "mongobridge", "--port", arPort, "--dest", "localhost:" + aPort ); + + // there hasn't been an initial sync, no no node will become master + + for( i = 0; i < 10; ++i ) { + assert( pair.isMaster( pair.right() ) == -3 && pair.isMaster( pair.left() ) == -3 ); + sleep( 500 ); + } + + stopMongoProgram( arPort ); + + startMongoProgram( "mongobridge", "--port", alPort, "--dest", "localhost:" + aPort ); + + for( i = 0; i < 10; ++i ) { + assert( pair.isMaster( pair.right() ) == -3 && pair.isMaster( pair.left() ) == -3 ); + sleep( 500 ); + } + + stopMongoProgram( alPort ); + + // connect l and r without a + + startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); + + pair.waitForSteadyState( [ 1, 0 ] ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); +} + +// recover from master - master setup +doTest3 = function() { + al = ar = lp = rp = null; + ports = allocatePorts( 7 ); + aPort = ports[ 0 ]; + alPort = ports[ 1 ]; + arPort = ports[ 2 ]; + lPort = ports[ 3 ]; + lpPort = ports[ 4 ]; + rPort = ports[ 5 ]; + rpPort = ports[ 6 ]; + + connect(); + + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + alPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + arPort ); + + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState(); + + // now can only talk to arbiter + stopMongoProgram( lpPort ); + stopMongoProgram( rpPort ); + pair.waitForSteadyState( [ 1, 1 ], null, true ); + + // recover + startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); + pair.waitForSteadyState( [ 1, 0 ], null, true ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); +} + +// check that initial sync is persistent +doTest4 = function( signal ) { + al = ar = lp = rp = null; + ports = allocatePorts( 7 ); + aPort = ports[ 0 ]; + alPort = ports[ 1 ]; + arPort = ports[ 2 ]; + lPort = ports[ 3 ]; + lpPort = ports[ 4 ]; + rPort = ports[ 5 ]; + rpPort = ports[ 6 ]; + + connect(); + + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + alPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + arPort ); + + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState(); + + pair.killNode( pair.left(), signal ); + pair.killNode( pair.right(), signal ); + stopMongoProgram( rpPort ); + stopMongoProgram( lpPort ); + + // now can only talk to arbiter + pair.start( true ); + pair.waitForSteadyState( [ 1, 1 ], null, true ); +} + +doTest1(); +doTest2(); +doTest3(); +doTest4( 15 ); +doTest4( 9 ); diff --git a/jstests/repl/pair4.js b/jstests/repl/pair4.js new file mode 100644 index 0000000..5a59c16 --- /dev/null +++ b/jstests/repl/pair4.js @@ -0,0 +1,159 @@ +// data consistency after master-master + +var baseName = "jstests_pair4test"; + +debug = function( o ) { + printjson( o ); +} + +ismaster = function( n ) { + var im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); + print( "ismaster: " + tojson( im ) ); + assert( im, "command ismaster failed" ); + return im.ismaster; +} + +connect = function() { + startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); +} + +disconnect = function() { + stopMongoProgram( lpPort ); + stopMongoProgram( rpPort ); +} + +write = function( m, n, id ) { + if ( id ) { + save = { _id:id, n:n }; + } else { + save = { n:n }; + } + m.getDB( baseName ).getCollection( baseName ).save( save ); +} + +check = function( m, n, id ) { + m.setSlaveOk(); + if ( id ) { + find = { _id:id, n:n }; + } else { + find = { n:n }; + } + assert.soon( function() { return m.getDB( baseName ).getCollection( baseName ).find( find ).count() > 0; }, + "failed waiting for " + m + " value of n to be " + n ); +} + +checkCount = function( m, c ) { + m.setSlaveOk(); + assert.soon( function() { + actual = m.getDB( baseName ).getCollection( baseName ).find().count(); + print( actual ); + return c == actual; }, + "count failed for " + m ); +} + +coll = function( m ) { + return m.getDB( baseName ).getCollection( baseName ); +} + +db2Coll = function( m ) { + return m.getDB( baseName + "_second" ).getCollection( baseName ); +} + +doTest = function( recover, newMaster, newSlave ) { + ports = allocatePorts( 5 ); + aPort = ports[ 0 ]; + lPort = ports[ 1 ]; + lpPort = ports[ 2 ]; + rPort = ports[ 3 ]; + rpPort = ports[ 4 ]; + + // start normally + connect(); + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + aPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + aPort ); + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState(); + + firstMaster = pair.master(); + firstSlave = pair.slave(); + + write( pair.master(), 0 ); + write( pair.master(), 1 ); + check( pair.slave(), 0 ); + check( pair.slave(), 1 ); + + // now each can only talk to arbiter + disconnect(); + pair.waitForSteadyState( [ 1, 1 ], null, true ); + + m = newMaster(); + write( m, 10 ); + write( m, 100, "a" ); + coll( m ).update( {n:1}, {$set:{n:2}} ); + db2Coll( m ).save( {n:500} ); + db2Coll( m ).findOne(); + + s = newSlave(); + write( s, 20 ); + write( s, 200, "a" ); + coll( s ).update( {n:1}, {n:1,m:3} ); + db2Coll( s ).save( {_id:"a",n:600} ); + db2Coll( s ).findOne(); + + // recover + recover(); + + nodes = [ pair.right(), pair.left() ]; + + nodes.forEach( function( x ) { checkCount( x, 5 ); } ); + nodes.forEach( function( x ) { [ 0, 10, 20, 100 ].forEach( function( y ) { check( x, y ); } ); } ); + + checkM = function( c ) { + assert.soon( function() { + obj = coll( c ).findOne( {n:2} ); + printjson( obj ); + return obj.m == undefined; + }, "n:2 test for " + c + " failed" ); + }; + nodes.forEach( function( x ) { checkM( x ); } ); + + // check separate database + nodes.forEach( function( x ) { assert.soon( function() { + r = db2Coll( x ).findOne( {_id:"a"} ); + debug( r ); + if ( r == null ) { + return false; + } + return 600 == r.n; + } ) } ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); + +} + +debug( "basic test" ); +doTest( function() { + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.right().host, true ); + }, function() { return pair.right(); }, function() { return pair.left(); } ); + +doRestartTest = function( signal ) { + doTest( function() { + if ( signal == 9 ) { + sleep( 3000 ); + } + pair.killNode( firstMaster, signal ); + connect(); + pair.start( true ); + pair.waitForSteadyState( [ 1, 0 ], firstSlave.host, true ); + }, function() { return firstSlave; }, function() { return firstMaster; } ); +} + +debug( "sigterm restart test" ); +doRestartTest( 15 ) // SIGTERM + +debug( "sigkill restart test" ); +doRestartTest( 9 ) // SIGKILL diff --git a/jstests/repl/pair5.js b/jstests/repl/pair5.js new file mode 100644 index 0000000..ed8c72d --- /dev/null +++ b/jstests/repl/pair5.js @@ -0,0 +1,95 @@ +// writes to new master while making master-master logs consistent + +var baseName = "jstests_pair5test"; + +debug = function( p ) { + print( p ); +} + +ismaster = function( n ) { + var im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); + print( "ismaster: " + tojson( im ) ); + assert( im, "command ismaster failed" ); + return im.ismaster; +} + +connect = function() { + startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); +} + +disconnect = function() { + stopMongoProgram( lpPort ); + stopMongoProgram( rpPort ); +} + +write = function( m, n, id ) { + if ( id ) { + save = { _id:id, n:n }; + } else { + save = { n:n }; + } + m.getDB( baseName ).getCollection( baseName ).save( save ); +} + +checkCount = function( m, c ) { + m.setSlaveOk(); + assert.soon( function() { + actual = m.getDB( baseName ).getCollection( baseName ).find().count(); + print( actual ); + return c == actual; }, + "count failed for " + m ); +} + +doTest = function( nSlave, opIdMem ) { + ports = allocatePorts( 5 ); + aPort = ports[ 0 ]; + lPort = ports[ 1 ]; + lpPort = ports[ 2 ]; + rPort = ports[ 3 ]; + rpPort = ports[ 4 ]; + + // start normally + connect(); + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + aPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + aPort ); + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState(); + + // now each can only talk to arbiter + disconnect(); + pair.waitForSteadyState( [ 1, 1 ], null, true ); + + // left will become slave + for( i = 0; i < nSlave; ++i ) { + write( pair.left(), i, i ); + } + pair.left().getDB( baseName ).getCollection( baseName ).findOne(); + + for( i = 10000; i < 15000; ++i ) { + write( pair.right(), i, i ); + } + pair.right().getDB( baseName ).getCollection( baseName ).findOne(); + + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.right().host, true ); + + pair.master().getDB( baseName ).getCollection( baseName ).update( {_id:nSlave - 1}, {_id:nSlave - 1,n:-1}, true ); + assert.eq( -1, pair.master().getDB( baseName ).getCollection( baseName ).findOne( {_id:nSlave - 1} ).n ); + checkCount( pair.master(), 5000 + nSlave ); + assert.eq( -1, pair.master().getDB( baseName ).getCollection( baseName ).findOne( {_id:nSlave - 1} ).n ); + pair.slave().setSlaveOk(); + assert.soon( function() { + n = pair.slave().getDB( baseName ).getCollection( baseName ).findOne( {_id:nSlave - 1} ).n; + print( n ); + return -1 == n; + } ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); + +} + +doTest( 5000, 100000000 ); +doTest( 5000, 100 ); // force op id converstion to collection based storage diff --git a/jstests/repl/pair6.js b/jstests/repl/pair6.js new file mode 100644 index 0000000..b249fc0 --- /dev/null +++ b/jstests/repl/pair6.js @@ -0,0 +1,115 @@ +// pairing cases where oplogs run out of space + +var baseName = "jstests_pair6test"; + +debug = function( p ) { + print( p ); +} + +ismaster = function( n ) { + var im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); + print( "ismaster: " + tojson( im ) ); + assert( im, "command ismaster failed" ); + return im.ismaster; +} + +connect = function() { + startMongoProgram( "mongobridge", "--port", lpPort, "--dest", "localhost:" + lPort ); + startMongoProgram( "mongobridge", "--port", rpPort, "--dest", "localhost:" + rPort ); +} + +disconnect = function() { + stopMongoProgram( lpPort ); + stopMongoProgram( rpPort ); +} + +checkCount = function( m, c ) { + m.setSlaveOk(); + assert.soon( function() { + actual = m.getDB( baseName ).getCollection( baseName ).find().count(); + print( actual ); + return c == actual; }, + "expected count " + c + " for " + m ); +} + +resetSlave = function( s ) { + s.setSlaveOk(); + assert.soon( function() { + ret = s.getDB( "admin" ).runCommand( { "resync" : 1 } ); + // printjson( ret ); + return 1 == ret.ok; + } ); +} + +big = new Array( 2000 ).toString(); + +doTest = function() { + ports = allocatePorts( 5 ); + aPort = ports[ 0 ]; + lPort = ports[ 1 ]; + lpPort = ports[ 2 ]; + rPort = ports[ 3 ]; + rpPort = ports[ 4 ]; + + // start normally + connect(); + a = new MongodRunner( aPort, "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( lPort, "/data/db/" + baseName + "-left", "127.0.0.1:" + rpPort, "127.0.0.1:" + aPort ); + r = new MongodRunner( rPort, "/data/db/" + baseName + "-right", "127.0.0.1:" + lpPort, "127.0.0.1:" + aPort ); + pair = new ReplPair( l, r, a ); + pair.start(); + pair.waitForSteadyState(); + + disconnect(); + pair.waitForSteadyState( [ 1, 1 ], null, true ); + + print( "test one" ); + + // fill new slave oplog + for( i = 0; i < 1000; ++i ) { + pair.left().getDB( baseName ).getCollection( baseName ).save( {b:big} ); + } + pair.left().getDB( baseName ).getCollection( baseName ).findOne(); + + // write single to new master + pair.right().getDB( baseName ).getCollection( baseName ).save( {} ); + + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.right().host, true ); + + resetSlave( pair.left() ); + + checkCount( pair.left(), 1 ); + checkCount( pair.right(), 1 ); + + pair.right().getDB( baseName ).getCollection( baseName ).remove( {} ); + checkCount( pair.left(), 0 ); + + disconnect(); + pair.waitForSteadyState( [ 1, 1 ], null, true ); + + print( "test two" ); + + // fill new master oplog + for( i = 0; i < 1000; ++i ) { + pair.right().getDB( baseName ).getCollection( baseName ).save( {b:big} ); + } + + pair.left().getDB( baseName ).getCollection( baseName ).save( {_id:"abcde"} ); + + connect(); + pair.waitForSteadyState( [ 1, 0 ], pair.right().host, true ); + + sleep( 15000 ); + + resetSlave( pair.left() ); + + checkCount( pair.left(), 1000 ); + checkCount( pair.right(), 1000 ); + assert.eq( 0, pair.left().getDB( baseName ).getCollection( baseName ).find( {_id:"abcde"} ).count() ); + + ports.forEach( function( x ) { stopMongoProgram( x ); } ); + +} + +doTest();
\ No newline at end of file diff --git a/jstests/repl/repl1.js b/jstests/repl/repl1.js new file mode 100644 index 0000000..60f3942 --- /dev/null +++ b/jstests/repl/repl1.js @@ -0,0 +1,55 @@ +// Test basic replication functionality + +var baseName = "jstests_repl1test"; + +soonCount = function( count ) { + assert.soon( function() { +// print( "check count" ); +// print( "count: " + s.getDB( baseName ).z.find().count() ); + return s.getDB( baseName ).a.find().count() == count; + } ); +} + +doTest = function( signal ) { + + rt = new ReplTest( "repl1tests" ); + + m = rt.start( true ); + s = rt.start( false ); + + am = m.getDB( baseName ).a + + for( i = 0; i < 1000; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + soonCount( 1000 ); + as = s.getDB( baseName ).a + assert.eq( 1, as.find( { i: 0 } ).count() ); + assert.eq( 1, as.find( { i: 999 } ).count() ); + + rt.stop( false, signal ); + + for( i = 1000; i < 1010; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + s = rt.start( false, null, true ); + soonCount( 1010 ); + as = s.getDB( baseName ).a + assert.eq( 1, as.find( { i: 1009 } ).count() ); + + rt.stop( true, signal ); + + m = rt.start( true, null, true ); + am = m.getDB( baseName ).a + + for( i = 1010; i < 1020; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + assert.soon( function() { return as.find().count() == 1020; } ); + assert.eq( 1, as.find( { i: 1019 } ).count() ); + + rt.stop(); +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl2.js b/jstests/repl/repl2.js new file mode 100644 index 0000000..c9fe6b9 --- /dev/null +++ b/jstests/repl/repl2.js @@ -0,0 +1,45 @@ +// Test resync command + +soonCount = function( count ) { + assert.soon( function() { +// print( "check count" ); +// print( "count: " + s.getDB( baseName ).z.find().count() ); + return s.getDB("foo").a.find().count() == count; + } ); +} + +doTest = function( signal ) { + + var rt = new ReplTest( "repl2tests" ); + + // implicit small oplog makes slave get out of sync + m = rt.start( true ); + s = rt.start( false ); + + am = m.getDB("foo").a + + am.save( { _id: new ObjectId() } ); + soonCount( 1 ); + assert.eq( 0, s.getDB( "admin" ).runCommand( { "resync" : 1 } ).ok ); + rt.stop( false , signal ); + + big = new Array( 2000 ).toString(); + for( i = 0; i < 1000; ++i ) + am.save( { _id: new ObjectId(), i: i, b: big } ); + + s = rt.start( false , null , true ); + assert.soon( function() { return 1 == s.getDB( "admin" ).runCommand( { "resync" : 1 } ).ok; } ); + + soonCount( 1001 ); + as = s.getDB("foo").a + assert.eq( 1, as.find( { i: 0 } ).count() ); + assert.eq( 1, as.find( { i: 999 } ).count() ); + + assert.eq( 0, s.getDB( "admin" ).runCommand( { "resync" : 1 } ).ok ); + + rt.stop(); + +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl3.js b/jstests/repl/repl3.js new file mode 100644 index 0000000..d3c3848 --- /dev/null +++ b/jstests/repl/repl3.js @@ -0,0 +1,47 @@ +// Test autoresync + +var baseName = "jstests_repl3test"; + +soonCount = function( count ) { + assert.soon( function() { +// print( "check count" ); +// print( "count: " + s.getDB( baseName ).z.find().count() + ", expected: " + count ); + return s.getDB( baseName ).a.find().count() == count; + } ); +} + +doTest = function( signal ) { + + rt = new ReplTest( "repl3tests" ); + + m = rt.start( true ); + s = rt.start( false ); + + am = m.getDB( baseName ).a + + am.save( { _id: new ObjectId() } ); + soonCount( 1 ); + rt.stop( false, signal ); + + big = new Array( 2000 ).toString(); + for( i = 0; i < 1000; ++i ) + am.save( { _id: new ObjectId(), i: i, b: big } ); + + s = rt.start( false, { autoresync: null }, true ); + + // after SyncException, mongod waits 10 secs. + sleep( 15000 ); + + // Need the 2 additional seconds timeout, since commands don't work on an 'allDead' node. + soonCount( 1001 ); + as = s.getDB( baseName ).a + assert.eq( 1, as.find( { i: 0 } ).count() ); + assert.eq( 1, as.find( { i: 999 } ).count() ); + + assert.commandFailed( s.getDB( "admin" ).runCommand( { "resync" : 1 } ) ); + + rt.stop(); +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl4.js b/jstests/repl/repl4.js new file mode 100644 index 0000000..de7ca43 --- /dev/null +++ b/jstests/repl/repl4.js @@ -0,0 +1,30 @@ +// Test replication 'only' mode + +soonCount = function( db, coll, count ) { + assert.soon( function() { + return s.getDB( db )[ coll ].find().count() == count; + } ); +} + +doTest = function() { + + rt = new ReplTest( "repl4tests" ); + + m = rt.start( true ); + s = rt.start( false, { only: "c" } ); + + cm = m.getDB( "c" ).c + bm = m.getDB( "b" ).b + + cm.save( { x:1 } ); + bm.save( { x:2 } ); + + soonCount( "c", "c", 1 ); + assert.eq( 1, s.getDB( "c" ).c.findOne().x ); + sleep( 10000 ); + printjson( s.getDBNames() ); + assert.eq( -1, s.getDBNames().indexOf( "b" ) ); + assert.eq( 0, s.getDB( "b" ).b.find().count() ); +} + +doTest(); diff --git a/jstests/repl/repl5.js b/jstests/repl/repl5.js new file mode 100644 index 0000000..b9bcef9 --- /dev/null +++ b/jstests/repl/repl5.js @@ -0,0 +1,32 @@ +// Test auto reclone after failed initial clone + +soonCountAtLeast = function( db, coll, count ) { + assert.soon( function() { +// print( "count: " + s.getDB( db )[ coll ].find().count() ); + return s.getDB( db )[ coll ].find().count() >= count; + } ); +} + +doTest = function( signal ) { + + rt = new ReplTest( "repl5tests" ); + + m = rt.start( true ); + + ma = m.getDB( "a" ).a; + for( i = 0; i < 10000; ++i ) + ma.save( { i:i } ); + + s = rt.start( false ); + soonCountAtLeast( "a", "a", 1 ); + rt.stop( false, signal ); + + s = rt.start( false, null, true ); + sleep( 1000 ); + soonCountAtLeast( "a", "a", 10000 ); + + rt.stop(); +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl6.js b/jstests/repl/repl6.js new file mode 100644 index 0000000..f4fdc9b --- /dev/null +++ b/jstests/repl/repl6.js @@ -0,0 +1,73 @@ +// Test one master replicating to two slaves + +var baseName = "jstests_repl6test"; + +soonCount = function( m, count ) { + assert.soon( function() { + return m.getDB( baseName ).a.find().count() == count; + }, "expected count: " + count + " from : " + m ); +} + +doTest = function( signal ) { + + ports = allocatePorts( 3 ); + + ms1 = new ReplTest( "repl6tests-1", [ ports[ 0 ], ports[ 1 ] ] ); + ms2 = new ReplTest( "repl6tests-2", [ ports[ 0 ], ports[ 2 ] ] ); + + m = ms1.start( true ); + s1 = ms1.start( false ); + s2 = ms2.start( false ); + + am = m.getDB( baseName ).a + + for( i = 0; i < 1000; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + soonCount( s1, 1000 ); + soonCount( s2, 1000 ); + + check = function( as ) { + assert.eq( 1, as.find( { i: 0 } ).count() ); + assert.eq( 1, as.find( { i: 999 } ).count() ); + } + + as = s1.getDB( baseName ).a + check( as ); + as = s2.getDB( baseName ).a + check( as ); + + ms1.stop( false, signal ); + ms2.stop( false, signal ); + + for( i = 1000; i < 1010; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + s1 = ms1.start( false, null, true ); + soonCount( s1, 1010 ); + as = s1.getDB( baseName ).a + assert.eq( 1, as.find( { i: 1009 } ).count() ); + + ms1.stop( true, signal ); + + m = ms1.start( true, null, true ); + am = m.getDB( baseName ).a + + for( i = 1010; i < 1020; ++i ) + am.save( { _id: new ObjectId(), i: i } ); + + soonCount( s1, 1020 ); + assert.eq( 1, as.find( { i: 1019 } ).count() ); + + s2 = ms2.start( false, null, true ); + soonCount( s2, 1020 ); + as = s2.getDB( baseName ).a + assert.eq( 1, as.find( { i: 1009 } ).count() ); + assert.eq( 1, as.find( { i: 1019 } ).count() ); + + ms1.stop(); + ms2.stop( false ); +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl7.js b/jstests/repl/repl7.js new file mode 100644 index 0000000..e3fdee9 --- /dev/null +++ b/jstests/repl/repl7.js @@ -0,0 +1,45 @@ +// Test persistence of list of dbs to add. + +doTest = function( signal ) { + + rt = new ReplTest( "repl7tests" ); + + m = rt.start( true ); + + for( n = "a"; n != "aaaaa"; n += "a" ) { + m.getDB( n ).a.save( {x:1} ); + } + + s = rt.start( false ); + + assert.soon( function() { + return -1 != s.getDBNames().indexOf( "aa" ); + }, "aa timeout", 60000, 1000 ); + + rt.stop( false, signal ); + + s = rt.start( false, null, signal ); + + assert.soon( function() { + for( n = "a"; n != "aaaaa"; n += "a" ) { + if ( -1 == s.getDBNames().indexOf( n ) ) + return false; + } + return true; + }, "a-aaaa timeout", 60000, 1000 ); + + assert.soon( function() { + for( n = "a"; n != "aaaaa"; n += "a" ) { + if ( 1 != m.getDB( n ).a.find().count() ) { + return false; + } + } + return true; }, "a-aaaa count timeout" ); + + sleep( 300 ); + + rt.stop(); +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/repl8.js b/jstests/repl/repl8.js new file mode 100644 index 0000000..64e65cc --- /dev/null +++ b/jstests/repl/repl8.js @@ -0,0 +1,30 @@ +// Test cloning of capped collections + +baseName = "jstests_repl_repl8"; + +rt = new ReplTest( "repl8tests" ); + +m = rt.start( true ); + +m.getDB( baseName ).createCollection( "first", {capped:true,size:1000} ); +assert( m.getDB( baseName ).getCollection( "first" ).isCapped() ); + +s = rt.start( false ); + +assert.soon( function() { return s.getDB( baseName ).getCollection( "first" ).isCapped(); } ); + +m.getDB( baseName ).createCollection( "second", {capped:true,size:1000} ); +assert.soon( function() { return s.getDB( baseName ).getCollection( "second" ).isCapped(); } ); + +m.getDB( baseName ).getCollection( "third" ).save( { a: 1 } ); +assert.soon( function() { return s.getDB( baseName ).getCollection( "third" ).exists(); } ); +assert.commandWorked( m.getDB( "admin" ).runCommand( {renameCollection:"jstests_repl_repl8.third", to:"jstests_repl_repl8.third_rename"} ) ); +assert( m.getDB( baseName ).getCollection( "third_rename" ).exists() ); +assert( !m.getDB( baseName ).getCollection( "third" ).exists() ); +assert.soon( function() { return s.getDB( baseName ).getCollection( "third_rename" ).exists(); } ); +assert.soon( function() { return !s.getDB( baseName ).getCollection( "third" ).exists(); } ); + +m.getDB( baseName ).getCollection( "fourth" ).save( {a:1} ); +assert.commandWorked( m.getDB( baseName ).getCollection( "fourth" ).convertToCapped( 1000 ) ); +assert( m.getDB( baseName ).getCollection( "fourth" ).isCapped() ); +assert.soon( function() { return s.getDB( baseName ).getCollection( "fourth" ).isCapped(); } ); diff --git a/jstests/repl/repl9.js b/jstests/repl/repl9.js new file mode 100644 index 0000000..be06e08 --- /dev/null +++ b/jstests/repl/repl9.js @@ -0,0 +1,48 @@ +// Test replication of collection renaming + +baseName = "jstests_repl_repl9"; + +rt = new ReplTest( "repl9tests" ); + +m = rt.start( true ); +s = rt.start( false ); + +admin = m.getDB( "admin" ); + +debug = function( foo ) {} // print( foo ); } + +// rename within db + +m.getDB( baseName ).one.save( { a: 1 } ); +assert.soon( function() { v = s.getDB( baseName ).one.findOne(); return v && 1 == v.a } ); + +assert.commandWorked( admin.runCommand( {renameCollection:"jstests_repl_repl9.one", to:"jstests_repl_repl9.two"} ) ); +assert.soon( function() { + if ( -1 == s.getDB( baseName ).getCollectionNames().indexOf( "two" ) ) { + debug( "no two coll" ); + debug( tojson( s.getDB( baseName ).getCollectionNames() ) ); + return false; + } + if ( !s.getDB( baseName ).two.findOne() ) { + debug( "no two object" ); + return false; + } + return 1 == s.getDB( baseName ).two.findOne().a; }); +assert.eq( -1, s.getDB( baseName ).getCollectionNames().indexOf( "one" ) ); + +// rename to new db + +first = baseName + "_first"; +second = baseName + "_second"; + +m.getDB( first ).one.save( { a: 1 } ); +assert.soon( function() { return s.getDB( first ).one.findOne() && 1 == s.getDB( first ).one.findOne().a; } ); + +assert.commandWorked( admin.runCommand( {renameCollection:"jstests_repl_repl9_first.one", to:"jstests_repl_repl9_second.two"} ) ); +assert.soon( function() { + return -1 != s.getDBNames().indexOf( second ) && + -1 != s.getDB( second ).getCollectionNames().indexOf( "two" ) && + s.getDB( second ).two.findOne() && + 1 == s.getDB( second ).two.findOne().a; } ); +assert.eq( -1, s.getDB( first ).getCollectionNames().indexOf( "one" ) ); + diff --git a/jstests/repl/replacePeer1.js b/jstests/repl/replacePeer1.js new file mode 100644 index 0000000..45ee544 --- /dev/null +++ b/jstests/repl/replacePeer1.js @@ -0,0 +1,71 @@ +// test replace peer on master + +var baseName = "jstests_replacepeer1test"; + +ismaster = function( n ) { + im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); +// print( "ismaster: " + tojson( im ) ); + assert( im ); + return im.ismaster; +} + +var writeOneIdx = 0; + +writeOne = function( n ) { + n.getDB( baseName ).z.save( { _id: new ObjectId(), i: ++writeOneIdx } ); +} + +getCount = function( n ) { + return n.getDB( baseName ).z.find( { i: writeOneIdx } ).toArray().length; +} + +checkWrite = function( m, s ) { + writeOne( m ); + assert.eq( 1, getCount( m ) ); + s.setSlaveOk(); + assert.soon( function() { + return 1 == getCount( s ); + } ); +} + +doTest = function( signal ) { + + ports = allocatePorts( 4 ); + + a = new MongodRunner( ports[ 0 ], "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( ports[ 1 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 3 ], "127.0.0.1:" + ports[ 0 ] ); + r = new MongodRunner( ports[ 3 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 1 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( l, r, a ); + rp.start(); + rp.waitForSteadyState( [ 1, 0 ], rp.right().host ); + + checkWrite( rp.master(), rp.slave() ); + + rp.killNode( rp.slave(), signal ); + + writeOne( rp.master() ); + + assert.commandWorked( rp.master().getDB( "admin" ).runCommand( {replacepeer:1} ) ); + + rp.killNode( rp.master(), signal ); + rp.killNode( rp.arbiter(), signal ); + + o = new MongodRunner( ports[ 2 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 3 ], "127.0.0.1:" + ports[ 0 ] ); + r = new MongodRunner( ports[ 3 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 2 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( o, r, a ); + resetDbpath( "/data/db/" + baseName + "-left" ); + rp.start( true ); + rp.waitForSteadyState( [ 1, 0 ], rp.right().host ); + + checkWrite( rp.master(), rp.slave() ); + rp.slave().setSlaveOk(); + assert.eq( 3, rp.slave().getDB( baseName ).z.find().toArray().length ); + + ports.forEach( function( x ) { stopMongod( x ); } ); + +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/repl/replacePeer2.js b/jstests/repl/replacePeer2.js new file mode 100644 index 0000000..09c8177 --- /dev/null +++ b/jstests/repl/replacePeer2.js @@ -0,0 +1,72 @@ +// test replace peer on slave + +var baseName = "jstests_replacepeer2test"; + +ismaster = function( n ) { + im = n.getDB( "admin" ).runCommand( { "ismaster" : 1 } ); +// print( "ismaster: " + tojson( im ) ); + assert( im ); + return im.ismaster; +} + +var writeOneIdx = 0; + +writeOne = function( n ) { + n.getDB( baseName ).z.save( { _id: new ObjectId(), i: ++writeOneIdx } ); +} + +getCount = function( n ) { + return n.getDB( baseName ).z.find( { i: writeOneIdx } ).toArray().length; +} + +checkWrite = function( m, s ) { + writeOne( m ); + assert.eq( 1, getCount( m ) ); + s.setSlaveOk(); + assert.soon( function() { + return 1 == getCount( s ); + } ); +} + +doTest = function( signal ) { + + ports = allocatePorts( 4 ); + + a = new MongodRunner( ports[ 0 ], "/data/db/" + baseName + "-arbiter" ); + l = new MongodRunner( ports[ 1 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 3 ], "127.0.0.1:" + ports[ 0 ] ); + r = new MongodRunner( ports[ 3 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 1 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( l, r, a ); + rp.start(); + rp.waitForSteadyState( [ 1, 0 ], rp.right().host ); + + checkWrite( rp.master(), rp.slave() ); + + // allow slave to finish initial sync + assert.soon( function() { return 1 == rp.slave().getDB( "admin" ).runCommand( {replacepeer:1} ).ok; } ); + + // Should not be saved to slave. + writeOne( rp.master() ); + // Make sure there would be enough time to save to l if we hadn't called replacepeer. + sleep( 10000 ); + + ports.forEach( function( x ) { stopMongod( x, signal ); } ); + + l = new MongodRunner( ports[ 1 ], "/data/db/" + baseName + "-left", "127.0.0.1:" + ports[ 2 ], "127.0.0.1:" + ports[ 0 ] ); + o = new MongodRunner( ports[ 2 ], "/data/db/" + baseName + "-right", "127.0.0.1:" + ports[ 1 ], "127.0.0.1:" + ports[ 0 ] ); + + rp = new ReplPair( l, o, a ); + resetDbpath( "/data/db/" + baseName + "-right" ); + rp.start( true ); + rp.waitForSteadyState( [ 1, 0 ], rp.left().host ); + + checkWrite( rp.master(), rp.slave() ); + rp.slave().setSlaveOk(); + assert.eq( 2, rp.slave().getDB( baseName ).z.find().toArray().length ); + + ports.forEach( function( x ) { stopMongod( x ); } ); + +} + +doTest( 15 ); // SIGTERM +doTest( 9 ); // SIGKILL diff --git a/jstests/set1.js b/jstests/set1.js new file mode 100644 index 0000000..d741387 --- /dev/null +++ b/jstests/set1.js @@ -0,0 +1,9 @@ + +t = db.set1; +t.drop(); + +t.insert( { _id : 1, emb : {} }); +t.update( { _id : 1 }, { $set : { emb : { 'a.dot' : 'data'} }}); +assert.eq( { _id : 1 , emb : {} } , t.findOne() , "A" ); + + diff --git a/jstests/set2.js b/jstests/set2.js new file mode 100644 index 0000000..221ee40 --- /dev/null +++ b/jstests/set2.js @@ -0,0 +1,18 @@ + +t = db.set2; +t.drop(); + +t.save( { _id : 1 , x : true , y : { x : true } } ); +assert.eq( true , t.findOne().x ); + +t.update( { _id : 1 } , { $set : { x : 17 } } ); +assert.eq( 17 , t.findOne().x ); + +assert.eq( true , t.findOne().y.x ); +t.update( { _id : 1 } , { $set : { "y.x" : 17 } } ); +assert.eq( 17 , t.findOne().y.x ); + +t.update( { _id : 1 } , { $set : { a : 2 , b : 3 } } ); +assert.eq( 2 , t.findOne().a ); +assert.eq( 3 , t.findOne().b ); + diff --git a/jstests/set3.js b/jstests/set3.js new file mode 100644 index 0000000..611abc4 --- /dev/null +++ b/jstests/set3.js @@ -0,0 +1,11 @@ + +t = db.set3; +t.drop(); + +t.insert( { "test1" : { "test2" : { "abcdefghijklmnopqrstu" : {"id":1} } } } ); +t.update( {}, {"$set":{"test1.test2.abcdefghijklmnopqrstuvwxyz":{"id":2}}}) + +x = t.findOne(); +assert.eq( 1 , x.test1.test2.abcdefghijklmnopqrstu.id , "A" ); +assert.eq( 2 , x.test1.test2.abcdefghijklmnopqrstuvwxyz.id , "B" ); + diff --git a/jstests/set4.js b/jstests/set4.js new file mode 100644 index 0000000..b37366c --- /dev/null +++ b/jstests/set4.js @@ -0,0 +1,15 @@ + +t = db.set4; +t.drop(); + +orig = { _id:1 , a : [ { x : 1 } ]} +t.insert( orig ); + +t.update( {}, { $set : { 'a.0.x' : 2, 'foo.bar' : 3 } } ); +orig.a[0].x = 2; orig.foo = { bar : 3 } +assert.eq( orig , t.findOne() , "A" ); + +t.update( {}, { $set : { 'a.0.x' : 4, 'foo.bar' : 5 } } ); +orig.a[0].x = 4; orig.foo.bar = 5; +assert.eq( orig , t.findOne() , "B" ); + diff --git a/jstests/sharding/auto1.js b/jstests/sharding/auto1.js new file mode 100644 index 0000000..92a4ce8 --- /dev/null +++ b/jstests/sharding/auto1.js @@ -0,0 +1,51 @@ +// auto1.js + +s = new ShardingTest( "auto1" , 2 , 1 , 1 ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +bigString = ""; +while ( bigString.length < 1024 * 50 ) + bigString += "asocsancdnsjfnsdnfsjdhfasdfasdfasdfnsadofnsadlkfnsaldknfsad"; + +db = s.getDB( "test" ) +coll = db.foo; + +var i=0; + +for ( ; i<500; i++ ){ + coll.save( { num : i , s : bigString } ); +} + +s.adminCommand( "connpoolsync" ); + +primary = s.getServer( "test" ).getDB( "test" ); + +assert.eq( 1 , s.config.chunks.count() ); +assert.eq( 500 , primary.foo.count() ); + +print( "datasize: " + tojson( s.getServer( "test" ).getDB( "admin" ).runCommand( { datasize : "test.foo" } ) ) ); + +for ( ; i<800; i++ ){ + coll.save( { num : i , s : bigString } ); +} + +assert.eq( 1 , s.config.chunks.count() ); + +for ( ; i<1500; i++ ){ + coll.save( { num : i , s : bigString } ); +} + +assert.eq( 3 , s.config.chunks.count() , "shard didn't split A " ); +s.printChunks(); + +for ( ; i<3000; i++ ){ + coll.save( { num : i , s : bigString } ); +} + +assert.eq( 4 , s.config.chunks.count() , "shard didn't split B " ); +s.printChunks(); + + +s.stop(); diff --git a/jstests/sharding/auto2.js b/jstests/sharding/auto2.js new file mode 100644 index 0000000..c6ec374 --- /dev/null +++ b/jstests/sharding/auto2.js @@ -0,0 +1,44 @@ +// auto2.js + +s = new ShardingTest( "auto2" , 2 , 1 , 1 ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +bigString = ""; +while ( bigString.length < 1024 * 50 ) + bigString += "asocsancdnsjfnsdnfsjdhfasdfasdfasdfnsadofnsadlkfnsaldknfsad"; + +db = s.getDB( "test" ) +coll = db.foo; + +var i=0; + +for ( j=0; j<30; j++ ){ + print( "j:" + j + " : " + + Date.timeFunc( + function(){ + for ( var k=0; k<100; k++ ){ + coll.save( { num : i , s : bigString } ); + i++; + } + } + ) ); + +} +s.adminCommand( "connpoolsync" ); + +print( "done inserting data" ); + +print( "datasize: " + tojson( s.getServer( "test" ).getDB( "admin" ).runCommand( { datasize : "test.foo" } ) ) ); +s.printChunks(); + +counta = s._connections[0].getDB( "test" ).foo.count(); +countb = s._connections[1].getDB( "test" ).foo.count(); + +assert.eq( j * 100 , counta + countb , "from each a:" + counta + " b:" + countb + " i:" + i ); +assert.eq( j * 100 , coll.find().limit(100000000).itcount() , "itcount A" ); + +assert( Array.unique( s.config.chunks.find().toArray().map( function(z){ return z.shard; } ) ).length == 2 , "should be using both servers" ); + +s.stop(); diff --git a/jstests/sharding/count1.js b/jstests/sharding/count1.js new file mode 100644 index 0000000..a697162 --- /dev/null +++ b/jstests/sharding/count1.js @@ -0,0 +1,55 @@ +// count1.js + +s = new ShardingTest( "count1" , 2 ); + +db = s.getDB( "test" ); + +db.bar.save( { n : 1 } ) +db.bar.save( { n : 2 } ) +db.bar.save( { n : 3 } ) + +assert.eq( 3 , db.bar.find().count() , "bar 1" ); +assert.eq( 1 , db.bar.find( { n : 1 } ).count() , "bar 2" ); + +s.adminCommand( { enablesharding : "test" } ) +s.adminCommand( { shardcollection : "test.foo" , key : { name : 1 } } ); + +primary = s.getServer( "test" ).getDB( "test" ); +seconday = s.getOther( primary ).getDB( "test" ); + +assert.eq( 1 , s.config.chunks.count() , "sanity check A" ); + +db.foo.save( { name : "eliot" } ) +db.foo.save( { name : "sara" } ) +db.foo.save( { name : "bob" } ) +db.foo.save( { name : "joe" } ) +db.foo.save( { name : "mark" } ) +db.foo.save( { name : "allan" } ) + +assert.eq( 6 , db.foo.find().count() , "basic count" ); + +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); + +assert.eq( 6 , db.foo.find().count() , "basic count after split " ); +assert.eq( 6 , db.foo.find().sort( { name : 1 } ).count() , "basic count after split sorted " ); + +s.adminCommand( { movechunk : "test.foo" , find : { name : "joe" } , to : seconday.getMongo().name } ); + +assert.eq( 3 , primary.foo.find().toArray().length , "primary count" ); +assert.eq( 3 , seconday.foo.find().toArray().length , "secondary count" ); +assert.eq( 3 , primary.foo.find().sort( { name : 1 } ).toArray().length , "primary count sorted" ); +assert.eq( 3 , seconday.foo.find().sort( { name : 1 } ).toArray().length , "secondary count sorted" ); + +assert.eq( 6 , db.foo.find().toArray().length , "total count after move" ); +assert.eq( 6 , db.foo.find().sort( { name : 1 } ).toArray().length , "total count() sorted" ); + +assert.eq( 6 , db.foo.find().sort( { name : 1 } ).count() , "total count with count() after move" ); + +assert.eq( "allan,bob,eliot,joe,mark,sara" , db.foo.find().sort( { name : 1 } ).toArray().map( function(z){ return z.name; } ) , "sort 1" ); +assert.eq( "sara,mark,joe,eliot,bob,allan" , db.foo.find().sort( { name : -1 } ).toArray().map( function(z){ return z.name; } ) , "sort 2" ); + +s.stop(); + + diff --git a/jstests/sharding/diffservers1.js b/jstests/sharding/diffservers1.js new file mode 100644 index 0000000..6497bc0 --- /dev/null +++ b/jstests/sharding/diffservers1.js @@ -0,0 +1,20 @@ + + +s = new ShardingTest( "diffservers1" , 2 ); + +assert.eq( 2 , s.config.shards.count() , "server count wrong" ); +assert.eq( 2 , s._connections[0].getDB( "config" ).shards.count() , "where are servers!" ); +assert.eq( 0 , s._connections[1].getDB( "config" ).shards.count() , "shouldn't be here" ); + +test1 = s.getDB( "test1" ).foo; +test1.save( { a : 1 } ); +test1.save( { a : 2 } ); +test1.save( { a : 3 } ); +assert( 3 , test1.count() ); + +assert( ! s.admin.runCommand( { addshard: "sdd$%" } ).ok , "bad hostname" ); +assert( ! s.admin.runCommand( { addshard: "127.0.0.1:43415" } ).ok , "host not up" ); +assert( ! s.admin.runCommand( { addshard: "127.0.0.1:43415" , allowLocal : true } ).ok , "host not up" ); + +s.stop(); + diff --git a/jstests/sharding/error1.js b/jstests/sharding/error1.js new file mode 100644 index 0000000..b4db9c3 --- /dev/null +++ b/jstests/sharding/error1.js @@ -0,0 +1,47 @@ + +s = new ShardingTest( "error1" , 2 , 1 , 1 ); +s.adminCommand( { enablesharding : "test" } ); + +a = s._connections[0].getDB( "test" ); +b = s._connections[1].getDB( "test" ); + +// ---- simple getLastError ---- + +db = s.getDB( "test" ); +db.foo.insert( { _id : 1 } ); +assert.isnull( db.getLastError() , "gle 1" ); +db.foo.insert( { _id : 1 } ); +assert( db.getLastError() , "gle21" ); +assert( db.getLastError() , "gle22" ); + +// --- sharded getlasterror + +s.adminCommand( { shardcollection : "test.foo2" , key : { num : 1 } } ); + +db.foo2.insert( { _id : 1 , num : 5 } ); +db.foo2.insert( { _id : 2 , num : 10 } ); +db.foo2.insert( { _id : 3 , num : 15 } ); +db.foo2.insert( { _id : 4 , num : 20 } ); + +s.adminCommand( { split : "test.foo2" , middle : { num : 10 } } ); +s.adminCommand( { movechunk : "test.foo2" , find : { num : 20 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +assert( a.foo2.count() > 0 && a.foo2.count() < 4 , "se1" ); +assert( b.foo2.count() > 0 && b.foo2.count() < 4 , "se2" ); +assert.eq( 4 , db.foo2.count() , "se3" ); + +db.foo2.insert( { _id : 5 , num : 25 } ); +assert( ! db.getLastError() , "se3.5" ); +s.sync(); +assert.eq( 5 , db.foo2.count() , "se4" ); + + + +db.foo2.insert( { _id : 5 , num : 30 } ); +assert( db.getLastError() , "se5" ); +assert( db.getLastError() , "se6" ); + +assert.eq( 5 , db.foo2.count() , "se5" ); + +// ---- +s.stop(); diff --git a/jstests/sharding/features1.js b/jstests/sharding/features1.js new file mode 100644 index 0000000..d2f692a --- /dev/null +++ b/jstests/sharding/features1.js @@ -0,0 +1,139 @@ +// features1.js + +s = new ShardingTest( "features1" , 2 , 1 , 1 ); + +s.adminCommand( { enablesharding : "test" } ); + +// ---- can't shard system namespaces ---- + +assert( ! s.admin.runCommand( { shardcollection : "test.system.blah" , key : { num : 1 } } ).ok , "shard system namespace" ); + +// ---- setup test.foo ----- + +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +db = s.getDB( "test" ); + +a = s._connections[0].getDB( "test" ); +b = s._connections[1].getDB( "test" ); + +db.foo.ensureIndex( { y : 1 } ); + +s.adminCommand( { split : "test.foo" , middle : { num : 10 } } ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 20 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +db.foo.save( { num : 5 } ); +db.foo.save( { num : 15 } ); + +s.sync(); + +// ---- make sure shard key index is everywhere ---- + +assert.eq( 3 , a.foo.getIndexKeys().length , "a index 1" ); +assert.eq( 3 , b.foo.getIndexKeys().length , "b index 1" ); + +// ---- make sure if you add an index it goes everywhere ------ + +db.foo.ensureIndex( { x : 1 } ); + +s.sync(); + +assert.eq( 4 , a.foo.getIndexKeys().length , "a index 2" ); +assert.eq( 4 , b.foo.getIndexKeys().length , "b index 2" ); + +// ---- no unique indexes ------ + +db.foo.ensureIndex( { z : 1 } , true ); + +s.sync(); + +assert.eq( 4 , a.foo.getIndexKeys().length , "a index 3" ); +assert.eq( 4 , b.foo.getIndexKeys().length , "b index 3" ); + +// ---- can't shard thing with unique indexes + +db.foo2.ensureIndex( { a : 1 } ); +s.sync(); +assert( s.admin.runCommand( { shardcollection : "test.foo2" , key : { num : 1 } } ).ok , "shard with index" ); + +db.foo3.ensureIndex( { a : 1 } , true ); +s.sync(); +printjson( db.system.indexes.find( { ns : "test.foo3" } ).toArray() ); +assert( ! s.admin.runCommand( { shardcollection : "test.foo3" , key : { num : 1 } } ).ok , "shard with unique index" ); + +// ----- eval ----- + +db.foo2.save( { num : 5 , a : 7 } ); +db.foo3.save( { num : 5 , a : 8 } ); + +assert.eq( 1 , db.foo3.count() , "eval pre1" ); +assert.eq( 1 , db.foo2.count() , "eval pre2" ); + +assert.eq( 8 , db.eval( function(){ return db.foo3.findOne().a; } ), "eval 1 " ); +assert.throws( function(){ db.eval( function(){ return db.foo2.findOne().a; } ) } , "eval 2" ) + +assert.eq( 1 , db.eval( function(){ return db.foo3.count(); } ), "eval 3 " ); +assert.throws( function(){ db.eval( function(){ return db.foo2.count(); } ) } , "eval 4" ) + + +// ---- unique shard key ---- + +assert( s.admin.runCommand( { shardcollection : "test.foo4" , key : { num : 1 } , unique : true } ).ok , "shard with index and unique" ); +s.adminCommand( { split : "test.foo4" , middle : { num : 10 } } ); +s.adminCommand( { movechunk : "test.foo4" , find : { num : 20 } , to : s.getOther( s.getServer( "test" ) ).name } ); +db.foo4.save( { num : 5 } ); +db.foo4.save( { num : 15 } ); +s.sync(); +assert.eq( 1 , a.foo4.count() , "ua1" ); +assert.eq( 1 , b.foo4.count() , "ub1" ); + +assert.eq( 2 , a.foo4.getIndexes().length , "ua2" ); +assert.eq( 2 , b.foo4.getIndexes().length , "ub2" ); + +assert( a.foo4.getIndexes()[1].unique , "ua3" ); +assert( b.foo4.getIndexes()[1].unique , "ub3" ); + +// --- don't let you convertToCapped ---- +assert( ! db.foo4.isCapped() , "ca1" ); +assert( ! a.foo4.isCapped() , "ca2" ); +assert( ! b.foo4.isCapped() , "ca3" ); +assert( ! db.foo4.convertToCapped( 30000 ).ok , "ca30" ); +assert( ! db.foo4.isCapped() , "ca4" ); +assert( ! a.foo4.isCapped() , "ca5" ); +assert( ! b.foo4.isCapped() , "ca6" ); + +// make sure i didn't break anything +db.foo4a.save( { a : 1 } ); +assert( ! db.foo4a.isCapped() , "ca7" ); +db.foo4a.convertToCapped( 30000 ); +assert( db.foo4a.isCapped() , "ca8" ); + +// --- don't let you shard a capped collection + +db.createCollection("foo5", {capped:true, size:30000}); +assert( db.foo5.isCapped() , "cb1" ); +assert( ! s.admin.runCommand( { shardcollection : "test.foo5" , key : { num : 1 } } ).ok , "shard capped" ); + + +// ----- group ---- + +db.foo6.save( { a : 1 } ); +db.foo6.save( { a : 3 } ); +db.foo6.save( { a : 3 } ); +s.sync(); + +assert.eq( 2 , db.foo6.group( { key : { a : 1 } , initial : { count : 0 } , + reduce : function(z,prev){ prev.count++; } } ).length ); + +assert.eq( 3 , db.foo6.find().count() ); +assert( s.admin.runCommand( { shardcollection : "test.foo6" , key : { a : 2 } } ).ok ); +assert.eq( 3 , db.foo6.find().count() ); + +s.adminCommand( { split : "test.foo6" , middle : { a : 2 } } ); +s.adminCommand( { movechunk : "test.foo6" , find : { a : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +assert.throws( function(){ db.foo6.group( { key : { a : 1 } , initial : { count : 0 } , reduce : function(z,prev){ prev.count++; } } ); } );; + + +s.stop() + diff --git a/jstests/sharding/features2.js b/jstests/sharding/features2.js new file mode 100644 index 0000000..47fedc8 --- /dev/null +++ b/jstests/sharding/features2.js @@ -0,0 +1,114 @@ +// features2.js + +s = new ShardingTest( "features2" , 2 , 1 , 1 ); +s.adminCommand( { enablesharding : "test" } ); + +a = s._connections[0].getDB( "test" ); +b = s._connections[1].getDB( "test" ); + +db = s.getDB( "test" ); + +// ---- distinct ---- + +db.foo.save( { x : 1 } ); +db.foo.save( { x : 2 } ); +db.foo.save( { x : 3 } ); + +assert.eq( "1,2,3" , db.foo.distinct( "x" ) , "distinct 1" ); +assert( a.foo.distinct("x").length == 3 || b.foo.distinct("x").length == 3 , "distinct 2" ); +assert( a.foo.distinct("x").length == 0 || b.foo.distinct("x").length == 0 , "distinct 3" ); + +assert.eq( 1 , s.onNumShards( "foo" ) , "A1" ); + +s.shardGo( "foo" , { x : 1 } , { x : 2 } , { x : 3 } ); + +assert.eq( 2 , s.onNumShards( "foo" ) , "A2" ); + +assert.eq( "1,2,3" , db.foo.distinct( "x" ) , "distinct 4" ); + +// ----- delete --- + +assert.eq( 3 , db.foo.count() , "D1" ); + +db.foo.remove( { x : 3 } ); +assert.eq( 2 , db.foo.count() , "D2" ); + +db.foo.save( { x : 3 } ); +assert.eq( 3 , db.foo.count() , "D3" ); + +db.foo.remove( { x : { $gt : 2 } } ); +assert.eq( 2 , db.foo.count() , "D4" ); + +db.foo.remove( { x : { $gt : -1 } } ); +assert.eq( 0 , db.foo.count() , "D5" ); + +db.foo.save( { x : 1 } ); +db.foo.save( { x : 2 } ); +db.foo.save( { x : 3 } ); +assert.eq( 3 , db.foo.count() , "D6" ); +db.foo.remove( {} ); +assert.eq( 0 , db.foo.count() , "D7" ); + +// --- _id key --- + +db.foo2.insert( { _id : new ObjectId() } ); +db.foo2.insert( { _id : new ObjectId() } ); +db.foo2.insert( { _id : new ObjectId() } ); + +assert.eq( 1 , s.onNumShards( "foo2" ) , "F1" ); + +s.adminCommand( { shardcollection : "test.foo2" , key : { _id : 1 } } ); + +assert.eq( 3 , db.foo2.count() , "F2" ) +db.foo2.insert( {} ); +assert.eq( 4 , db.foo2.count() , "F3" ) + + +// --- map/reduce + +db.mr.save( { x : 1 , tags : [ "a" , "b" ] } ); +db.mr.save( { x : 2 , tags : [ "b" , "c" ] } ); +db.mr.save( { x : 3 , tags : [ "c" , "a" ] } ); +db.mr.save( { x : 4 , tags : [ "b" , "c" ] } ); + +m = function(){ + this.tags.forEach( + function(z){ + emit( z , { count : 1 } ); + } + ); +}; + +r = function( key , values ){ + var total = 0; + for ( var i=0; i<values.length; i++ ){ + total += values[i].count; + } + return { count : total }; +}; + +doMR = function( n ){ + var res = db.mr.mapReduce( m , r ); + printjson( res ); + var x = db[res.result]; + assert.eq( 3 , x.find().count() , "MR T1 " + n ); + + var z = {}; + x.find().forEach( function(a){ z[a._id] = a.value.count; } ); + assert.eq( 3 , Object.keySet( z ).length , "MR T2 " + n ); + assert.eq( 2 , z.a , "MR T2 " + n ); + assert.eq( 3 , z.b , "MR T2 " + n ); + assert.eq( 3 , z.c , "MR T2 " + n ); + + x.drop(); +} + +doMR( "before" ); + +assert.eq( 1 , s.onNumShards( "mr" ) , "E1" ); +s.shardGo( "mr" , { x : 1 } , { x : 2 } , { x : 3 } ); +assert.eq( 2 , s.onNumShards( "mr" ) , "E1" ); + +doMR( "after" ); + +s.stop(); diff --git a/jstests/sharding/key_many.js b/jstests/sharding/key_many.js new file mode 100644 index 0000000..43e7cc5 --- /dev/null +++ b/jstests/sharding/key_many.js @@ -0,0 +1,121 @@ +// key_many.js + +// values have to be sorted +types = + [ { name : "string" , values : [ "allan" , "bob" , "eliot" , "joe" , "mark" , "sara" ] , keyfield: "k" } , + { name : "double" , values : [ 1.2 , 3.5 , 4.5 , 4.6 , 6.7 , 9.9 ] , keyfield : "a" } , + { name : "string_id" , values : [ "allan" , "bob" , "eliot" , "joe" , "mark" , "sara" ] , keyfield : "_id" }, + { name : "embedded" , values : [ "allan" , "bob" , "eliot" , "joe" , "mark" , "sara" ] , keyfield : "a.b" } , + { name : "embedded 2" , values : [ "allan" , "bob" , "eliot" , "joe" , "mark" , "sara" ] , keyfield : "a.b.c" } , + { name : "object" , values : [ {a:1, b:1.2}, {a:1, b:3.5}, {a:1, b:4.5}, {a:2, b:1.2}, {a:2, b:3.5}, {a:2, b:4.5} ] , keyfield : "o" } , + ] + +s = new ShardingTest( "key_many" , 2 ); + +s.adminCommand( { enablesharding : "test" } ) +db = s.getDB( "test" ); +primary = s.getServer( "test" ).getDB( "test" ); +seconday = s.getOther( primary ).getDB( "test" ); + +function makeObjectDotted( v ){ + var o = {}; + o[curT.keyfield] = v; + return o; +} + +function makeObject( v ){ + var o = {}; + var p = o; + + var keys = curT.keyfield.split('.'); + for(var i=0; i<keys.length-1; i++){ + p[keys[i]] = {}; + p = p[keys[i]]; + } + + p[keys[i]] = v; + + return o; +} + +function getKey( o ){ + var keys = curT.keyfield.split('.'); + for(var i=0; i<keys.length; i++){ + o = o[keys[i]]; + } + return o; +} + + + +for ( var i=0; i<types.length; i++ ){ + curT = types[i]; //global + + print("\n\n#### Now Testing " + curT.name + " ####\n\n"); + + var shortName = "foo_" + curT.name; + var longName = "test." + shortName; + + var c = db[shortName]; + s.adminCommand( { shardcollection : longName , key : makeObjectDotted( 1 ) } ); + + assert.eq( 1 , s.config.chunks.find( { ns : longName } ).count() , curT.name + " sanity check A" ); + + var unsorted = Array.shuffle( Object.extend( [] , curT.values ) ); + c.insert( makeObject( unsorted[0] ) ); + for ( var x=1; x<unsorted.length; x++ ) + c.save( makeObject( unsorted[x] ) ); + + assert.eq( 6 , c.find().count() , curT.name + " basic count" ); + + s.adminCommand( { split : longName , find : makeObjectDotted( curT.values[3] ) } ); + s.adminCommand( { split : longName , find : makeObjectDotted( curT.values[3] ) } ); + s.adminCommand( { split : longName , find : makeObjectDotted( curT.values[3] ) } ); + + s.adminCommand( { movechunk : longName , find : makeObjectDotted( curT.values[3] ) , to : seconday.getMongo().name } ); + + s.printChunks(); + + assert.eq( 3 , primary[shortName].find().toArray().length , curT.name + " primary count" ); + assert.eq( 3 , seconday[shortName].find().toArray().length , curT.name + " secondary count" ); + + assert.eq( 6 , c.find().toArray().length , curT.name + " total count" ); + assert.eq( 6 , c.find().sort( makeObjectDotted( 1 ) ).toArray().length , curT.name + " total count sorted" ); + + assert.eq( 6 , c.find().sort( makeObjectDotted( 1 ) ).count() , curT.name + " total count with count()" ); + + assert.eq( curT.values , c.find().sort( makeObjectDotted( 1 ) ).toArray().map( getKey ) , curT.name + " sort 1" ); + assert.eq( curT.values.reverse() , c.find().sort( makeObjectDotted( -1 ) ).toArray().map( getKey ) , curT.name + " sort 2" ); + + + assert.eq( 0 , c.find( { xx : 17 } ).sort( { zz : 1 } ).count() , curT.name + " xx 0a " ); + assert.eq( 0 , c.find( { xx : 17 } ).sort( makeObjectDotted( 1 ) ).count() , curT.name + " xx 0b " ); + assert.eq( 0 , c.find( { xx : 17 } ).count() , curT.name + " xx 0c " ); + assert.eq( 0 , c.find( { xx : { $exists : true } } ).count() , curT.name + " xx 1 " ); + + c.update( makeObjectDotted( curT.values[3] ) , { $set : { xx : 17 } } ); + assert.eq( 1 , c.find( { xx : { $exists : true } } ).count() , curT.name + " xx 2 " ); + assert.eq( curT.values[3] , getKey( c.findOne( { xx : 17 } ) ) , curT.name + " xx 3 " ); + + c.update( makeObjectDotted( curT.values[3] ) , { $set : { xx : 17 } } , {upsert: true}); + assert.eq( null , db.getLastError() , curT.name + " upserts should work if they include the shard key in the query" ); + + c.ensureIndex( { _id : 1 } , { unique : true } ); + assert.eq( null , db.getLastError() , curT.name + " creating _id index should be ok" ); + + // multi update + var mysum = 0; + c.find().forEach( function(z){ mysum += z.xx || 0; } ); + assert.eq( 17 , mysum, curT.name + " multi update pre" ); + c.update( {} , { $inc : { xx : 1 } } , false , true ); + var mysum = 0; + c.find().forEach( function(z){ mysum += z.xx || 0; } ); + assert.eq( 23 , mysum, curT.name + " multi update" ); + + // TODO remove +} + + +s.stop(); + + diff --git a/jstests/sharding/key_string.js b/jstests/sharding/key_string.js new file mode 100644 index 0000000..8ee1c70 --- /dev/null +++ b/jstests/sharding/key_string.js @@ -0,0 +1,44 @@ +// key_string.js + +s = new ShardingTest( "keystring" , 2 ); + +db = s.getDB( "test" ); +s.adminCommand( { enablesharding : "test" } ) +s.adminCommand( { shardcollection : "test.foo" , key : { name : 1 } } ); + +primary = s.getServer( "test" ).getDB( "test" ); +seconday = s.getOther( primary ).getDB( "test" ); + +assert.eq( 1 , s.config.chunks.count() , "sanity check A" ); + +db.foo.save( { name : "eliot" } ) +db.foo.save( { name : "sara" } ) +db.foo.save( { name : "bob" } ) +db.foo.save( { name : "joe" } ) +db.foo.save( { name : "mark" } ) +db.foo.save( { name : "allan" } ) + +assert.eq( 6 , db.foo.find().count() , "basic count" ); + +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); +s.adminCommand( { split : "test.foo" , find : { name : "joe" } } ); + +s.adminCommand( { movechunk : "test.foo" , find : { name : "joe" } , to : seconday.getMongo().name } ); + +s.printChunks(); + +assert.eq( 3 , primary.foo.find().toArray().length , "primary count" ); +assert.eq( 3 , seconday.foo.find().toArray().length , "secondary count" ); + +assert.eq( 6 , db.foo.find().toArray().length , "total count" ); +assert.eq( 6 , db.foo.find().sort( { name : 1 } ).toArray().length , "total count sorted" ); + +assert.eq( 6 , db.foo.find().sort( { name : 1 } ).count() , "total count with count()" ); + +assert.eq( "allan,bob,eliot,joe,mark,sara" , db.foo.find().sort( { name : 1 } ).toArray().map( function(z){ return z.name; } ) , "sort 1" ); +assert.eq( "sara,mark,joe,eliot,bob,allan" , db.foo.find().sort( { name : -1 } ).toArray().map( function(z){ return z.name; } ) , "sort 2" ); + +s.stop(); + + diff --git a/jstests/sharding/movePrimary1.js b/jstests/sharding/movePrimary1.js new file mode 100644 index 0000000..20dc6c1 --- /dev/null +++ b/jstests/sharding/movePrimary1.js @@ -0,0 +1,31 @@ + + +s = new ShardingTest( "movePrimary1" , 2 ); + +initDB = function( name ){ + var db = s.getDB( name ); + var c = db.foo; + c.save( { a : 1 } ); + c.save( { a : 2 } ); + c.save( { a : 3 } ); + assert( 3 , c.count() ); + + return s.getServer( name ); +} + +from = initDB( "test1" ); +to = s.getOther( from ); + +assert.eq( 3 , from.getDB( "test1" ).foo.count() , "from doesn't have data before move" ); +assert.eq( 0 , to.getDB( "test1" ).foo.count() , "to has data before move" ); + +assert.eq( s.config.databases.findOne( { name : "test1" } ).primary , from.name , "not in db correctly to start" ); +s.admin.runCommand( { moveprimary : "test1" , to : to.name } ); +assert.eq( s.config.databases.findOne( { name : "test1" } ).primary , to.name , "to in config db didn't change" ); + + +assert.eq( 0 , from.getDB( "test1" ).foo.count() , "from still has data after move" ); +assert.eq( 3 , to.getDB( "test1" ).foo.count() , "to doesn't have data after move" ); + +s.stop(); + diff --git a/jstests/sharding/moveshard1.js b/jstests/sharding/moveshard1.js new file mode 100644 index 0000000..b074b4c --- /dev/null +++ b/jstests/sharding/moveshard1.js @@ -0,0 +1,39 @@ +// movechunk1.js + +s = new ShardingTest( "movechunk1" , 2 ); + +l = s._connections[0]; +r = s._connections[1]; + +ldb = l.getDB( "foo" ); +rdb = r.getDB( "foo" ); + +ldb.things.save( { a : 1 } ) +ldb.things.save( { a : 2 } ) +ldb.things.save( { a : 3 } ) + +assert.eq( ldb.things.count() , 3 ); +assert.eq( rdb.things.count() , 0 ); + +startResult = l.getDB( "admin" ).runCommand( { "movechunk.start" : "foo.things" , + "to" : s._serverNames[1] , + "from" : s._serverNames[0] , + filter : { a : { $gt : 2 } } + } ); +print( "movechunk.start: " + tojson( startResult ) ); +assert( startResult.ok == 1 , "start failed!" ); + +finishResult = l.getDB( "admin" ).runCommand( { "movechunk.finish" : "foo.things" , + finishToken : startResult.finishToken , + to : s._serverNames[1] , + newVersion : 1 } ); +print( "movechunk.finish: " + tojson( finishResult ) ); +assert( finishResult.ok == 1 , "finishResult failed!" ); + +assert.eq( rdb.things.count() , 1 , "right has wrong size after move" ); +assert.eq( ldb.things.count() , 2 , "left has wrong size after move" ); + + +s.stop(); + + diff --git a/jstests/sharding/passthrough1.js b/jstests/sharding/passthrough1.js new file mode 100644 index 0000000..d5df0d2 --- /dev/null +++ b/jstests/sharding/passthrough1.js @@ -0,0 +1,10 @@ + +s = new ShardingTest( "passthrough1" , 2 ) + +db = s.getDB( "test" ); +db.foo.insert( { num : 1 , name : "eliot" } ); +db.foo.insert( { num : 2 , name : "sara" } ); +db.foo.insert( { num : -1 , name : "joe" } ); +assert.eq( 3 , db.foo.find().length() ); + +s.stop(); diff --git a/jstests/sharding/shard1.js b/jstests/sharding/shard1.js new file mode 100644 index 0000000..bbe1144 --- /dev/null +++ b/jstests/sharding/shard1.js @@ -0,0 +1,32 @@ +/** +* this tests some of the ground work +*/ + +s = new ShardingTest( "shard1" , 2 ); + +db = s.getDB( "test" ); +db.foo.insert( { num : 1 , name : "eliot" } ); +db.foo.insert( { num : 2 , name : "sara" } ); +db.foo.insert( { num : -1 , name : "joe" } ); +assert.eq( 3 , db.foo.find().length() ); + +shardCommand = { shardcollection : "test.foo" , key : { num : 1 } }; + +assert.throws( function(){ s.adminCommand( shardCommand ); } ); + +s.adminCommand( { enablesharding : "test" } ); +assert.eq( 3 , db.foo.find().length() , "after partitioning count failed" ); + +s.adminCommand( shardCommand ); +dbconfig = s.config.databases.findOne( { name : "test" } ); +assert.eq( dbconfig.sharded["test.foo"] , { key : { num : 1 } , unique : false } , "Sharded content" ); + +assert.eq( 1 , s.config.chunks.count() ); +si = s.config.chunks.findOne(); +assert( si ); +assert.eq( si.ns , "test.foo" ); + +assert.eq( 3 , db.foo.find().length() , "after sharding, no split count failed" ); + + +s.stop(); diff --git a/jstests/sharding/shard2.js b/jstests/sharding/shard2.js new file mode 100644 index 0000000..566a0db --- /dev/null +++ b/jstests/sharding/shard2.js @@ -0,0 +1,194 @@ +// shard2.js + +/** +* test basic sharding +*/ + +placeCheck = function( num ){ + print("shard2 step: " + num ); +} + +s = new ShardingTest( "shard2" , 2 , 6 ); + +db = s.getDB( "test" ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); +assert.eq( 1 , s.config.chunks.count() , "sanity check 1" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 0 } } ); +assert.eq( 2 , s.config.chunks.count() , "should be 2 shards" ); +chunks = s.config.chunks.find().toArray(); +assert.eq( chunks[0].shard , chunks[1].shard , "server should be the same after a split" ); + + +db.foo.save( { num : 1 , name : "eliot" } ); +db.foo.save( { num : 2 , name : "sara" } ); +db.foo.save( { num : -1 , name : "joe" } ); + +s.adminCommand( "connpoolsync" ); + +assert.eq( 3 , s.getServer( "test" ).getDB( "test" ).foo.find().length() , "not right directly to db A" ); +assert.eq( 3 , db.foo.find().length() , "not right on shard" ); + +primary = s.getServer( "test" ).getDB( "test" ); +secondary = s.getOther( primary ).getDB( "test" ); + +assert.eq( 3 , primary.foo.find().length() , "primary wrong B" ); +assert.eq( 0 , secondary.foo.find().length() , "secondary wrong C" ); +assert.eq( 3 , db.foo.find().sort( { num : 1 } ).length() ); + +placeCheck( 2 ); + +// NOTE: at this point we have 2 shard on 1 server + +// test move shard +assert.throws( function(){ s.adminCommand( { movechunk : "test.foo" , find : { num : 1 } , to : primary.getMongo().name } ); } ); +assert.throws( function(){ s.adminCommand( { movechunk : "test.foo" , find : { num : 1 } , to : "adasd" } ) } ); + +s.adminCommand( { movechunk : "test.foo" , find : { num : 1 } , to : secondary.getMongo().name } ); +assert.eq( 2 , secondary.foo.find().length() , "secondary should have 2 after move shard" ); +assert.eq( 1 , primary.foo.find().length() , "primary should only have 1 after move shard" ); + +assert.eq( 2 , s.config.chunks.count() , "still should have 2 shards after move not:" + s.getChunksString() ); +chunks = s.config.chunks.find().toArray(); +assert.neq( chunks[0].shard , chunks[1].shard , "servers should NOT be the same after the move" ); + +placeCheck( 3 ); + +// test inserts go to right server/shard + +db.foo.save( { num : 3 , name : "bob" } ); +s.adminCommand( "connpoolsync" ); +assert.eq( 1 , primary.foo.find().length() , "after move insert go wrong place?" ); +assert.eq( 3 , secondary.foo.find().length() , "after move insert go wrong place?" ); + +db.foo.save( { num : -2 , name : "funny man" } ); +s.adminCommand( "connpoolsync" ); +assert.eq( 2 , primary.foo.find().length() , "after move insert go wrong place?" ); +assert.eq( 3 , secondary.foo.find().length() , "after move insert go wrong place?" ); + + +db.foo.save( { num : 0 , name : "funny guy" } ); +s.adminCommand( "connpoolsync" ); +assert.eq( 2 , primary.foo.find().length() , "boundary A" ); +assert.eq( 4 , secondary.foo.find().length() , "boundary B" ); + +placeCheck( 4 ); + +// findOne +assert.eq( "eliot" , db.foo.findOne( { num : 1 } ).name ); +assert.eq( "funny man" , db.foo.findOne( { num : -2 } ).name ); + +// getAll +function sumQuery( c ){ + var sum = 0; + c.toArray().forEach( + function(z){ + sum += z.num; + } + ); + return sum; +} +assert.eq( 6 , db.foo.find().length() , "sharded query 1" ); +assert.eq( 3 , sumQuery( db.foo.find() ) , "sharded query 2" ); + +placeCheck( 5 ); + +// sort by num + +assert.eq( 3 , sumQuery( db.foo.find().sort( { num : 1 } ) ) , "sharding query w/sort 1" ); +assert.eq( 3 , sumQuery( db.foo.find().sort( { num : -1 } ) ) , "sharding query w/sort 2" ); + +assert.eq( "funny man" , db.foo.find().sort( { num : 1 } )[0].name , "sharding query w/sort 3 order wrong" ); +assert.eq( -2 , db.foo.find().sort( { num : 1 } )[0].num , "sharding query w/sort 4 order wrong" ); + +assert.eq( "bob" , db.foo.find().sort( { num : -1 } )[0].name , "sharding query w/sort 5 order wrong" ); +assert.eq( 3 , db.foo.find().sort( { num : -1 } )[0].num , "sharding query w/sort 6 order wrong" ); + +placeCheck( 6 ); +// sory by name + +function getNames( c ){ + return c.toArray().map( function(z){ return z.name; } ); +} +correct = getNames( db.foo.find() ).sort(); +assert.eq( correct , getNames( db.foo.find().sort( { name : 1 } ) ) ); +correct = correct.reverse(); +assert.eq( correct , getNames( db.foo.find().sort( { name : -1 } ) ) ); + +assert.eq( 3 , sumQuery( db.foo.find().sort( { name : 1 } ) ) , "sharding query w/non-shard sort 1" ); +assert.eq( 3 , sumQuery( db.foo.find().sort( { name : -1 } ) ) , "sharding query w/non-shard sort 2" ); + + +// sort by num multiple shards per server +s.adminCommand( { split : "test.foo" , middle : { num : 2 } } ); +assert.eq( "funny man" , db.foo.find().sort( { num : 1 } )[0].name , "sharding query w/sort and another split 1 order wrong" ); +assert.eq( "bob" , db.foo.find().sort( { num : -1 } )[0].name , "sharding query w/sort and another split 2 order wrong" ); +assert.eq( "funny man" , db.foo.find( { num : { $lt : 100 } } ).sort( { num : 1 } ).arrayAccess(0).name , "sharding query w/sort and another split 3 order wrong" ); + +placeCheck( 7 ); + +// getMore +assert.eq( 4 , db.foo.find().limit(-4).toArray().length , "getMore 1" ); +function countCursor( c ){ + var num = 0; + while ( c.hasNext() ){ + c.next(); + num++; + } + return num; +} +assert.eq( 6 , countCursor( db.foo.find()._exec() ) , "getMore 2" ); +assert.eq( 6 , countCursor( db.foo.find().limit(1)._exec() ) , "getMore 3" ); + +// find by non-shard-key +db.foo.find().forEach( + function(z){ + var y = db.foo.findOne( { _id : z._id } ); + assert( y , "_id check 1 : " + tojson( z ) ); + assert.eq( z.num , y.num , "_id check 2 : " + tojson( z ) ); + } +); + +// update +person = db.foo.findOne( { num : 3 } ); +assert.eq( "bob" , person.name , "update setup 1" ); +person.name = "bob is gone"; +db.foo.update( { num : 3 } , person ); +person = db.foo.findOne( { num : 3 } ); +assert.eq( "bob is gone" , person.name , "update test B" ); + +// remove +assert( db.foo.findOne( { num : 3 } ) != null , "remove test A" ); +db.foo.remove( { num : 3 } ); +assert.isnull( db.foo.findOne( { num : 3 } ) , "remove test B" ); + +db.foo.save( { num : 3 , name : "eliot2" } ); +person = db.foo.findOne( { num : 3 } ); +assert( person , "remove test C" ); +assert.eq( person.name , "eliot2" ); + +db.foo.remove( { _id : person._id } ); +assert.isnull( db.foo.findOne( { num : 3 } ) , "remove test E" ); + +placeCheck( 8 ); + +// TODO: getLastError +db.getLastError(); +db.getPrevError(); + +// ---- move all to the secondary + +assert.eq( 2 , s.onNumShards( "foo" ) , "on 2 shards" ); + +secondary.foo.insert( { num : -3 } ); + +s.adminCommand( { movechunk : "test.foo" , find : { num : -2 } , to : secondary.getMongo().name } ); +assert.eq( 1 , s.onNumShards( "foo" ) , "on 1 shards" ); + +s.adminCommand( { movechunk : "test.foo" , find : { num : -2 } , to : primary.getMongo().name } ); +assert.eq( 2 , s.onNumShards( "foo" ) , "on 2 shards again" ); +assert.eq( 3 , s.config.chunks.count() , "only 3 chunks" ); + +s.stop(); diff --git a/jstests/sharding/shard3.js b/jstests/sharding/shard3.js new file mode 100644 index 0000000..8c5b184 --- /dev/null +++ b/jstests/sharding/shard3.js @@ -0,0 +1,130 @@ +// shard3.js + +s = new ShardingTest( "shard3" , 2 , 50 , 2 ); + +s2 = s._mongos[1]; + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +a = s.getDB( "test" ).foo; +b = s2.getDB( "test" ).foo; + +primary = s.getServer( "test" ).getDB( "test" ).foo; +secondary = s.getOther( primary.name ).getDB( "test" ).foo; + +a.save( { num : 1 } ); +a.save( { num : 2 } ); +a.save( { num : 3 } ); + +assert.eq( 3 , a.find().toArray().length , "normal A" ); +assert.eq( 3 , b.find().toArray().length , "other A" ); + +assert.eq( 3 , primary.count() , "p1" ) +assert.eq( 0 , secondary.count() , "s1" ) + +assert.eq( 1 , s.onNumShards( "foo" ) , "on 1 shards" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 2 } } ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +assert( primary.find().toArray().length > 0 , "blah 1" ); +assert( secondary.find().toArray().length > 0 , "blah 2" ); +assert.eq( 3 , primary.find().itcount() + secondary.find().itcount() , "blah 3" ) + +assert.eq( 3 , a.find().toArray().length , "normal B" ); +assert.eq( 3 , b.find().toArray().length , "other B" ); + +// --- filtering --- + +function doCounts( name , total ){ + total = total || ( primary.count() + secondary.count() ); + assert.eq( total , a.count() , name + " count" ); + assert.eq( total , a.find().sort( { n : 1 } ).itcount() , name + " itcount - sort n" ); + assert.eq( total , a.find().itcount() , name + " itcount" ); + assert.eq( total , a.find().sort( { _id : 1 } ).itcount() , name + " itcount - sort _id" ); + return total; +} + +var total = doCounts( "before wrong save" ) +secondary.save( { num : -3 } ); +doCounts( "after wrong save" , total ) + +// --- move all to 1 --- +print( "MOVE ALL TO 1" ); + +assert.eq( 2 , s.onNumShards( "foo" ) , "on 2 shards" ); +s.printCollectionInfo( "test.foo" ); + +assert( a.findOne( { num : 1 } ) ) +assert( b.findOne( { num : 1 } ) ) + +print( "GOING TO MOVE" ); +s.printCollectionInfo( "test.foo" ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 1 } , to : s.getOther( s.getServer( "test" ) ).name } ); +s.printCollectionInfo( "test.foo" ); +assert.eq( 1 , s.onNumShards( "foo" ) , "on 1 shard again" ); +assert( a.findOne( { num : 1 } ) ) +assert( b.findOne( { num : 1 } ) ) + +print( "*** drop" ); + +s.printCollectionInfo( "test.foo" , "before drop" ); +a.drop(); +s.printCollectionInfo( "test.foo" , "after drop" ); + +assert.eq( 0 , a.count() , "a count after drop" ) +assert.eq( 0 , b.count() , "b count after drop" ) + +s.printCollectionInfo( "test.foo" , "after counts" ); + +assert.eq( 0 , primary.count() , "p count after drop" ) +assert.eq( 0 , secondary.count() , "s count after drop" ) + +primary.save( { num : 1 } ); +secondary.save( { num : 4 } ); + +assert.eq( 1 , primary.count() , "p count after drop adn save" ) +assert.eq( 1 , secondary.count() , "s count after drop save " ) + + +print("*** makes sure that sharding knows where things live" ); + +assert.eq( 1 , a.count() , "a count after drop and save" ) +s.printCollectionInfo( "test.foo" , "after a count" ); +assert.eq( 1 , b.count() , "b count after drop and save" ) +s.printCollectionInfo( "test.foo" , "after b count" ); + +assert( a.findOne( { num : 1 } ) , "a drop1" ); +assert.isnull( a.findOne( { num : 4 } ) , "a drop1" ); + +s.printCollectionInfo( "test.foo" , "after a findOne tests" ); + +assert( b.findOne( { num : 1 } ) , "b drop1" ); +assert.isnull( b.findOne( { num : 4 } ) , "b drop1" ); + +s.printCollectionInfo( "test.foo" , "after b findOne tests" ); + +print( "*** dropDatabase setup" ) + +s.printShardingStatus() +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); +a.save( { num : 2 } ); +a.save( { num : 3 } ); +s.adminCommand( { split : "test.foo" , middle : { num : 2 } } ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); +s.printShardingStatus(); + +s.printCollectionInfo( "test.foo" , "after dropDatabase setup" ); +doCounts( "after dropDatabase setup2" ) +s.printCollectionInfo( "test.foo" , "after dropDatabase setup3" ); + +print( "*** ready to call dropDatabase" ) +res = s.getDB( "test" ).dropDatabase(); +assert.eq( 1 , res.ok , "dropDatabase failed : " + tojson( res ) ); + +s.printShardingStatus(); +s.printCollectionInfo( "test.foo" , "after dropDatabase call 1" ); +assert.eq( 0 , doCounts( "after dropDatabase called" ) ) + +s.stop(); diff --git a/jstests/sharding/shard4.js b/jstests/sharding/shard4.js new file mode 100644 index 0000000..2d7a0df --- /dev/null +++ b/jstests/sharding/shard4.js @@ -0,0 +1,49 @@ +// shard4.js + +s = new ShardingTest( "shard4" , 2 , 50 , 2 ); + +s2 = s._mongos[1]; + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +s.getDB( "test" ).foo.save( { num : 1 } ); +s.getDB( "test" ).foo.save( { num : 2 } ); +s.getDB( "test" ).foo.save( { num : 3 } ); +s.getDB( "test" ).foo.save( { num : 4 } ); +s.getDB( "test" ).foo.save( { num : 5 } ); +s.getDB( "test" ).foo.save( { num : 6 } ); +s.getDB( "test" ).foo.save( { num : 7 } ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal A" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other A" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 4 } } ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +assert( s._connections[0].getDB( "test" ).foo.find().toArray().length > 0 , "blah 1" ); +assert( s._connections[1].getDB( "test" ).foo.find().toArray().length > 0 , "blah 2" ); +assert.eq( 7 , s._connections[0].getDB( "test" ).foo.find().toArray().length + + s._connections[1].getDB( "test" ).foo.find().toArray().length , "blah 3" ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal B" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other B" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 2 } } ); +//s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); +s.printChunks(); + +print( "* A" ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal B 1" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other B 2" ); +print( "* B" ); +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal B 3" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other B 4" ); + +for ( var i=0; i<10; i++ ){ + print( "* C " + i ); + assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other B " + i ); +} + +s.stop(); diff --git a/jstests/sharding/shard5.js b/jstests/sharding/shard5.js new file mode 100644 index 0000000..050a7d7 --- /dev/null +++ b/jstests/sharding/shard5.js @@ -0,0 +1,52 @@ +// shard5.js + +// tests write passthrough + +s = new ShardingTest( "shard5" , 2 , 50 , 2 ); + +s2 = s._mongos[1]; + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { num : 1 } } ); + +s.getDB( "test" ).foo.save( { num : 1 } ); +s.getDB( "test" ).foo.save( { num : 2 } ); +s.getDB( "test" ).foo.save( { num : 3 } ); +s.getDB( "test" ).foo.save( { num : 4 } ); +s.getDB( "test" ).foo.save( { num : 5 } ); +s.getDB( "test" ).foo.save( { num : 6 } ); +s.getDB( "test" ).foo.save( { num : 7 } ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal A" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other A" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 4 } } ); +s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); + +assert( s._connections[0].getDB( "test" ).foo.find().toArray().length > 0 , "blah 1" ); +assert( s._connections[1].getDB( "test" ).foo.find().toArray().length > 0 , "blah 2" ); +assert.eq( 7 , s._connections[0].getDB( "test" ).foo.find().toArray().length + + s._connections[1].getDB( "test" ).foo.find().toArray().length , "blah 3" ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal B" ); +assert.eq( 7 , s2.getDB( "test" ).foo.find().toArray().length , "other B" ); + +s.adminCommand( { split : "test.foo" , middle : { num : 2 } } ); +//s.adminCommand( { movechunk : "test.foo" , find : { num : 3 } , to : s.getOther( s.getServer( "test" ) ).name } ); +s.printChunks() + +print( "* A" ); + +assert.eq( 7 , s.getDB( "test" ).foo.find().toArray().length , "normal B 1" ); + +s2.getDB( "test" ).foo.save( { num : 2 } ); + +assert.soon( + function(){ + return 8 == s2.getDB( "test" ).foo.find().toArray().length; + } , "other B 2" , 5000 , 100 ) + +assert.eq( 2 , s.onNumShards( "foo" ) , "on 2 shards" ); + + +s.stop(); diff --git a/jstests/sharding/shard6.js b/jstests/sharding/shard6.js new file mode 100644 index 0000000..e15d74c --- /dev/null +++ b/jstests/sharding/shard6.js @@ -0,0 +1,39 @@ +// shard6.js + +s = new ShardingTest( "shard6" , 2 , 0 , 1 ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.data" , key : { num : 1 } } ); + +db = s.getDB( "test" ); + +// we want a lot of data, so lets make a 50k string to cheat :) +bigString = ""; +while ( bigString.length < 50000 ) + bigString += "this is a big string. "; + +// ok, now lets insert a some data +var num = 0; +for ( ; num<100; num++ ){ + db.data.save( { num : num , bigString : bigString } ); +} + +assert.eq( 100 , db.data.find().toArray().length ); + +// limit + +assert.eq( 77 , db.data.find().limit(77).itcount() , "limit test 1" ); +assert.eq( 1 , db.data.find().limit(1).itcount() , "limit test 2" ); +for ( var i=1; i<10; i++ ){ + assert.eq( i , db.data.find().limit(i).itcount() , "limit test 3 : " + i ); +} + + +// --- test save support --- + +o = db.data.findOne(); +o.x = 16; +db.data.save( o ); +assert.eq( 16 , db.data.findOne( { _id : o._id } ).x , "x1 - did save fail?" ); + +s.stop(); diff --git a/jstests/sharding/splitpick.js b/jstests/sharding/splitpick.js new file mode 100644 index 0000000..ad27645 --- /dev/null +++ b/jstests/sharding/splitpick.js @@ -0,0 +1,33 @@ +// splitpick.js + +/** +* tests picking the middle to split on +*/ + +s = new ShardingTest( "splitpick" , 2 ); + +db = s.getDB( "test" ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.foo" , key : { a : 1 } } ); + +c = db.foo; + +for ( var i=1; i<20; i++ ){ + c.save( { a : i } ); +} +c.save( { a : 99 } ); + +assert.eq( s.admin.runCommand( { splitvalue : "test.foo" , find : { a : 1 } } ).middle.a , 1 , "splitvalue 1" ); +assert.eq( s.admin.runCommand( { splitvalue : "test.foo" , find : { a : 3 } } ).middle.a , 1 , "splitvalue 2" ); + +s.adminCommand( { split : "test.foo" , find : { a : 1 } } ); +assert.eq( s.admin.runCommand( { splitvalue : "test.foo" , find : { a : 3 } } ).middle.a , 99 , "splitvalue 3" ); +s.adminCommand( { split : "test.foo" , find : { a : 99 } } ); + +assert.eq( s.config.chunks.count() , 3 ); +s.printChunks(); + +assert.eq( s.admin.runCommand( { splitvalue : "test.foo" , find : { a : 50 } } ).middle.a , 10 , "splitvalue 4 " ); + +s.stop(); diff --git a/jstests/sharding/update1.js b/jstests/sharding/update1.js new file mode 100644 index 0000000..82c3d8a --- /dev/null +++ b/jstests/sharding/update1.js @@ -0,0 +1,33 @@ +s = new ShardingTest( "auto1" , 2 , 1 , 1 ); + +s.adminCommand( { enablesharding : "test" } ); +s.adminCommand( { shardcollection : "test.update1" , key : { key : 1 } } ); + +db = s.getDB( "test" ) +coll = db.update1; + +coll.insert({_id:1, key:1}); + +// these are upserts +coll.save({_id:2, key:2}); +coll.save({_id:3, key:3}); + +assert.eq(coll.count(), 3, "count A") + +// update existing using save() +coll.save({_id:1, key:1, other:1}); + +// update existing using update() +coll.update({_id:2}, {key:2, other:2}); +//coll.update({_id:3, key:3}, {other:3}); //should add key to new object (doesn't work yet) +coll.update({_id:3}, {key:3, other:3}); + +assert.eq(coll.count(), 3, "count B") +coll.find().forEach(function(x){ + assert.eq(x._id, x.key, "_id == key"); + assert.eq(x._id, x.other, "_id == other"); +}); + + +s.stop() + diff --git a/jstests/sharding/version1.js b/jstests/sharding/version1.js new file mode 100644 index 0000000..0516aff --- /dev/null +++ b/jstests/sharding/version1.js @@ -0,0 +1,23 @@ +// version1.js + +s = new ShardingTest( "version1" , 1 , 2 ) + +a = s._connections[0].getDB( "admin" ); + +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).ok == 0 ); +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : "a" } ).ok == 0 ); +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , authoritative : true } ).ok == 0 ); +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 2 } ).ok == 0 , "should have failed b/c no auth" ); + +assert.commandWorked( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 2 , authoritative : true } ) , "should have worked" ); +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : "a" , version : 2 } ).ok == 0 ); + +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 2 } ).ok == 1 ); +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 1 } ).ok == 0 ); + +assert.eq( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 3 } ).oldVersion.i , 2 , "oldVersion" ); + +assert.eq( a.runCommand( { "getShardVersion" : "alleyinsider.foo" } ).mine.i , 3 , "my get version A" ); +assert.eq( a.runCommand( { "getShardVersion" : "alleyinsider.foo" } ).global.i , 3 , "my get version B" ); + +s.stop(); diff --git a/jstests/sharding/version2.js b/jstests/sharding/version2.js new file mode 100644 index 0000000..9683c92 --- /dev/null +++ b/jstests/sharding/version2.js @@ -0,0 +1,36 @@ +// version2.js + +s = new ShardingTest( "version2" , 1 , 2 ) + +a = s._connections[0].getDB( "admin" ); + +// setup from one client + +assert( a.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).mine.i == 0 ); +assert( a.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).global.i == 0 ); + +assert( a.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 2 , authoritative : true } ).ok == 1 ); + +assert( a.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).mine.i == 2 ); +assert( a.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).global.i == 2 ); + +// from another client + +a2 = connect( s._connections[0].name + "/admin" ); + +assert.eq( a2.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).global.i , 2 , "a2 global 1" ); +assert.eq( a2.runCommand( { "getShardVersion" : "alleyinsider.foo" , configdb : s._configDB } ).mine.i , 0 , "a2 mine 1" ); + +function simpleFindOne(){ + return a2.getMongo().getDB( "alleyinsider" ).foo.findOne(); +} + +assert.commandWorked( a2.runCommand( { "setShardVersion" : "alleyinsider.bar" , configdb : s._configDB , version : 2 , authoritative : true } ) , "setShardVersion bar temp"); +assert.throws( simpleFindOne , [] , "should complain about not in sharded mode 1" ); +assert( a2.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 2 } ).ok == 1 , "setShardVersion a2-1"); +simpleFindOne(); // now should run ok +assert( a2.runCommand( { "setShardVersion" : "alleyinsider.foo" , configdb : s._configDB , version : 3 } ).ok == 1 , "setShardVersion a2-2"); +simpleFindOne(); // newer version is ok + + +s.stop(); diff --git a/jstests/shellspawn.js b/jstests/shellspawn.js new file mode 100644 index 0000000..ea2b671 --- /dev/null +++ b/jstests/shellspawn.js @@ -0,0 +1,24 @@ +baseName = "jstests_shellspawn"; +t = db.getCollection( baseName ); +t.drop(); + +if ( typeof( _startMongoProgram ) == "undefined" ){ + print( "no fork support" ); +} +else { + spawn = startMongoProgramNoConnect( "mongo", "--port", myPort(), "--eval", "sleep( 2000 ); db.getCollection( \"" + baseName + "\" ).save( {a:1} );" ); + + assert.soon( function() { return 1 == t.count(); } ); + + stopMongoProgramByPid( spawn ); + + spawn = startMongoProgramNoConnect( "mongo", "--port", myPort(), "--eval", "print( \"I am a shell\" );" ); + + spawn = startMongoProgramNoConnect( "mongo", "--port", myPort() ); + + spawn = startMongoProgramNoConnect( "mongo", "--port", myPort() ); + + stopMongoProgramByPid( spawn ); + + // all these shells should be killed +}
\ No newline at end of file diff --git a/jstests/slow/ns1.js b/jstests/slow/ns1.js new file mode 100644 index 0000000..f51db01 --- /dev/null +++ b/jstests/slow/ns1.js @@ -0,0 +1,49 @@ + +mydb = db.getSisterDB( "test_ns1" ); +mydb.dropDatabase(); + +check = function( n , isNew ){ + var coll = mydb["x" + n]; + if ( isNew ){ + assert.eq( 0 , coll.count() , "pop a: " + n ); + coll.insert( { _id : n } ); + } + assert.eq( 1 , coll.count() , "pop b: " + n ); + assert.eq( n , coll.findOne()._id , "pop c: " + n ); + return coll; +} + +max = 0; + +for ( ; max<1000; max++ ){ + check(max,true); +} + +function checkall( removed ){ + for ( var i=0; i<max; i++ ){ + if ( removed == i ){ + assert.eq( 0 , mydb["x"+i].count() , "should be 0 : " + removed ); + } + else { + check( i , false ); + } + } +} + +checkall(); + +Random.srand( 123124 ); +its = max / 2; +print( "its: " + its ); +for ( i=0; i<its; i++ ){ + x = Random.randInt( max ); + check( x , false ).drop(); + checkall( x ); + check( x , true ); + if ( ( i + 1 ) % 20 == 0 ){ + print( i + "/" + its ); + } +} +print( "yay" ) + +mydb.dropDatabase(); diff --git a/jstests/sort1.js b/jstests/sort1.js new file mode 100644 index 0000000..341b343 --- /dev/null +++ b/jstests/sort1.js @@ -0,0 +1,50 @@ +// test sorting, mainly a test ver simple with no index + +debug = function( s ){ + //print( s ); +} + +t = db.sorrrt; +t.drop(); + +t.save({x:3,z:33}); +t.save({x:5,z:33}); +t.save({x:2,z:33}); +t.save({x:3,z:33}); +t.save({x:1,z:33}); + +debug( "a" ) +for( var pass = 0; pass < 2; pass++ ) { + assert( t.find().sort({x:1})[0].x == 1 ); + assert( t.find().sort({x:1}).skip(1)[0].x == 2 ); + assert( t.find().sort({x:-1})[0].x == 5 ); + assert( t.find().sort({x:-1})[1].x == 3 ); + assert.eq( t.find().sort({x:-1}).skip(0)[0].x , 5 ); + assert.eq( t.find().sort({x:-1}).skip(1)[0].x , 3 ); + t.ensureIndex({x:1}); + +} + +debug( "b" ) +assert(t.validate().valid); + + +db.sorrrt2.drop(); +db.sorrrt2.save({x:'a'}); +db.sorrrt2.save({x:'aba'}); +db.sorrrt2.save({x:'zed'}); +db.sorrrt2.save({x:'foo'}); + +debug( "c" ) + +for( var pass = 0; pass < 2; pass++ ) { + debug( tojson( db.sorrrt2.find().sort( { "x" : 1 } ).limit(1).next() ) ); + assert.eq( "a" , db.sorrrt2.find().sort({'x': 1}).limit(1).next().x , "c.1" ); + assert.eq( "a" , db.sorrrt2.find().sort({'x': 1}).next().x , "c.2" ); + assert.eq( "zed" , db.sorrrt2.find().sort({'x': -1}).limit(1).next().x , "c.3" ); + assert.eq( "zed" , db.sorrrt2.find().sort({'x': -1}).next().x , "c.4" ); +} + +debug( "d" ) + +assert(db.sorrrt2.validate().valid); diff --git a/jstests/sort2.js b/jstests/sort2.js new file mode 100644 index 0000000..facd64c --- /dev/null +++ b/jstests/sort2.js @@ -0,0 +1,22 @@ +// test sorting, mainly a test ver simple with no index + +t = db.sorrrt2; +t.drop(); + +t.save({x:1, y:{a:5,b:4}}); +t.save({x:1, y:{a:7,b:3}}); +t.save({x:1, y:{a:2,b:3}}); +t.save({x:1, y:{a:9,b:3}}); + +for( var pass = 0; pass < 2; pass++ ) { + + var res = t.find().sort({'y.a':1}).toArray(); + assert( res[0].y.a == 2 ); + assert( res[1].y.a == 5 ); + assert( res.length == 4 ); + + t.ensureIndex({"y.a":1}); + +} + +assert(t.validate().valid); diff --git a/jstests/sort3.js b/jstests/sort3.js new file mode 100644 index 0000000..b79f1f6 --- /dev/null +++ b/jstests/sort3.js @@ -0,0 +1,16 @@ + +t = db.sort3; +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 5 } ); +t.save( { a : 3 } ); + +assert.eq( "1,5,3" , t.find().toArray().map( function(z){ return z.a; } ) ); + +assert.eq( "1,3,5" , t.find().sort( { a : 1 } ).toArray().map( function(z){ return z.a; } ) ); +assert.eq( "5,3,1" , t.find().sort( { a : -1 } ).toArray().map( function(z){ return z.a; } ) ); + +assert.eq( "1,3,5" , t.find( { query : {} , orderby : { a : 1 } } ).toArray().map( function(z){ return z.a; } ) ); +assert.eq( "5,3,1" , t.find( { query : {} , orderby : { a : -1 } } ).toArray().map( function(z){ return z.a; } ) ); + diff --git a/jstests/sort4.js b/jstests/sort4.js new file mode 100644 index 0000000..5174b46 --- /dev/null +++ b/jstests/sort4.js @@ -0,0 +1,43 @@ +t = db.sort4; +t.drop(); + + +function nice( sort , correct , extra ){ + var c = t.find().sort( sort ); + var s = ""; + c.forEach( + function(z){ + if ( s.length ) + s += ","; + s += z.name; + if ( z.prename ) + s += z.prename; + } + ); + print( tojson( sort ) + "\t" + s ); + if ( correct ) + assert.eq( correct , s , tojson( sort ) + "(" + extra + ")" ); + return s; +} + +t.save({name: 'A', prename: 'B'}) +t.save({name: 'A', prename: 'C'}) +t.save({name: 'B', prename: 'B'}) +t.save({name: 'B', prename: 'D'}) + +nice( { name:1 } , "AB,AC,BB,BD" , "s1" ); +nice( { prename : 1 } , "AB,BB,AC,BD" , "s2" ); +nice( {name:1, prename:1} , "AB,AC,BB,BD" , "s3" ); + +t.save({name: 'A'}) +nice( {name:1, prename:1} , "A,AB,AC,BB,BD" , "e1" ); + +t.save({name: 'C'}) +nice( {name:1, prename:1} , "A,AB,AC,BB,BD,C" , "e2" ); // SERVER-282 + +t.ensureIndex( { name : 1 , prename : 1 } ); +nice( {name:1, prename:1} , "A,AB,AC,BB,BD,C" , "e2ia" ); // SERVER-282 + +t.dropIndexes(); +t.ensureIndex( { name : 1 } ); +nice( {name:1, prename:1} , "A,AB,AC,BB,BD,C" , "e2ib" ); // SERVER-282 diff --git a/jstests/sort5.js b/jstests/sort5.js new file mode 100644 index 0000000..a589355 --- /dev/null +++ b/jstests/sort5.js @@ -0,0 +1,21 @@ +var t = db.sort5; +t.drop(); + +t.save({_id: 5, x: 1, y: {a: 5, b: 4}}); +t.save({_id: 7, x: 2, y: {a: 7, b: 3}}); +t.save({_id: 2, x: 3, y: {a: 2, b: 3}}); +t.save({_id: 9, x: 4, y: {a: 9, b: 3}}); + +// test compound sorting + +assert.eq( [4,2,3,1] , t.find().sort({"y.b": 1 , "y.a" : -1 }).map( function(z){ return z.x; } ) , "A no index" ); +t.ensureIndex({"y.b": 1, "y.a": -1}); +assert.eq( [4,2,3,1] , t.find().sort({"y.b": 1 , "y.a" : -1 }).map( function(z){ return z.x; } ) , "A index" ); +assert(t.validate().valid, "A valid"); + +// test sorting on compound key involving _id + +// assert.eq( [4,2,3,1] , t.find().sort({"y.b": 1 , _id : -1 }).map( function(z){ return z.x; } ) , "B no index" ); +// t.ensureIndex({"y.b": 1, "_id": -1}); +// assert.eq( [4,2,3,1] , t.find().sort({"y.b": 1 , _id : -1 }).map( function(z){ return z.x; } ) , "B index" ); +// assert(t.validate().valid, "B valid"); diff --git a/jstests/sort_numeric.js b/jstests/sort_numeric.js new file mode 100644 index 0000000..807f23d --- /dev/null +++ b/jstests/sort_numeric.js @@ -0,0 +1,35 @@ + +t = db.sort_numeric; +t.drop(); + +// there are two numeric types int he db; make sure it handles them right +// for comparisons. + +t.save( { a : 3 } ); +t.save( { a : 3.1 } ); +t.save( { a : 2.9 } ); +t.save( { a : 1 } ); +t.save( { a : 1.9 } ); +t.save( { a : 5 } ); +t.save( { a : 4.9 } ); +t.save( { a : 2.91 } ); + +for( var pass = 0; pass < 2; pass++ ) { + + var c = t.find().sort({a:1}); + var last = 0; + while( c.hasNext() ) { + current = c.next(); + assert( current.a > last ); + last = current.a; + } + + assert( t.find({a:3}).count() == 1 ); + assert( t.find({a:3.0}).count() == 1 ); + assert( t.find({a:3.0}).length() == 1 ); + + t.ensureIndex({a:1}); +} + +assert(t.validate().valid); + diff --git a/jstests/stats.js b/jstests/stats.js new file mode 100644 index 0000000..26de644 --- /dev/null +++ b/jstests/stats.js @@ -0,0 +1,9 @@ + +t = db.stats1; +t.drop(); + +t.save( { a : 1 } ); + +assert.lt( 0 , t.dataSize() , "A" ); +assert.lt( t.dataSize() , t.storageSize() , "B" ); +assert.lt( 0 , t.totalIndexSize() , "C" ); diff --git a/jstests/storefunc.js b/jstests/storefunc.js new file mode 100644 index 0000000..bae1090 --- /dev/null +++ b/jstests/storefunc.js @@ -0,0 +1,31 @@ + +s = db.system.js; +s.remove({}); +assert.eq( 0 , s.count() , "setup - A" ); + +s.save( { _id : "x" , value : "3" } ); +assert.isnull( db.getLastError() , "setup - B" ); +assert.eq( 1 , s.count() , "setup - C" ); + +s.remove( { _id : "x" } ); +assert.eq( 0 , s.count() , "setup - D" ); +s.save( { _id : "x" , value : "4" } ); +assert.eq( 1 , s.count() , "setup - E" ); + +assert.eq( 4 , s.findOne().value , "setup - F" ); +s.update( { _id : "x" } , { $set : { value : 5 } } ); +assert.eq( 1 , s.count() , "setup - G" ); +assert.eq( 5 , s.findOne().value , "setup - H" ); + +assert.eq( 5 , db.eval( "return x" ) , "exec - 1 " ); + +s.update( { _id : "x" } , { $set : { value : 6 } } ); +assert.eq( 1 , s.count() , "setup2 - A" ); +assert.eq( 6 , s.findOne().value , "setup - B" ); + +assert.eq( 6 , db.eval( "return x" ) , "exec - 2 " ); + + + +s.insert( { _id : "bar" , value : function( z ){ return 17 + z; } } ); +assert.eq( 22 , db.eval( "return bar(5);" ) , "exec - 3 " ); diff --git a/jstests/sub1.js b/jstests/sub1.js new file mode 100644 index 0000000..9e643f8 --- /dev/null +++ b/jstests/sub1.js @@ -0,0 +1,14 @@ +// sub1.js + +t = db.sub1; +t.drop(); + +x = { a : 1 , b : { c : { d : 2 } } } + +t.save( x ); + +y = t.findOne(); + +assert.eq( 1 , y.a ); +assert.eq( 2 , y.b.c.d ); +print( tojson( y ) ); diff --git a/jstests/tool/csv1.js b/jstests/tool/csv1.js new file mode 100644 index 0000000..df8aa10 --- /dev/null +++ b/jstests/tool/csv1.js @@ -0,0 +1,43 @@ +// csv1.js + +t = new ToolTest( "csv1" ) + +c = t.startDB( "foo" ); + +base = { a : 1 , b : "foo,bar" , c: 5 }; + +assert.eq( 0 , c.count() , "setup1" ); +c.insert( base ); +delete base._id +assert.eq( 1 , c.count() , "setup2" ); + +t.runTool( "export" , "--out" , t.extFile , "-d" , t.baseName , "-c" , "foo" , "--csv" , "-f" , "a,b,c" ) + +c.drop() +assert.eq( 0 , c.count() , "after drop" ) + +t.runTool( "import" , "--file" , t.extFile , "-d" , t.baseName , "-c" , "foo" , "--type" , "csv" , "-f" , "a,b,c" ); +assert.soon( "c.findOne()" , "no data after sleep" ); +assert.eq( 2 , c.count() , "after restore 2" ); + +a = c.find().sort( { a : 1 } ).toArray(); +delete a[0]._id +delete a[1]._id +assert.eq( tojson( { a : "a" , b : "b" , c : "c" } ) , tojson( a[1] ) , "csv parse 1" ); +assert.eq( tojson( base ) , tojson(a[0]) , "csv parse 0" ) + +c.drop() +assert.eq( 0 , c.count() , "after drop 2" ) + +t.runTool( "import" , "--file" , t.extFile , "-d" , t.baseName , "-c" , "foo" , "--type" , "csv" , "--headerline" ) +assert.soon( "c.findOne()" , "no data after sleep" ); +assert.eq( 1 , c.count() , "after restore 2" ); + +x = c.findOne() +delete x._id; +assert.eq( tojson( base ) , tojson(x) , "csv parse 2" ) + + + + +t.stop() diff --git a/jstests/tool/dumprestore1.js b/jstests/tool/dumprestore1.js new file mode 100644 index 0000000..73f8fea --- /dev/null +++ b/jstests/tool/dumprestore1.js @@ -0,0 +1,20 @@ +// dumprestore1.js + +t = new ToolTest( "dumprestore1" ); + +c = t.startDB( "foo" ); +assert.eq( 0 , c.count() , "setup1" ); +c.save( { a : 22 } ); +assert.eq( 1 , c.count() , "setup2" ); + +t.runTool( "dump" , "--out" , t.ext ); + +c.drop(); +assert.eq( 0 , c.count() , "after drop" ); + +t.runTool( "restore" , "--dir" , t.ext ); +assert.soon( "c.findOne()" , "no data after sleep" ); +assert.eq( 1 , c.count() , "after restore 2" ); +assert.eq( 22 , c.findOne().a , "after restore 2" ); + +t.stop(); diff --git a/jstests/tool/dumprestore2.js b/jstests/tool/dumprestore2.js new file mode 100644 index 0000000..86e65ae --- /dev/null +++ b/jstests/tool/dumprestore2.js @@ -0,0 +1,26 @@ +// dumprestore2.js + +t = new ToolTest( "dumprestore2" ); + +c = t.startDB( "foo" ); +assert.eq( 0 , c.count() , "setup1" ); +c.save( { a : 22 } ); +assert.eq( 1 , c.count() , "setup2" ); +t.stop(); + +t.runTool( "dump" , "--dbpath" , t.dbpath , "--out" , t.ext ); + +resetDbpath( t.dbpath ); +assert.eq( 0 , listFiles( t.dbpath ).length , "clear" ); + +t.runTool( "restore" , "--dbpath" , t.dbpath , "--dir" , t.ext ); + +listFiles( t.dbpath ).forEach( printjson ) + +c = t.startDB( "foo" ); +assert.soon( "c.findOne()" , "no data after startup" ); +assert.eq( 1 , c.count() , "after restore 2" ); +assert.eq( 22 , c.findOne().a , "after restore 2" ); + +t.stop(); + diff --git a/jstests/tool/exportimport1.js b/jstests/tool/exportimport1.js new file mode 100644 index 0000000..22934fe --- /dev/null +++ b/jstests/tool/exportimport1.js @@ -0,0 +1,20 @@ +// exportimport1.js + +t = new ToolTest( "exportimport1" ); + +c = t.startDB( "foo" ); +assert.eq( 0 , c.count() , "setup1" ); +c.save( { a : 22 } ); +assert.eq( 1 , c.count() , "setup2" ); + +t.runTool( "export" , "--out" , t.extFile , "-d" , t.baseName , "-c" , "foo" ); + +c.drop(); +assert.eq( 0 , c.count() , "after drop" , "-d" , t.baseName , "-c" , "foo" );; + +t.runTool( "import" , "--file" , t.extFile , "-d" , t.baseName , "-c" , "foo" ); +assert.soon( "c.findOne()" , "no data after sleep" ); +assert.eq( 1 , c.count() , "after restore 2" ); +assert.eq( 22 , c.findOne().a , "after restore 2" ); + +t.stop(); diff --git a/jstests/tool/exportimport2.js b/jstests/tool/exportimport2.js new file mode 100644 index 0000000..fbcf239 --- /dev/null +++ b/jstests/tool/exportimport2.js @@ -0,0 +1,24 @@ +// exportimport2.js + +t = new ToolTest( "exportimport2" ); + +c = t.startDB( "foo" ); +assert.eq( 0 , c.count() , "setup1" ); +c.save( { a : 22 } ); +assert.eq( 1 , c.count() , "setup2" ); +t.stop(); + +t.runTool( "export" , "--dbpath" , t.dbpath , "--out" , t.extFile , "-d" , t.baseName , "-c" , "foo" ); + +resetDbpath( t.dbpath ); +assert.eq( 0 , listFiles( t.dbpath ).length , "clear" ); + +t.runTool( "import" , "--dbpath" , t.dbpath , "--file" , t.extFile , "-d" , t.baseName , "-c" , "foo" ); + +c = t.startDB( "foo" ); +assert.soon( "c.findOne()" , "no data after startup" ); +assert.eq( 1 , c.count() , "after restore 2" ); +assert.eq( 22 , c.findOne().a , "after restore 2" ); + +t.stop(); + diff --git a/jstests/tool/tool1.js b/jstests/tool/tool1.js new file mode 100644 index 0000000..00e92e7 --- /dev/null +++ b/jstests/tool/tool1.js @@ -0,0 +1,64 @@ +// mongo tool tests, very basic to start with + +baseName = "jstests_tool_tool1"; +dbPath = "/data/db/" + baseName + "/"; +externalPath = "/data/db/" + baseName + "_external/" +externalFile = externalPath + "export.json" + +function fileSize(){ + var l = listFiles( externalPath ); + for ( var i=0; i<l.length; i++ ){ + if ( l[i].name == externalFile ) + return l[i].size; + } + return -1; +} + + +port = allocatePorts( 1 )[ 0 ]; +resetDbpath( externalPath ); + +m = startMongod( "--port", port, "--dbpath", dbPath, "--nohttpinterface", "--bind_ip", "127.0.0.1" ); +c = m.getDB( baseName ).getCollection( baseName ); +c.save( { a: 1 } ); +assert( c.findOne() ); + +runMongoProgram( "mongodump", "--host", "127.0.0.1:" + port, "--out", externalPath ); +c.drop(); +runMongoProgram( "mongorestore", "--host", "127.0.0.1:" + port, "--dir", externalPath ); +assert.soon( "c.findOne()" , "mongodump then restore has no data w/sleep" ); +assert( c.findOne() , "mongodump then restore has no data" ); +assert.eq( 1 , c.findOne().a , "mongodump then restore has no broken data" ); + +resetDbpath( externalPath ); + +assert.eq( -1 , fileSize() , "mongoexport prep invalid" ); +runMongoProgram( "mongoexport", "--host", "127.0.0.1:" + port, "-d", baseName, "-c", baseName, "--out", externalFile ); +assert.lt( 10 , fileSize() , "file size changed" ); + +c.drop(); +runMongoProgram( "mongoimport", "--host", "127.0.0.1:" + port, "-d", baseName, "-c", baseName, "--file", externalFile ); +assert.soon( "c.findOne()" , "mongo import json A" ); +assert( c.findOne() && 1 == c.findOne().a , "mongo import json B" ); + +stopMongod( port ); +resetDbpath( externalPath ); + +runMongoProgram( "mongodump", "--dbpath", dbPath, "--out", externalPath ); +resetDbpath( dbPath ); +runMongoProgram( "mongorestore", "--dbpath", dbPath, "--dir", externalPath ); +m = startMongoProgram( "mongod", "--port", port, "--dbpath", dbPath, "--nohttpinterface", "--bind_ip", "127.0.0.1" ); +c = m.getDB( baseName ).getCollection( baseName ); +assert.soon( "c.findOne()" , "object missing a" ); +assert( 1 == c.findOne().a, "object wrong" ); + +stopMongod( port ); +resetDbpath( externalPath ); + +runMongoProgram( "mongoexport", "--dbpath", dbPath, "-d", baseName, "-c", baseName, "--out", externalFile ); +resetDbpath( dbPath ); +runMongoProgram( "mongoimport", "--dbpath", dbPath, "-d", baseName, "-c", baseName, "--file", externalFile ); +m = startMongoProgram( "mongod", "--port", port, "--dbpath", dbPath, "--nohttpinterface", "--bind_ip", "127.0.0.1" ); +c = m.getDB( baseName ).getCollection( baseName ); +assert.soon( "c.findOne()" , "object missing b" ); +assert( 1 == c.findOne().a, "object wrong" ); diff --git a/jstests/type1.js b/jstests/type1.js new file mode 100644 index 0000000..94385fa --- /dev/null +++ b/jstests/type1.js @@ -0,0 +1,23 @@ + +t = db.type1; +t.drop(); + +t.save( { x : 1.1 } ); +t.save( { x : "3" } ); +t.save( { x : "asd" } ); +t.save( { x : "foo" } ); + +assert.eq( 4 , t.find().count() , "A1" ); +assert.eq( 1 , t.find( { x : { $type : 1 } } ).count() , "A2" ); +assert.eq( 3 , t.find( { x : { $type : 2 } } ).count() , "A3" ); +assert.eq( 0 , t.find( { x : { $type : 3 } } ).count() , "A4" ); +assert.eq( 4 , t.find( { x : { $type : 1 } } ).explain().nscanned , "A5" ); + + +t.ensureIndex( { x : 1 } ); + +assert.eq( 4 , t.find().count() , "B1" ); +assert.eq( 1 , t.find( { x : { $type : 1 } } ).count() , "B2" ); +assert.eq( 3 , t.find( { x : { $type : 2 } } ).count() , "B3" ); +assert.eq( 0 , t.find( { x : { $type : 3 } } ).count() , "B4" ); +assert.eq( 1 , t.find( { x : { $type : 1 } } ).explain().nscanned , "B5" ); diff --git a/jstests/unique2.js b/jstests/unique2.js new file mode 100644 index 0000000..42cf9fb --- /dev/null +++ b/jstests/unique2.js @@ -0,0 +1,41 @@ + +t = db.jstests_unique2; + +t.drop(); + +/* test for good behavior when indexing multikeys */ + +t.insert({k:3}); +t.insert({k:[2,3]}); +t.insert({k:[4,3]}); + +t.ensureIndex({k:1}, {unique:true, dropDups:true}); + +assert( t.count() == 1 ) ; +assert( t.find().sort({k:1}).toArray().length == 1 ) ; +assert( t.find().sort({k:1}).count() == 1 ) ; + +t.drop(); + +t.ensureIndex({k:1}, {unique:true}); + +t.insert({k:3}); +t.insert({k:[2,3]}); +t.insert({k:[4,3]}); + +assert( t.count() == 1 ) ; +assert( t.find().sort({k:1}).toArray().length == 1 ) ; +assert( t.find().sort({k:1}).count() == 1 ) ; + +t.dropIndexes(); + +t.insert({k:[2,3]}); +t.insert({k:[4,3]}); +assert( t.count() == 3 ) ; + +t.ensureIndex({k:1}, {unique:true, dropDups:true}); + +assert( t.count() == 1 ) ; +assert( t.find().sort({k:1}).toArray().length == 1 ) ; +assert( t.find().sort({k:1}).count() == 1 ) ; + diff --git a/jstests/uniqueness.js b/jstests/uniqueness.js new file mode 100644 index 0000000..f1651b3 --- /dev/null +++ b/jstests/uniqueness.js @@ -0,0 +1,45 @@ + +t = db.jstests_uniqueness; + +t.drop(); + +// test uniqueness of _id + +t.save( { _id : 3 } ); +assert( !db.getLastError(), 1 ); + +// this should yield an error +t.insert( { _id : 3 } ); +assert( db.getLastError() , 2); +assert( t.count() == 1, "hmmm"); + +t.insert( { _id : 4, x : 99 } ); +assert( !db.getLastError() , 3); + +// this should yield an error +t.update( { _id : 4 } , { _id : 3, x : 99 } ); +assert( db.getLastError() , 4); +assert( t.findOne( {_id:4} ), 5 ); + +// Check for an error message when we index and there are dups +db.jstests_uniqueness2.drop(); +db.jstests_uniqueness2.insert({a:3}); +db.jstests_uniqueness2.insert({a:3}); +assert( db.jstests_uniqueness2.count() == 2 , 6) ; +db.jstests_uniqueness2.ensureIndex({a:1}, true); +assert( db.getLastError() , 7); + +/* Check that if we update and remove _id, it gets added back by the DB */ + +/* - test when object grows */ +t.drop(); +t.save( { _id : 'Z' } ); +t.update( {}, { k : 2 } ); +assert( t.findOne()._id == 'Z', "uniqueness.js problem with adding back _id" ); + +/* - test when doesn't grow */ +t.drop(); +t.save( { _id : 'Z', k : 3 } ); +t.update( {}, { k : 2 } ); +assert( t.findOne()._id == 'Z', "uniqueness.js problem with adding back _id (2)" ); + diff --git a/jstests/unset.js b/jstests/unset.js new file mode 100644 index 0000000..f3cdcf0 --- /dev/null +++ b/jstests/unset.js @@ -0,0 +1,19 @@ +t = db.unset; +t.drop(); + +orig = { _id : 1, emb : {} }; +t.insert(orig); + +t.update( { _id : 1 }, { $unset : { 'emb.a' : 1 }}); +t.update( { _id : 1 }, { $unset : { 'z' : 1 }}); +assert.eq( orig , t.findOne() , "A" ); + +t.update( { _id : 1 }, { $set : { 'emb.a' : 1 }}); +t.update( { _id : 1 }, { $set : { 'z' : 1 }}); + +t.update( { _id : 1 }, { $unset : { 'emb.a' : 1 }}); +t.update( { _id : 1 }, { $unset : { 'z' : 1 }}); +assert.eq( orig , t.findOne() , "B" ); // note that emb isn't removed + +t.update( { _id : 1 }, { $unset : { 'emb' : 1 }}); +assert.eq( {_id :1} , t.findOne() , "C" ); diff --git a/jstests/update.js b/jstests/update.js new file mode 100644 index 0000000..70f9f15 --- /dev/null +++ b/jstests/update.js @@ -0,0 +1,25 @@ + + +asdf = db.getCollection( "asdf" ); +asdf.drop(); + +var txt = "asdf"; +for(var i=0; i<10; i++) { + txt = txt + txt; +} + +// fill db +for(var i=1; i<=5000; i++) { + var obj = {txt : txt}; + asdf.save(obj); + + var obj2 = {txt: txt, comments: [{num: i, txt: txt}, {num: [], txt: txt}, {num: true, txt: txt}]}; + asdf.update(obj, obj2); + + if(i%100 == 0) { + var c = asdf.count(); + assert.eq(c , i); + } +} + +assert(asdf.validate().valid); diff --git a/jstests/update2.js b/jstests/update2.js new file mode 100644 index 0000000..654914c --- /dev/null +++ b/jstests/update2.js @@ -0,0 +1,18 @@ +f = db.ed_db_update2; + +f.drop(); +f.save( { a: 4 } ); +f.update( { a: 4 }, { $inc: { a: 2 } } ); +assert.eq( 6, f.findOne().a ); + +f.drop(); +f.save( { a: 4 } ); +f.ensureIndex( { a: 1 } ); +f.update( { a: 4 }, { $inc: { a: 2 } } ); +assert.eq( 6, f.findOne().a ); + +// Verify that drop clears the index +f.drop(); +f.save( { a: 4 } ); +f.update( { a: 4 }, { $inc: { a: 2 } } ); +assert.eq( 6, f.findOne().a ); diff --git a/jstests/update3.js b/jstests/update3.js new file mode 100644 index 0000000..4dfeb90 --- /dev/null +++ b/jstests/update3.js @@ -0,0 +1,23 @@ +// Update with mods corner cases. + +f = db.jstests_update3; + +f.drop(); +f.save( { a:1 } ); +f.update( {}, {$inc:{ a:1 }} ); +assert.eq( 2, f.findOne().a , "A" ); + +f.drop(); +f.save( { a:{ b: 1 } } ); +f.update( {}, {$inc:{ "a.b":1 }} ); +assert.eq( 2, f.findOne().a.b , "B" ); + +f.drop(); +f.save( { a:{ b: 1 } } ); +f.update( {}, {$set:{ "a.b":5 }} ); +assert.eq( 5, f.findOne().a.b , "C" ); + +f.drop(); +f.save( {'_id':0} ); +f.update( {}, {$set:{'_id':5}} ); +assert.eq( 0, f.findOne()._id , "D" ); diff --git a/jstests/update4.js b/jstests/update4.js new file mode 100644 index 0000000..1502f67 --- /dev/null +++ b/jstests/update4.js @@ -0,0 +1,33 @@ +f = db.jstests_update4; +f.drop(); + +getLastError = function() { + ret = db.runCommand( { getlasterror : 1 } ); +// printjson( ret ); + return ret; +} + +f.save( {a:1} ); +f.update( {a:1}, {a:2} ); +assert.eq( true, getLastError().updatedExisting , "A" ); +assert.eq( 1, getLastError().n , "B" ); +f.update( {a:1}, {a:2} ); +assert.eq( false, getLastError().updatedExisting , "C" ); +assert.eq( 0, getLastError().n , "D" ); + +f.update( {a:1}, {a:1}, true ); +assert.eq( false, getLastError().updatedExisting , "E" ); +assert.eq( 1, getLastError().n , "F" ); +f.update( {a:1}, {a:1}, true ); +assert.eq( true, getLastError().updatedExisting , "G" ); +assert.eq( 1, getLastError().n , "H" ); +assert.eq( true, db.getPrevError().updatedExisting , "I" ); +assert.eq( 1, db.getPrevError().nPrev , "J" ); + +f.findOne(); +assert.eq( undefined, getLastError().updatedExisting , "K" ); +assert.eq( true, db.getPrevError().updatedExisting , "L" ); +assert.eq( 2, db.getPrevError().nPrev , "M" ); + +db.forceError(); +assert.eq( undefined, getLastError().updatedExisting , "N" ); diff --git a/jstests/update5.js b/jstests/update5.js new file mode 100644 index 0000000..2728000 --- /dev/null +++ b/jstests/update5.js @@ -0,0 +1,41 @@ + +t = db.update5; + +function go( key ){ + + t.drop(); + + function check( num , name ){ + assert.eq( 1 , t.find().count() , tojson( key ) + " count " + name ); + assert.eq( num , t.findOne().n , tojson( key ) + " value " + name ); + } + + t.update( key , { $inc : { n : 1 } } , true ); + check( 1 , "A" ); + + t.update( key , { $inc : { n : 1 } } , true ); + check( 2 , "B" ); + + t.update( key , { $inc : { n : 1 } } , true ); + check( 3 , "C" ); + + var ik = {}; + for ( k in key ) + ik[k] = 1; + t.ensureIndex( ik ); + + t.update( key , { $inc : { n : 1 } } , true ); + check( 4 , "D" ); + +} + +go( { a : 5 } ); +go( { a : 5 } ); + +go( { a : 5 , b : 7 } ); +go( { a : null , b : 7 } ); + +go( { referer: 'blah' } ); +go( { referer: 'blah', lame: 'bar' } ); +go( { referer: 'blah', name: 'bar' } ); +go( { date: null, referer: 'blah', name: 'bar' } ); diff --git a/jstests/update6.js b/jstests/update6.js new file mode 100644 index 0000000..1f42fe5 --- /dev/null +++ b/jstests/update6.js @@ -0,0 +1,46 @@ + +t = db.update6; +t.drop(); + +t.save( { a : 1 , b : { c : 1 , d : 1 } } ); + +t.update( { a : 1 } , { $inc : { "b.c" : 1 } } ); +assert.eq( 2 , t.findOne().b.c , "A" ); +assert.eq( "c,d" , Object.keySet( t.findOne().b ).toString() , "B" ); + +t.update( { a : 1 } , { $inc : { "b.0e" : 1 } } ); +assert.eq( 1 , t.findOne().b["0e"] , "C" ); +assert.eq( "0e,c,d" , Object.keySet( t.findOne().b ).toString() , "D" ); + +// ----- + +t.drop(); + +t.save( {"_id" : 2 , + "b3" : {"0720" : 5 , "0721" : 12 , "0722" : 11 , "0723" : 3 , "0721" : 12} , + //"b323" : {"0720" : 1} , + } + ); + + +assert.eq( 4 , Object.keySet( t.find({_id:2},{b3:1})[0].b3 ).length , "test 1 : ks before" ); +t.update({_id:2},{$inc: { 'b3.0719' : 1}},true) +assert.eq( 5 , Object.keySet( t.find({_id:2},{b3:1})[0].b3 ).length , "test 1 : ks after" ); + + +// ----- + +t.drop(); + +t.save( {"_id" : 2 , + "b3" : {"0720" : 5 , "0721" : 12 , "0722" : 11 , "0723" : 3 , "0721" : 12} , + "b324" : {"0720" : 1} , + } + ); + + +assert.eq( 4 , Object.keySet( t.find({_id:2},{b3:1})[0].b3 ).length , "test 2 : ks before" ); +printjson( t.find({_id:2},{b3:1})[0].b3 ) +t.update({_id:2},{$inc: { 'b3.0719' : 1}} ) +printjson( t.find({_id:2},{b3:1})[0].b3 ) +assert.eq( 5 , Object.keySet( t.find({_id:2},{b3:1})[0].b3 ).length , "test 2 : ks after" ); diff --git a/jstests/update7.js b/jstests/update7.js new file mode 100644 index 0000000..b893121 --- /dev/null +++ b/jstests/update7.js @@ -0,0 +1,138 @@ + +t = db.update7; +t.drop(); + +function s(){ + return t.find().sort( { _id : 1 } ).map( function(z){ return z.x; } ); +} + +t.save( { _id : 1 , x : 1 } ); +t.save( { _id : 2 , x : 5 } ); + +assert.eq( "1,5" , s() , "A" ); + +t.update( {} , { $inc : { x : 1 } } ); +assert.eq( "2,5" , s() , "B" ); + +t.update( { _id : 1 } , { $inc : { x : 1 } } ); +assert.eq( "3,5" , s() , "C" ); + +t.update( { _id : 2 } , { $inc : { x : 1 } } ); +assert.eq( "3,6" , s() , "D" ); + +t.update( {} , { $inc : { x : 1 } } , false , true ); +assert.eq( "4,7" , s() , "E" ); + +t.update( {} , { $set : { x : 2 } } , false , true ); +assert.eq( "2,2" , s() , "F" ); + +// non-matching in cursor + +t.drop(); + +t.save( { _id : 1 , x : 1 , a : 1 , b : 1 } ); +t.save( { _id : 2 , x : 5 , a : 1 , b : 2 } ); +assert.eq( "1,5" , s() , "B1" ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "2,6" , s() , "B2" ); + +t.update( { b : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,6" , s() , "B3" ); + +t.update( { b : 3 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,6" , s() , "B4" ); + +t.ensureIndex( { a : 1 } ); +t.ensureIndex( { b : 1 } ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "4,7" , s() , "B5" ); + +t.update( { b : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,7" , s() , "B6" ); + +t.update( { b : 3 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,7" , s() , "B7" ); + +t.update( { b : 2 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,8" , s() , "B7" ); + + +// multi-key + +t.drop(); + +t.save( { _id : 1 , x : 1 , a : [ 1 , 2 ] } ); +t.save( { _id : 2 , x : 5 , a : [ 2 , 3 ] } ); +assert.eq( "1,5" , s() , "C1" ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "2,5" , s() , "C2" ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,5" , s() , "C3" ); + +t.update( { a : 3 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,6" , s() , "C4" ); + +t.update( { a : 2 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "4,7" , s() , "C5" ); + +t.update( { a : { $gt : 0 } } , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,8" , s() , "C6" ); + + +t.drop(); + +t.save( { _id : 1 , x : 1 , a : [ 1 , 2 ] } ); +t.save( { _id : 2 , x : 5 , a : [ 2 , 3 ] } ); +t.ensureIndex( { a : 1 } ); +assert.eq( "1,5" , s() , "D1" ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "2,5" , s() , "D2" ); + +t.update( { a : 1 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,5" , s() , "D3" ); + +t.update( { a : 3 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "3,6" , s() , "D4" ); + +t.update( { a : 2 } , { $inc : { x : 1 } } , false , true ); +assert.eq( "4,7" , s() , "D5" ); + +t.update( { a : { $gt : 0 } } , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,8" , s() , "D6" ); + +t.update( { a : { $lt : 10 } } , { $inc : { x : -1 } } , false , true ); +assert.eq( "4,7" , s() , "D7" ); + +// --- + +t.save( { _id : 3 } ); +assert.eq( "4,7," , s() , "E1" ); +t.update( {} , { $inc : { x : 1 } } , false , true ); +assert.eq( "5,8,1" , s() , "E2" ); + +for ( i = 4; i<8; i++ ) + t.save( { _id : i } ); +t.save( { _id : i , x : 1 } ); +assert.eq( "5,8,1,,,,,1" , s() , "E4" ); +t.update( {} , { $inc : { x : 1 } } , false , true ); +assert.eq( "6,9,2,1,1,1,1,2" , s() , "E5" ); + + +// --- $inc indexed field + +t.drop(); + +t.save( { x : 1 } ); +t.save( { x : 2 } ); +t.save( { x : 3 } ); + +t.ensureIndex( { x : 1 } ); + +assert.eq( "1,2,3" , s() , "F1" ) +t.update( { x : { $gt : 0 } } , { $inc : { x : 5 } } , false , true ); +assert.eq( "6,7,8" , s() , "F1" ) diff --git a/jstests/update8.js b/jstests/update8.js new file mode 100644 index 0000000..2388ff8 --- /dev/null +++ b/jstests/update8.js @@ -0,0 +1,11 @@ + +t = db.update8; +t.drop(); + +t.update( { _id : 1 , tags: {"$ne": "a"}}, {"$push": { tags : "a" } } , true ) +assert.eq( { _id : 1 , tags : [ "a" ] } , t.findOne() , "A" ); + +t.drop() +//SERVER-390 +//t.update( { "x.y" : 1 } , { $inc : { i : 1 } } , true ); +//printjson( t.findOne() ); diff --git a/jstests/update9.js b/jstests/update9.js new file mode 100644 index 0000000..45b9e2d --- /dev/null +++ b/jstests/update9.js @@ -0,0 +1,19 @@ + +t = db.update9; +t.drop() + +orig = { "_id" : 1 , + "question" : "a", + "choices" : { "1" : { "choice" : "b" }, + "0" : { "choice" : "c" } } , + + } + +t.save( orig ); +assert.eq( orig , t.findOne() , "A" ); + +t.update({_id: 1, 'choices.0.votes': {$ne: 1}}, {$push: {'choices.0.votes': 1}}) + +orig.choices["0"].votes = [ 1 ] ; +assert.eq( orig.choices["0"] , t.findOne().choices["0"] , "B" ); + diff --git a/jstests/updatea.js b/jstests/updatea.js new file mode 100644 index 0000000..9864aa6 --- /dev/null +++ b/jstests/updatea.js @@ -0,0 +1,50 @@ + +t = db.updatea; +t.drop(); + +orig = { _id : 1 , a : [ { x : 1 , y : 2 } , { x : 10 , y : 11 } ] } + +t.save( orig ) + +// SERVER-181 +t.update( {} , { $set : { "a.0.x" : 3 } } ) +orig.a[0].x = 3; +assert.eq( orig , t.findOne() , "A1" ); + +t.update( {} , { $set : { "a.1.z" : 17 } } ) +orig.a[1].z = 17; +assert.eq( orig , t.findOne() , "A2" ); + +// SERVER-273 +t.update( {} , { $unset : { "a.1.y" : 1 } } ) +delete orig.a[1].y +assert.eq( orig , t.findOne() , "A3" ); + +// SERVER-333 +t.drop(); +orig = { _id : 1 , comments : [ { name : "blah" , rate_up : 0 , rate_ups : [] } ] } +t.save( orig ); + +t.update( {} , { $inc: { "comments.0.rate_up" : 1 } , $push: { "comments.0.rate_ups" : 99 } } ) +orig.comments[0].rate_up++; +orig.comments[0].rate_ups.push( 99 ) +assert.eq( orig , t.findOne() , "B1" ) + +t.drop(); +orig = { _id : 1 , a : [] } +for ( i=0; i<12; i++ ) + orig.a.push( i ); + + +t.save( orig ); +assert.eq( orig , t.findOne() , "C1" ); + +t.update( {} , { $inc: { "a.0" : 1 } } ); +orig.a[0]++; +assert.eq( orig , t.findOne() , "C2" ); + +t.update( {} , { $inc: { "a.10" : 1 } } ); +orig.a[10]++; + + + diff --git a/jstests/updateb.js b/jstests/updateb.js new file mode 100644 index 0000000..ee7c531 --- /dev/null +++ b/jstests/updateb.js @@ -0,0 +1,11 @@ + +t = db.updateb; +t.drop(); + +t.update( { "x.y" : 2 } , { $inc : { a : 7 } } , true ); + +correct = { a : 7 , x : { y : 2 } }; +got = t.findOne(); +delete got._id; +assert.eq( correct , got , "A" ) + diff --git a/jstests/where1.js b/jstests/where1.js new file mode 100644 index 0000000..017d1f3 --- /dev/null +++ b/jstests/where1.js @@ -0,0 +1,14 @@ + +t = db.getCollection( "where1" ); +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 2 } ); +t.save( { a : 3 } ); + +assert.eq( 1 , t.find( function(){ return this.a == 2; } ).length() , "A" ); + +assert.eq( 1 , t.find( { $where : "return this.a == 2" } ).toArray().length , "B" ); +assert.eq( 1 , t.find( { $where : "this.a == 2" } ).toArray().length , "C" ); + +assert.eq( 1 , t.find( "this.a == 2" ).toArray().length , "D" ); diff --git a/jstests/where2.js b/jstests/where2.js new file mode 100644 index 0000000..9262b30 --- /dev/null +++ b/jstests/where2.js @@ -0,0 +1,10 @@ + +t = db.getCollection( "where2" ); +t.drop(); + +t.save( { a : 1 } ); +t.save( { a : 2 } ); +t.save( { a : 3 } ); + +assert.eq( 1 , t.find( { $where : "this.a == 2" } ).toArray().length , "A" ); +assert.eq( 1 , t.find( { $where : "\nthis.a == 2" } ).toArray().length , "B" ); diff --git a/lib/libboost_thread-gcc41-mt-d-1_34_1.a b/lib/libboost_thread-gcc41-mt-d-1_34_1.a Binary files differnew file mode 100644 index 0000000..09377ac --- /dev/null +++ b/lib/libboost_thread-gcc41-mt-d-1_34_1.a diff --git a/mongo.xcodeproj/project.pbxproj b/mongo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f8dabce --- /dev/null +++ b/mongo.xcodeproj/project.pbxproj @@ -0,0 +1,1649 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 44; + objects = { + +/* Begin PBXFileReference section */ + 9302D9930F30AB8C00DFA4EF /* collection.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = collection.js; sourceTree = "<group>"; }; + 9302D9940F30AB8C00DFA4EF /* db.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = db.js; sourceTree = "<group>"; }; + 9302D9950F30AB8C00DFA4EF /* dbshell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbshell.cpp; sourceTree = "<group>"; }; + 9302D9980F30AB8C00DFA4EF /* mongo.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mongo.js; sourceTree = "<group>"; }; + 9302D9990F30AB8C00DFA4EF /* mongo.jsall */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = mongo.jsall; sourceTree = "<group>"; }; + 9302D99E0F30AB8C00DFA4EF /* query.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = query.js; sourceTree = "<group>"; }; + 9302D9A20F30AB8C00DFA4EF /* utils.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = utils.js; sourceTree = "<group>"; }; + 9303D1AB10E1415C00294FAC /* client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = client.cpp; sourceTree = "<group>"; }; + 9303D1AC10E1415C00294FAC /* client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = client.h; sourceTree = "<group>"; }; + 9303D1AD10E1415C00294FAC /* cmdline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cmdline.h; sourceTree = "<group>"; }; + 9303D1AE10E1415C00294FAC /* curop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = curop.h; sourceTree = "<group>"; }; + 9303D1AF10E1415C00294FAC /* extsort.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = extsort.cpp; sourceTree = "<group>"; }; + 9303D1B010E1415C00294FAC /* extsort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = extsort.h; sourceTree = "<group>"; }; + 9303D1B110E1415C00294FAC /* filever.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filever.h; sourceTree = "<group>"; }; + 9303D1B210E1415C00294FAC /* module.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = module.cpp; sourceTree = "<group>"; }; + 9303D1B310E1415C00294FAC /* module.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = module.h; sourceTree = "<group>"; }; + 9303D1B510E1415C00294FAC /* mms.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mms.cpp; sourceTree = "<group>"; }; + 9303D1B610E1415C00294FAC /* mms.o */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.objfile"; path = mms.o; sourceTree = "<group>"; }; + 9303D1B710E1415C00294FAC /* mr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mr.cpp; sourceTree = "<group>"; }; + 9303D1B810E1415C00294FAC /* update.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = update.cpp; sourceTree = "<group>"; }; + 9303D1B910E1415C00294FAC /* update.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = update.h; sourceTree = "<group>"; }; + 930B844D0FA10D1C00F22B4B /* optime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = optime.h; sourceTree = "<group>"; }; + 931184DC0F83C95800A6DC44 /* message_server_port.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = message_server_port.cpp; sourceTree = "<group>"; }; + 931186FB0F8535FF00A6DC44 /* bridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bridge.cpp; sourceTree = "<group>"; }; + 931979810FBC67FB001FE537 /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = "<group>"; }; + 931A027A0F58AA4400147C0E /* jsobjmanipulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jsobjmanipulator.h; sourceTree = "<group>"; }; + 93278F570F72D32900844664 /* gridfs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gridfs.cpp; sourceTree = "<group>"; }; + 93278F580F72D32900844664 /* gridfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gridfs.h; sourceTree = "<group>"; }; + 93278F610F72D39400844664 /* cursors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cursors.cpp; sourceTree = "<group>"; }; + 93278F620F72D39400844664 /* cursors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cursors.h; sourceTree = "<group>"; }; + 93278F630F72D39400844664 /* d_logic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = d_logic.cpp; sourceTree = "<group>"; }; + 93278F640F72D39400844664 /* d_logic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = d_logic.h; sourceTree = "<group>"; }; + 93278F650F72D39400844664 /* strategy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = strategy.cpp; sourceTree = "<group>"; }; + 93278F660F72D39400844664 /* strategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strategy.h; sourceTree = "<group>"; }; + 93278F670F72D39400844664 /* strategy_shard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = strategy_shard.cpp; sourceTree = "<group>"; }; + 932AC3EB0F4A5B34005BF8B0 /* queryoptimizertests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = queryoptimizertests.cpp; sourceTree = "<group>"; }; + 932AC4310F4A5E9D005BF8B0 /* SConstruct */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConstruct; sourceTree = "<group>"; }; + 933A4D130F55A68600145C4B /* authTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = authTest.cpp; sourceTree = "<group>"; }; + 933A4D150F55A68600145C4B /* clientTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = clientTest.cpp; sourceTree = "<group>"; }; + 933A4D170F55A68600145C4B /* first.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = first.cpp; sourceTree = "<group>"; }; + 933A4D190F55A68600145C4B /* second.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = second.cpp; sourceTree = "<group>"; }; + 933A4D1B0F55A68600145C4B /* tail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tail.cpp; sourceTree = "<group>"; }; + 933A4D1C0F55A68600145C4B /* tutorial.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tutorial.cpp; sourceTree = "<group>"; }; + 933A4D1D0F55A68600145C4B /* whereExample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = whereExample.cpp; sourceTree = "<group>"; }; + 933E22110F4327B2000209E3 /* perftest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = perftest.cpp; sourceTree = "<group>"; }; + 933E22120F4327B2000209E3 /* perftest.o */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.objfile"; path = perftest.o; sourceTree = "<group>"; }; + 9342232B0EF16D4F00608550 /* connpool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = connpool.cpp; sourceTree = "<group>"; }; + 9342232C0EF16D4F00608550 /* connpool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = connpool.h; sourceTree = "<group>"; }; + 9342232D0EF16D4F00608550 /* dbclient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbclient.cpp; sourceTree = "<group>"; }; + 9342232E0EF16D4F00608550 /* dbclient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbclient.h; sourceTree = "<group>"; }; + 934223300EF16D4F00608550 /* model.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = model.cpp; sourceTree = "<group>"; }; + 934223310EF16D4F00608550 /* model.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model.h; sourceTree = "<group>"; }; + 934223860EF16D7000608550 /* btreetests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = btreetests.cpp; sourceTree = "<group>"; }; + 934223880EF16D7000608550 /* dbtests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbtests.cpp; sourceTree = "<group>"; }; + 934223890EF16D7000608550 /* dbtests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbtests.h; sourceTree = "<group>"; }; + 9342238C0EF16D7000608550 /* mockdbclient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mockdbclient.h; sourceTree = "<group>"; }; + 9342238D0EF16D7000608550 /* pairingtests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pairingtests.cpp; sourceTree = "<group>"; }; + 934223900EF16DB400608550 /* btree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = btree.cpp; sourceTree = "<group>"; }; + 934223910EF16DB400608550 /* btree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = btree.h; sourceTree = "<group>"; }; + 934223920EF16DB400608550 /* btreecursor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = btreecursor.cpp; sourceTree = "<group>"; }; + 934223930EF16DB400608550 /* clientcursor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = clientcursor.cpp; sourceTree = "<group>"; }; + 934223940EF16DB400608550 /* clientcursor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clientcursor.h; sourceTree = "<group>"; }; + 934223950EF16DB400608550 /* cloner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cloner.cpp; sourceTree = "<group>"; }; + 934223960EF16DB400608550 /* commands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = commands.cpp; sourceTree = "<group>"; }; + 934223970EF16DB400608550 /* commands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = commands.h; sourceTree = "<group>"; }; + 934223980EF16DB400608550 /* cursor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; }; + 934223990EF16DB400608550 /* database.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = database.h; sourceTree = "<group>"; }; + 9342239A0EF16DB400608550 /* db.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = db.cpp; sourceTree = "<group>"; }; + 9342239B0EF16DB400608550 /* db.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = db.h; sourceTree = "<group>"; }; + 9342239F0EF16DB400608550 /* dbcommands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbcommands.cpp; sourceTree = "<group>"; }; + 934223A00EF16DB400608550 /* dbeval.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbeval.cpp; sourceTree = "<group>"; }; + 934223A10EF16DB400608550 /* dbhelpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbhelpers.cpp; sourceTree = "<group>"; }; + 934223A20EF16DB400608550 /* dbhelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbhelpers.h; sourceTree = "<group>"; }; + 934223A30EF16DB400608550 /* dbinfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbinfo.cpp; sourceTree = "<group>"; }; + 934223A40EF16DB400608550 /* dbinfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbinfo.h; sourceTree = "<group>"; }; + 934223A50EF16DB400608550 /* dbmessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbmessage.h; sourceTree = "<group>"; }; + 934223A60EF16DB400608550 /* dbwebserver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dbwebserver.cpp; sourceTree = "<group>"; }; + 934223A70EF16DB400608550 /* instance.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = instance.cpp; sourceTree = "<group>"; }; + 934223A80EF16DB400608550 /* instance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = instance.h; sourceTree = "<group>"; }; + 934223A90EF16DB400608550 /* introspect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = introspect.cpp; sourceTree = "<group>"; }; + 934223AA0EF16DB400608550 /* introspect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = introspect.h; sourceTree = "<group>"; }; + 934223AD0EF16DB400608550 /* javatest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = javatest.cpp; sourceTree = "<group>"; }; + 934223AE0EF16DB400608550 /* jsobj.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsobj.cpp; sourceTree = "<group>"; }; + 934223AF0EF16DB400608550 /* jsobj.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jsobj.h; sourceTree = "<group>"; }; + 934223B00EF16DB400608550 /* json.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json.cpp; sourceTree = "<group>"; }; + 934223B10EF16DB400608550 /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json.h; sourceTree = "<group>"; }; + 934223B70EF16DB400608550 /* matcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = matcher.cpp; sourceTree = "<group>"; }; + 934223B80EF16DB400608550 /* matcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = matcher.h; sourceTree = "<group>"; }; + 934223B90EF16DB400608550 /* minilex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minilex.h; sourceTree = "<group>"; }; + 934223BA0EF16DB400608550 /* namespace.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = namespace.cpp; sourceTree = "<group>"; }; + 934223BB0EF16DB400608550 /* namespace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = namespace.h; sourceTree = "<group>"; }; + 934223BD0EF16DB400608550 /* pdfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pdfile.cpp; sourceTree = "<group>"; }; + 934223BE0EF16DB400608550 /* pdfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pdfile.h; sourceTree = "<group>"; }; + 934223BF0EF16DB400608550 /* query.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = query.cpp; sourceTree = "<group>"; }; + 934223C00EF16DB400608550 /* query.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = query.h; sourceTree = "<group>"; }; + 934223C10EF16DB400608550 /* queryoptimizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = queryoptimizer.cpp; sourceTree = "<group>"; }; + 934223C20EF16DB400608550 /* queryoptimizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queryoptimizer.h; sourceTree = "<group>"; }; + 934223C30EF16DB400608550 /* repl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = repl.cpp; sourceTree = "<group>"; }; + 934223C40EF16DB400608550 /* repl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = repl.h; sourceTree = "<group>"; }; + 934223C50EF16DB400608550 /* replset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = replset.h; sourceTree = "<group>"; }; + 934223C60EF16DB400608550 /* resource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resource.h; sourceTree = "<group>"; }; + 934223C70EF16DB400608550 /* scanandorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scanandorder.h; sourceTree = "<group>"; }; + 934223C80EF16DB400608550 /* storage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = storage.h; sourceTree = "<group>"; }; + 934223C90EF16DB400608550 /* tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tests.cpp; sourceTree = "<group>"; }; + 934BEB9B10DFFA9600178102 /* _lodeRunner.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = _lodeRunner.js; sourceTree = "<group>"; }; + 934BEB9C10DFFA9600178102 /* _runner.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = _runner.js; sourceTree = "<group>"; }; + 934BEB9D10DFFA9600178102 /* _runner_leak.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = _runner_leak.js; sourceTree = "<group>"; }; + 934BEB9E10DFFA9600178102 /* _runner_leak_nojni.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = _runner_leak_nojni.js; sourceTree = "<group>"; }; + 934BEB9F10DFFA9600178102 /* _runner_sharding.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = _runner_sharding.js; sourceTree = "<group>"; }; + 934BEBA010DFFA9600178102 /* all.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = all.js; sourceTree = "<group>"; }; + 934BEBA110DFFA9600178102 /* all2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = all2.js; sourceTree = "<group>"; }; + 934BEBA210DFFA9600178102 /* apitest_db.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = apitest_db.js; sourceTree = "<group>"; }; + 934BEBA310DFFA9600178102 /* apitest_dbcollection.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = apitest_dbcollection.js; sourceTree = "<group>"; }; + 934BEBA410DFFA9600178102 /* array1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = array1.js; sourceTree = "<group>"; }; + 934BEBA510DFFA9600178102 /* array3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = array3.js; sourceTree = "<group>"; }; + 934BEBA610DFFA9600178102 /* auth1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = auth1.js; sourceTree = "<group>"; }; + 934BEBA710DFFA9600178102 /* auth2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = auth2.js; sourceTree = "<group>"; }; + 934BEBA810DFFA9600178102 /* autoid.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = autoid.js; sourceTree = "<group>"; }; + 934BEBA910DFFA9600178102 /* basic1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic1.js; sourceTree = "<group>"; }; + 934BEBAA10DFFA9600178102 /* basic2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic2.js; sourceTree = "<group>"; }; + 934BEBAB10DFFA9600178102 /* basic3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic3.js; sourceTree = "<group>"; }; + 934BEBAC10DFFA9600178102 /* basic4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic4.js; sourceTree = "<group>"; }; + 934BEBAD10DFFA9600178102 /* basic5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic5.js; sourceTree = "<group>"; }; + 934BEBAE10DFFA9600178102 /* basic6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic6.js; sourceTree = "<group>"; }; + 934BEBAF10DFFA9600178102 /* basic7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic7.js; sourceTree = "<group>"; }; + 934BEBB010DFFA9600178102 /* basic8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic8.js; sourceTree = "<group>"; }; + 934BEBB110DFFA9600178102 /* basic9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic9.js; sourceTree = "<group>"; }; + 934BEBB210DFFA9600178102 /* basica.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basica.js; sourceTree = "<group>"; }; + 934BEBB310DFFA9600178102 /* basicb.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basicb.js; sourceTree = "<group>"; }; + 934BEBB410DFFA9600178102 /* capped.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped.js; sourceTree = "<group>"; }; + 934BEBB510DFFA9600178102 /* capped1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped1.js; sourceTree = "<group>"; }; + 934BEBB610DFFA9600178102 /* btree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = btree.h; sourceTree = "<group>"; }; + 934BEBB710DFFA9600178102 /* capped3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped3.js; sourceTree = "<group>"; }; + 934BEBB810DFFA9600178102 /* capped4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped4.js; sourceTree = "<group>"; }; + 934BEBBA10DFFA9600178102 /* clonecollection.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = clonecollection.js; sourceTree = "<group>"; }; + 934BEBBB10DFFA9600178102 /* copydb.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = copydb.js; sourceTree = "<group>"; }; + 934BEBBC10DFFA9600178102 /* count.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count.js; sourceTree = "<group>"; }; + 934BEBBD10DFFA9600178102 /* count2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count2.js; sourceTree = "<group>"; }; + 934BEBBE10DFFA9600178102 /* count3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count3.js; sourceTree = "<group>"; }; + 934BEBBF10DFFA9600178102 /* count4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count4.js; sourceTree = "<group>"; }; + 934BEBC010DFFA9600178102 /* count5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count5.js; sourceTree = "<group>"; }; + 934BEBC110DFFA9600178102 /* cursor1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor1.js; sourceTree = "<group>"; }; + 934BEBC210DFFA9600178102 /* cursor2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor2.js; sourceTree = "<group>"; }; + 934BEBC310DFFA9600178102 /* cursor3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor3.js; sourceTree = "<group>"; }; + 934BEBC410DFFA9600178102 /* cursor4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor4.js; sourceTree = "<group>"; }; + 934BEBC510DFFA9600178102 /* cursor5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor5.js; sourceTree = "<group>"; }; + 934BEBC610DFFA9600178102 /* cursor6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor6.js; sourceTree = "<group>"; }; + 934BEBC710DFFA9600178102 /* cursor7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor7.js; sourceTree = "<group>"; }; + 934BEBC810DFFA9600178102 /* cursor8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cursor8.js; sourceTree = "<group>"; }; + 934BEBC910DFFA9600178102 /* datasize.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = datasize.js; sourceTree = "<group>"; }; + 934BEBCA10DFFA9600178102 /* date1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = date1.js; sourceTree = "<group>"; }; + 934BEBCB10DFFA9600178102 /* dbref1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dbref1.js; sourceTree = "<group>"; }; + 934BEBCC10DFFA9600178102 /* dbref2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dbref2.js; sourceTree = "<group>"; }; + 934BEBCE10DFFA9600178102 /* dbNoCreate.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dbNoCreate.js; sourceTree = "<group>"; }; + 934BEBCF10DFFA9600178102 /* diskfull.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = diskfull.js; sourceTree = "<group>"; }; + 934BEBD010DFFA9600178102 /* norepeat.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = norepeat.js; sourceTree = "<group>"; }; + 934BEBD110DFFA9600178102 /* distinct1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = distinct1.js; sourceTree = "<group>"; }; + 934BEBD210DFFA9600178102 /* distinct2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = distinct2.js; sourceTree = "<group>"; }; + 934BEBD310DFFA9600178102 /* drop.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = drop.js; sourceTree = "<group>"; }; + 934BEBD410DFFA9600178102 /* error1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = error1.js; sourceTree = "<group>"; }; + 934BEBD510DFFA9600178102 /* error2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = error2.js; sourceTree = "<group>"; }; + 934BEBD610DFFA9600178102 /* error3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = error3.js; sourceTree = "<group>"; }; + 934BEBD710DFFA9600178102 /* eval0.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval0.js; sourceTree = "<group>"; }; + 934BEBD810DFFA9600178102 /* eval1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval1.js; sourceTree = "<group>"; }; + 934BEBD910DFFA9600178102 /* eval2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval2.js; sourceTree = "<group>"; }; + 934BEBDA10DFFA9600178102 /* eval3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval3.js; sourceTree = "<group>"; }; + 934BEBDB10DFFA9600178102 /* eval4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval4.js; sourceTree = "<group>"; }; + 934BEBDC10DFFA9600178102 /* eval5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval5.js; sourceTree = "<group>"; }; + 934BEBDD10DFFA9600178102 /* eval6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval6.js; sourceTree = "<group>"; }; + 934BEBDE10DFFA9600178102 /* eval7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval7.js; sourceTree = "<group>"; }; + 934BEBDF10DFFA9600178102 /* eval8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval8.js; sourceTree = "<group>"; }; + 934BEBE010DFFA9600178102 /* eval9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = eval9.js; sourceTree = "<group>"; }; + 934BEBE110DFFA9600178102 /* evala.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = evala.js; sourceTree = "<group>"; }; + 934BEBE210DFFA9600178102 /* evalb.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = evalb.js; sourceTree = "<group>"; }; + 934BEBE310DFFA9600178102 /* exists.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = exists.js; sourceTree = "<group>"; }; + 934BEBE410DFFA9600178102 /* explain1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = explain1.js; sourceTree = "<group>"; }; + 934BEBE510DFFA9600178102 /* find1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find1.js; sourceTree = "<group>"; }; + 934BEBE610DFFA9600178102 /* find2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find2.js; sourceTree = "<group>"; }; + 934BEBE710DFFA9600178102 /* find3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find3.js; sourceTree = "<group>"; }; + 934BEBE810DFFA9600178102 /* find4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find4.js; sourceTree = "<group>"; }; + 934BEBE910DFFA9600178102 /* find5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find5.js; sourceTree = "<group>"; }; + 934BEBEA10DFFA9600178102 /* find6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find6.js; sourceTree = "<group>"; }; + 934BEBEB10DFFA9600178102 /* fm1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = fm1.js; sourceTree = "<group>"; }; + 934BEBEC10DFFA9600178102 /* fm2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = fm2.js; sourceTree = "<group>"; }; + 934BEBED10DFFA9600178102 /* fm3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = fm3.js; sourceTree = "<group>"; }; + 934BEBEE10DFFA9600178102 /* group1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = group1.js; sourceTree = "<group>"; }; + 934BEBEF10DFFA9600178102 /* group2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = group2.js; sourceTree = "<group>"; }; + 934BEBF010DFFA9600178102 /* group3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = group3.js; sourceTree = "<group>"; }; + 934BEBF110DFFA9600178102 /* group4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = group4.js; sourceTree = "<group>"; }; + 934BEBF210DFFA9600178102 /* group5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = group5.js; sourceTree = "<group>"; }; + 934BEBF310DFFA9600178102 /* hint1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = hint1.js; sourceTree = "<group>"; }; + 934BEBF410DFFA9600178102 /* id1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = id1.js; sourceTree = "<group>"; }; + 934BEBF510DFFA9600178102 /* in.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = in.js; sourceTree = "<group>"; }; + 934BEBF610DFFA9600178102 /* in2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = in2.js; sourceTree = "<group>"; }; + 934BEBF710DFFA9600178102 /* inc1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = inc1.js; sourceTree = "<group>"; }; + 934BEBF810DFFA9600178102 /* inc2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = inc2.js; sourceTree = "<group>"; }; + 934BEBF910DFFA9600178102 /* inc3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = inc3.js; sourceTree = "<group>"; }; + 934BEBFA10DFFA9600178102 /* index1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index1.js; sourceTree = "<group>"; }; + 934BEBFB10DFFA9600178102 /* index10.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index10.js; sourceTree = "<group>"; }; + 934BEBFC10DFFA9600178102 /* index2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index2.js; sourceTree = "<group>"; }; + 934BEBFD10DFFA9600178102 /* index3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index3.js; sourceTree = "<group>"; }; + 934BEBFE10DFFA9600178102 /* index4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index4.js; sourceTree = "<group>"; }; + 934BEBFF10DFFA9600178102 /* index5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index5.js; sourceTree = "<group>"; }; + 934BEC0010DFFA9600178102 /* index6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index6.js; sourceTree = "<group>"; }; + 934BEC0110DFFA9600178102 /* index7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index7.js; sourceTree = "<group>"; }; + 934BEC0210DFFA9600178102 /* index8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index8.js; sourceTree = "<group>"; }; + 934BEC0310DFFA9600178102 /* index9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index9.js; sourceTree = "<group>"; }; + 934BEC0410DFFA9600178102 /* index_check1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check1.js; sourceTree = "<group>"; }; + 934BEC0510DFFA9600178102 /* index_check2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check2.js; sourceTree = "<group>"; }; + 934BEC0610DFFA9600178102 /* index_check3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check3.js; sourceTree = "<group>"; }; + 934BEC0710DFFA9600178102 /* index_check5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check5.js; sourceTree = "<group>"; }; + 934BEC0810DFFA9600178102 /* index_check6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check6.js; sourceTree = "<group>"; }; + 934BEC0910DFFA9600178102 /* index_check7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_check7.js; sourceTree = "<group>"; }; + 934BEC0A10DFFA9600178102 /* index_many.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index_many.js; sourceTree = "<group>"; }; + 934BEC0B10DFFA9600178102 /* indexa.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexa.js; sourceTree = "<group>"; }; + 934BEC0C10DFFA9600178102 /* indexapi.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexapi.js; sourceTree = "<group>"; }; + 934BEC0D10DFFA9600178102 /* indexb.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexb.js; sourceTree = "<group>"; }; + 934BEC0E10DFFA9600178102 /* indexc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexc.js; sourceTree = "<group>"; }; + 934BEC0F10DFFA9600178102 /* indexd.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexd.js; sourceTree = "<group>"; }; + 934BEC1010DFFA9600178102 /* indexe.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = indexe.js; sourceTree = "<group>"; }; + 934BEC1110DFFA9600178102 /* jni1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni1.js; sourceTree = "<group>"; }; + 934BEC1210DFFA9600178102 /* jni2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni2.js; sourceTree = "<group>"; }; + 934BEC1310DFFA9600178102 /* jni3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni3.js; sourceTree = "<group>"; }; + 934BEC1410DFFA9600178102 /* jni4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni4.js; sourceTree = "<group>"; }; + 934BEC1510DFFA9600178102 /* jni5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni5.js; sourceTree = "<group>"; }; + 934BEC1610DFFA9600178102 /* jni7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni7.js; sourceTree = "<group>"; }; + 934BEC1710DFFA9600178102 /* jni8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni8.js; sourceTree = "<group>"; }; + 934BEC1810DFFA9600178102 /* jni9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jni9.js; sourceTree = "<group>"; }; + 934BEC1910DFFA9600178102 /* json1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = json1.js; sourceTree = "<group>"; }; + 934BEC1A10DFFA9600178102 /* map1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = map1.js; sourceTree = "<group>"; }; + 934BEC1B10DFFA9600178102 /* median.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = median.js; sourceTree = "<group>"; }; + 934BEC1C10DFFA9600178102 /* minmax.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = minmax.js; sourceTree = "<group>"; }; + 934BEC1D10DFFA9600178102 /* mod1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mod1.js; sourceTree = "<group>"; }; + 934BEC1E10DFFA9600178102 /* mr1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr1.js; sourceTree = "<group>"; }; + 934BEC1F10DFFA9600178102 /* mr2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr2.js; sourceTree = "<group>"; }; + 934BEC2010DFFA9600178102 /* mr3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr3.js; sourceTree = "<group>"; }; + 934BEC2110DFFA9600178102 /* mr4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr4.js; sourceTree = "<group>"; }; + 934BEC2210DFFA9600178102 /* mr5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr5.js; sourceTree = "<group>"; }; + 934BEC2310DFFA9600178102 /* multi.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = multi.js; sourceTree = "<group>"; }; + 934BEC2410DFFA9600178102 /* multi2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = multi2.js; sourceTree = "<group>"; }; + 934BEC2510DFFA9600178102 /* ne1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ne1.js; sourceTree = "<group>"; }; + 934BEC2610DFFA9600178102 /* nin.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = nin.js; sourceTree = "<group>"; }; + 934BEC2710DFFA9600178102 /* not1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = not1.js; sourceTree = "<group>"; }; + 934BEC2810DFFA9600178102 /* null.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = null.js; sourceTree = "<group>"; }; + 934BEC2910DFFA9600178102 /* objid1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = objid1.js; sourceTree = "<group>"; }; + 934BEC2A10DFFA9600178102 /* objid2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = objid2.js; sourceTree = "<group>"; }; + 934BEC2B10DFFA9600178102 /* objid3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = objid3.js; sourceTree = "<group>"; }; + 934BEC2C10DFFA9600178102 /* objid4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = objid4.js; sourceTree = "<group>"; }; + 934BEC2D10DFFA9600178102 /* objid5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = objid5.js; sourceTree = "<group>"; }; + 934BEC2F10DFFA9600178102 /* find1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find1.js; sourceTree = "<group>"; }; + 934BEC3010DFFA9600178102 /* index1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index1.js; sourceTree = "<group>"; }; + 934BEC3110DFFA9600178102 /* remove1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove1.js; sourceTree = "<group>"; }; + 934BEC3210DFFA9600178102 /* profile1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = profile1.js; sourceTree = "<group>"; }; + 934BEC3310DFFA9600178102 /* pull.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pull.js; sourceTree = "<group>"; }; + 934BEC3410DFFA9600178102 /* pull2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pull2.js; sourceTree = "<group>"; }; + 934BEC3510DFFA9600178102 /* pullall.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pullall.js; sourceTree = "<group>"; }; + 934BEC3610DFFA9600178102 /* push.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = push.js; sourceTree = "<group>"; }; + 934BEC3710DFFA9600178102 /* pushall.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pushall.js; sourceTree = "<group>"; }; + 934BEC3810DFFA9600178102 /* query1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = query1.js; sourceTree = "<group>"; }; + 934BEC3910DFFA9600178102 /* queryoptimizer1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = queryoptimizer1.js; sourceTree = "<group>"; }; + 934BEC3B10DFFA9600178102 /* quota1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = quota1.js; sourceTree = "<group>"; }; + 934BEC3C10DFFA9600178102 /* recstore.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = recstore.js; sourceTree = "<group>"; }; + 934BEC3D10DFFA9600178102 /* ref.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ref.js; sourceTree = "<group>"; }; + 934BEC3E10DFFA9600178102 /* ref2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ref2.js; sourceTree = "<group>"; }; + 934BEC3F10DFFA9600178102 /* ref3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ref3.js; sourceTree = "<group>"; }; + 934BEC4010DFFA9600178102 /* ref4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ref4.js; sourceTree = "<group>"; }; + 934BEC4110DFFA9600178102 /* regex.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = regex.js; sourceTree = "<group>"; }; + 934BEC4210DFFA9600178102 /* regex2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = regex2.js; sourceTree = "<group>"; }; + 934BEC4310DFFA9600178102 /* regex3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = regex3.js; sourceTree = "<group>"; }; + 934BEC4410DFFA9600178102 /* regex4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = regex4.js; sourceTree = "<group>"; }; + 934BEC4510DFFA9600178102 /* remove.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove.js; sourceTree = "<group>"; }; + 934BEC4610DFFA9600178102 /* remove2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove2.js; sourceTree = "<group>"; }; + 934BEC4710DFFA9600178102 /* remove3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove3.js; sourceTree = "<group>"; }; + 934BEC4810DFFA9600178102 /* remove4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove4.js; sourceTree = "<group>"; }; + 934BEC4910DFFA9600178102 /* remove5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove5.js; sourceTree = "<group>"; }; + 934BEC4A10DFFA9600178102 /* remove6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove6.js; sourceTree = "<group>"; }; + 934BEC4B10DFFA9600178102 /* remove7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove7.js; sourceTree = "<group>"; }; + 934BEC4C10DFFA9600178102 /* remove8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remove8.js; sourceTree = "<group>"; }; + 934BEC4D10DFFA9600178102 /* rename.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = rename.js; sourceTree = "<group>"; }; + 934BEC4E10DFFA9600178102 /* rename2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = rename2.js; sourceTree = "<group>"; }; + 934BEC4F10DFFA9600178102 /* repair.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repair.js; sourceTree = "<group>"; }; + 934BEC5110DFFA9600178102 /* basic1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic1.js; sourceTree = "<group>"; }; + 934BEC5210DFFA9600178102 /* pair1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair1.js; sourceTree = "<group>"; }; + 934BEC5310DFFA9600178102 /* pair2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair2.js; sourceTree = "<group>"; }; + 934BEC5410DFFA9600178102 /* pair3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair3.js; sourceTree = "<group>"; }; + 934BEC5510DFFA9600178102 /* pair4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair4.js; sourceTree = "<group>"; }; + 934BEC5610DFFA9600178102 /* pair5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair5.js; sourceTree = "<group>"; }; + 934BEC5710DFFA9600178102 /* pair6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pair6.js; sourceTree = "<group>"; }; + 934BEC5810DFFA9600178102 /* repl1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl1.js; sourceTree = "<group>"; }; + 934BEC5910DFFA9600178102 /* repl2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl2.js; sourceTree = "<group>"; }; + 934BEC5A10DFFA9600178102 /* repl3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl3.js; sourceTree = "<group>"; }; + 934BEC5B10DFFA9600178102 /* repl4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl4.js; sourceTree = "<group>"; }; + 934BEC5C10DFFA9600178102 /* repl5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl5.js; sourceTree = "<group>"; }; + 934BEC5D10DFFA9600178102 /* repl6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl6.js; sourceTree = "<group>"; }; + 934BEC5E10DFFA9600178102 /* repl7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl7.js; sourceTree = "<group>"; }; + 934BEC5F10DFFA9600178102 /* repl8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl8.js; sourceTree = "<group>"; }; + 934BEC6010DFFA9600178102 /* repl9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = repl9.js; sourceTree = "<group>"; }; + 934BEC6110DFFA9600178102 /* replacePeer1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = replacePeer1.js; sourceTree = "<group>"; }; + 934BEC6210DFFA9700178102 /* replacePeer2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = replacePeer2.js; sourceTree = "<group>"; }; + 934BEC6310DFFA9700178102 /* set1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = set1.js; sourceTree = "<group>"; }; + 934BEC6410DFFA9700178102 /* set2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = set2.js; sourceTree = "<group>"; }; + 934BEC6510DFFA9700178102 /* set3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = set3.js; sourceTree = "<group>"; }; + 934BEC6710DFFA9700178102 /* auto1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = auto1.js; sourceTree = "<group>"; }; + 934BEC6810DFFA9700178102 /* auto2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = auto2.js; sourceTree = "<group>"; }; + 934BEC6910DFFA9700178102 /* count1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = count1.js; sourceTree = "<group>"; }; + 934BEC6A10DFFA9700178102 /* diffservers1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = diffservers1.js; sourceTree = "<group>"; }; + 934BEC6B10DFFA9700178102 /* error1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = error1.js; sourceTree = "<group>"; }; + 934BEC6C10DFFA9700178102 /* features1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = features1.js; sourceTree = "<group>"; }; + 934BEC6D10DFFA9700178102 /* features2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = features2.js; sourceTree = "<group>"; }; + 934BEC6E10DFFA9700178102 /* key_many.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = key_many.js; sourceTree = "<group>"; }; + 934BEC6F10DFFA9700178102 /* key_string.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = key_string.js; sourceTree = "<group>"; }; + 934BEC7010DFFA9700178102 /* movePrimary1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = movePrimary1.js; sourceTree = "<group>"; }; + 934BEC7110DFFA9700178102 /* moveshard1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = moveshard1.js; sourceTree = "<group>"; }; + 934BEC7210DFFA9700178102 /* passthrough1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = passthrough1.js; sourceTree = "<group>"; }; + 934BEC7310DFFA9700178102 /* shard1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard1.js; sourceTree = "<group>"; }; + 934BEC7410DFFA9700178102 /* shard2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard2.js; sourceTree = "<group>"; }; + 934BEC7510DFFA9700178102 /* shard3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard3.js; sourceTree = "<group>"; }; + 934BEC7610DFFA9700178102 /* shard4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard4.js; sourceTree = "<group>"; }; + 934BEC7710DFFA9700178102 /* shard5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard5.js; sourceTree = "<group>"; }; + 934BEC7810DFFA9700178102 /* shard6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shard6.js; sourceTree = "<group>"; }; + 934BEC7910DFFA9700178102 /* splitpick.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = splitpick.js; sourceTree = "<group>"; }; + 934BEC7A10DFFA9700178102 /* version1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = version1.js; sourceTree = "<group>"; }; + 934BEC7B10DFFA9700178102 /* version2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = version2.js; sourceTree = "<group>"; }; + 934BEC7C10DFFA9700178102 /* shellspawn.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shellspawn.js; sourceTree = "<group>"; }; + 934BEC7D10DFFA9700178102 /* sort1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort1.js; sourceTree = "<group>"; }; + 934BEC7E10DFFA9700178102 /* sort2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort2.js; sourceTree = "<group>"; }; + 934BEC7F10DFFA9700178102 /* sort3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort3.js; sourceTree = "<group>"; }; + 934BEC8010DFFA9700178102 /* sort4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort4.js; sourceTree = "<group>"; }; + 934BEC8110DFFA9700178102 /* sort5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort5.js; sourceTree = "<group>"; }; + 934BEC8210DFFA9700178102 /* sort_numeric.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sort_numeric.js; sourceTree = "<group>"; }; + 934BEC8310DFFA9700178102 /* stats.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = stats.js; sourceTree = "<group>"; }; + 934BEC8410DFFA9700178102 /* storefunc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = storefunc.js; sourceTree = "<group>"; }; + 934BEC8510DFFA9700178102 /* sub1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sub1.js; sourceTree = "<group>"; }; + 934BEC8710DFFA9700178102 /* csv1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = csv1.js; sourceTree = "<group>"; }; + 934BEC8810DFFA9700178102 /* dumprestore1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dumprestore1.js; sourceTree = "<group>"; }; + 934BEC8910DFFA9700178102 /* dumprestore2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dumprestore2.js; sourceTree = "<group>"; }; + 934BEC8A10DFFA9700178102 /* exportimport1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = exportimport1.js; sourceTree = "<group>"; }; + 934BEC8B10DFFA9700178102 /* exportimport2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = exportimport2.js; sourceTree = "<group>"; }; + 934BEC8C10DFFA9700178102 /* tool1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = tool1.js; sourceTree = "<group>"; }; + 934BEC8D10DFFA9700178102 /* type1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = type1.js; sourceTree = "<group>"; }; + 934BEC8E10DFFA9700178102 /* unique2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = unique2.js; sourceTree = "<group>"; }; + 934BEC8F10DFFA9700178102 /* uniqueness.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = uniqueness.js; sourceTree = "<group>"; }; + 934BEC9010DFFA9700178102 /* unset.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = unset.js; sourceTree = "<group>"; }; + 934BEC9110DFFA9700178102 /* update.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update.js; sourceTree = "<group>"; }; + 934BEC9210DFFA9700178102 /* update2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update2.js; sourceTree = "<group>"; }; + 934BEC9310DFFA9700178102 /* update3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update3.js; sourceTree = "<group>"; }; + 934BEC9410DFFA9700178102 /* update4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update4.js; sourceTree = "<group>"; }; + 934BEC9510DFFA9700178102 /* update5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update5.js; sourceTree = "<group>"; }; + 934BEC9610DFFA9700178102 /* update6.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update6.js; sourceTree = "<group>"; }; + 934BEC9710DFFA9700178102 /* update7.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update7.js; sourceTree = "<group>"; }; + 934BEC9810DFFA9700178102 /* update8.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update8.js; sourceTree = "<group>"; }; + 934BEC9910DFFA9700178102 /* update9.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = update9.js; sourceTree = "<group>"; }; + 934BEC9A10DFFA9700178102 /* updatea.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = updatea.js; sourceTree = "<group>"; }; + 934BEC9B10DFFA9700178102 /* where1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = where1.js; sourceTree = "<group>"; }; + 934BEC9C10DFFA9700178102 /* where2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = where2.js; sourceTree = "<group>"; }; + 934BEE8C10E050A500178102 /* allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = allocator.h; sourceTree = "<group>"; }; + 934BEE8D10E050A500178102 /* assert_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = assert_util.cpp; sourceTree = "<group>"; }; + 934BEE8E10E050A500178102 /* assert_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = assert_util.h; sourceTree = "<group>"; }; + 934BEE8F10E050A500178102 /* base64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base64.cpp; sourceTree = "<group>"; }; + 934BEE9010E050A500178102 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = "<group>"; }; + 934BEE9110E050A500178102 /* debug_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug_util.cpp; sourceTree = "<group>"; }; + 934BEE9210E050A500178102 /* debug_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug_util.h; sourceTree = "<group>"; }; + 934BEE9310E050A500178102 /* embedded_builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = embedded_builder.h; sourceTree = "<group>"; }; + 934BEE9410E050A500178102 /* httpclient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = httpclient.cpp; sourceTree = "<group>"; }; + 934BEE9510E050A500178102 /* httpclient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = httpclient.h; sourceTree = "<group>"; }; + 934BEE9610E050A500178102 /* md5main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = md5main.cpp; sourceTree = "<group>"; }; + 934BEE9710E050A500178102 /* message_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = message_server.h; sourceTree = "<group>"; }; + 934BEE9810E050A500178102 /* message_server_asio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = message_server_asio.cpp; sourceTree = "<group>"; }; + 934BEE9910E050A500178102 /* mvar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mvar.h; sourceTree = "<group>"; }; + 934BEE9A10E050A500178102 /* ntservice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ntservice.cpp; sourceTree = "<group>"; }; + 934BEE9B10E050A500178102 /* ntservice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ntservice.h; sourceTree = "<group>"; }; + 934BEE9C10E050A500178102 /* processinfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = processinfo.h; sourceTree = "<group>"; }; + 934BEE9D10E050A500178102 /* processinfo_darwin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = processinfo_darwin.cpp; sourceTree = "<group>"; }; + 934BEE9E10E050A500178102 /* processinfo_linux2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = processinfo_linux2.cpp; sourceTree = "<group>"; }; + 934BEE9F10E050A500178102 /* processinfo_none.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = processinfo_none.cpp; sourceTree = "<group>"; }; + 934BEEA010E050A500178102 /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; }; + 934DD87C0EFAD23B00459CC1 /* background.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = background.cpp; sourceTree = "<group>"; }; + 934DD87D0EFAD23B00459CC1 /* background.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = background.h; sourceTree = "<group>"; }; + 934DD87F0EFAD23B00459CC1 /* builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builder.h; sourceTree = "<group>"; }; + 934DD8800EFAD23B00459CC1 /* goodies.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = goodies.h; sourceTree = "<group>"; }; + 934DD8810EFAD23B00459CC1 /* hashtab.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hashtab.h; sourceTree = "<group>"; }; + 934DD8820EFAD23B00459CC1 /* log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log.h; sourceTree = "<group>"; }; + 934DD8830EFAD23B00459CC1 /* lruishmap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lruishmap.h; sourceTree = "<group>"; }; + 934DD8840EFAD23B00459CC1 /* miniwebserver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = miniwebserver.cpp; sourceTree = "<group>"; }; + 934DD8850EFAD23B00459CC1 /* miniwebserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = miniwebserver.h; sourceTree = "<group>"; }; + 934DD8870EFAD23B00459CC1 /* mmap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mmap.cpp; sourceTree = "<group>"; }; + 934DD8880EFAD23B00459CC1 /* mmap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mmap.h; sourceTree = "<group>"; }; + 934DD88A0EFAD23B00459CC1 /* sock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sock.cpp; sourceTree = "<group>"; }; + 934DD88B0EFAD23B00459CC1 /* sock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sock.h; sourceTree = "<group>"; }; + 934DD88D0EFAD23B00459CC1 /* unittest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unittest.h; sourceTree = "<group>"; }; + 934DD88E0EFAD23B00459CC1 /* util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = util.cpp; sourceTree = "<group>"; }; + 935C941B1106709800439EB1 /* preallocate.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = preallocate.js; sourceTree = "<group>"; }; + 936B89590F4C899400934AF2 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file.h; sourceTree = "<group>"; }; + 936B895A0F4C899400934AF2 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5.c; sourceTree = "<group>"; }; + 936B895B0F4C899400934AF2 /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; }; + 936B895C0F4C899400934AF2 /* md5.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = md5.hpp; sourceTree = "<group>"; }; + 936B895D0F4C899400934AF2 /* md5main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5main.c; sourceTree = "<group>"; }; + 936B895E0F4C899400934AF2 /* message.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = message.cpp; sourceTree = "<group>"; }; + 936B895F0F4C899400934AF2 /* message.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = message.h; sourceTree = "<group>"; }; + 936B89600F4C899400934AF2 /* top.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = top.h; sourceTree = "<group>"; }; + 937CACE90F27BF4900C57AA6 /* socktests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = socktests.cpp; sourceTree = "<group>"; }; + 937D0E340F28CB070071FFA9 /* repltests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = repltests.cpp; sourceTree = "<group>"; }; + 937D14AB0F2A225F0071FFA9 /* nonce.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nonce.h; sourceTree = "<group>"; }; + 937D14AC0F2A226E0071FFA9 /* nonce.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = nonce.cpp; sourceTree = "<group>"; }; + 938A7A420F54871000FB7A07 /* storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = storage.cpp; sourceTree = "<group>"; }; + 938A7A430F54873600FB7A07 /* concurrency.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = concurrency.h; sourceTree = "<group>"; }; + 938A7A440F54873600FB7A07 /* queryutil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = queryutil.cpp; sourceTree = "<group>"; }; + 938A7A450F54873600FB7A07 /* queryutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queryutil.h; sourceTree = "<group>"; }; + 938A7A460F54873600FB7A07 /* rec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rec.h; sourceTree = "<group>"; }; + 938A7A470F54873600FB7A07 /* reccache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reccache.cpp; sourceTree = "<group>"; }; + 938A7A480F54873600FB7A07 /* reccache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reccache.h; sourceTree = "<group>"; }; + 938A7A490F54873600FB7A07 /* reci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reci.h; sourceTree = "<group>"; }; + 938A7A4A0F54873600FB7A07 /* recstore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = recstore.h; sourceTree = "<group>"; }; + 93A13A210F4620A500AF1B0D /* commands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = commands.cpp; sourceTree = "<group>"; }; + 93A13A230F4620A500AF1B0D /* config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = config.cpp; sourceTree = "<group>"; }; + 93A13A240F4620A500AF1B0D /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; }; + 93A13A270F4620A500AF1B0D /* request.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = request.cpp; sourceTree = "<group>"; }; + 93A13A280F4620A500AF1B0D /* request.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = request.h; sourceTree = "<group>"; }; + 93A13A2A0F4620A500AF1B0D /* server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = server.cpp; sourceTree = "<group>"; }; + 93A13A2B0F4620A500AF1B0D /* server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server.h; sourceTree = "<group>"; }; + 93A13A2D0F4620A500AF1B0D /* shard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shard.cpp; sourceTree = "<group>"; }; + 93A13A2E0F4620A500AF1B0D /* shard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shard.h; sourceTree = "<group>"; }; + 93A13A300F4620A500AF1B0D /* strategy_single.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = strategy_single.cpp; sourceTree = "<group>"; }; + 93A13A330F4620E500AF1B0D /* dump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dump.cpp; sourceTree = "<group>"; }; + 93A13A350F4620E500AF1B0D /* export.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = export.cpp; sourceTree = "<group>"; }; + 93A13A370F4620E500AF1B0D /* files.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = files.cpp; sourceTree = "<group>"; }; + 93A13A390F4620E500AF1B0D /* importJSON.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = importJSON.cpp; sourceTree = "<group>"; }; + 93A13A3B0F4620E500AF1B0D /* restore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = restore.cpp; sourceTree = "<group>"; }; + 93A13A3D0F4620E500AF1B0D /* sniffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sniffer.cpp; sourceTree = "<group>"; }; + 93A13A3F0F4620E500AF1B0D /* Tool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tool.cpp; sourceTree = "<group>"; }; + 93A13A400F4620E500AF1B0D /* Tool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tool.h; sourceTree = "<group>"; }; + 93A479F30FAF2A5000E760DD /* engine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engine.cpp; sourceTree = "<group>"; }; + 93A479F40FAF2A5000E760DD /* engine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine.h; sourceTree = "<group>"; }; + 93A479F60FAF2A5000E760DD /* engine_java.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engine_java.cpp; sourceTree = "<group>"; }; + 93A479F70FAF2A5000E760DD /* engine_java.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine_java.h; sourceTree = "<group>"; }; + 93A479F90FAF2A5000E760DD /* engine_none.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engine_none.cpp; sourceTree = "<group>"; }; + 93A479FA0FAF2A5000E760DD /* engine_spidermonkey.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engine_spidermonkey.cpp; sourceTree = "<group>"; }; + 93A47AA50FAF416F00E760DD /* engine_v8.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engine_v8.cpp; sourceTree = "<group>"; }; + 93A47AA60FAF41B200E760DD /* engine_v8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine_v8.h; sourceTree = "<group>"; }; + 93A6E10C0F24CF9800DA4EBF /* lasterror.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lasterror.h; sourceTree = "<group>"; }; + 93A6E10D0F24CFB100DA4EBF /* flushtest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = flushtest.cpp; sourceTree = "<group>"; }; + 93A6E10E0F24CFD300DA4EBF /* security.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = security.h; sourceTree = "<group>"; }; + 93A6E10F0F24CFEA00DA4EBF /* security_commands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = security_commands.cpp; sourceTree = "<group>"; }; + 93A71DB610D06CAD003C9E90 /* mr.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mr.js; sourceTree = "<group>"; }; + 93A8CD170F33B78D00C92B85 /* mmap_mm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mmap_mm.cpp; path = util/mmap_mm.cpp; sourceTree = SOURCE_ROOT; }; + 93A8CD180F33B7A000C92B85 /* mmap_posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mmap_posix.cpp; path = util/mmap_posix.cpp; sourceTree = SOURCE_ROOT; }; + 93A8CD190F33B7AF00C92B85 /* mmap_win.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mmap_win.cpp; path = util/mmap_win.cpp; sourceTree = SOURCE_ROOT; }; + 93AEC57A10E94749005DF720 /* insert.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = insert.js; sourceTree = "<group>"; }; + 93AF75500F216D0300994C66 /* jsontests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsontests.cpp; sourceTree = "<group>"; }; + 93B4A81A0F1C01B4000C862C /* security.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = security.cpp; sourceTree = "<group>"; }; + 93B4A81B0F1C01D8000C862C /* lasterror.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lasterror.cpp; sourceTree = "<group>"; }; + 93B4A8290F1C024C000C862C /* cursor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cursor.cpp; sourceTree = "<group>"; }; + 93B4A82A0F1C0256000C862C /* pdfiletests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pdfiletests.cpp; sourceTree = "<group>"; }; + 93BC2AE10FB87662006BC285 /* cursortests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cursortests.cpp; sourceTree = "<group>"; }; + 93BC2AE20FB87662006BC285 /* jstests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jstests.cpp; sourceTree = "<group>"; }; + 93BCE15610F25DFE00FA139B /* arrayfind1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = arrayfind1.js; sourceTree = "<group>"; }; + 93BCE15710F25DFE00FA139B /* dbadmin.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = dbadmin.js; sourceTree = "<group>"; }; + 93BCE15810F25DFE00FA139B /* error4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = error4.js; sourceTree = "<group>"; }; + 93BCE15910F25DFE00FA139B /* find_and_modify.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = find_and_modify.js; sourceTree = "<group>"; }; + 93BCE15A10F25DFE00FA139B /* fsync.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = fsync.js; sourceTree = "<group>"; }; + 93BCE15B10F25DFE00FA139B /* regex5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = regex5.js; sourceTree = "<group>"; }; + 93BCE15C10F25DFE00FA139B /* rename3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = rename3.js; sourceTree = "<group>"; }; + 93BCE16010F2642900FA139B /* database.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = database.cpp; path = db/database.cpp; sourceTree = "<group>"; }; + 93BCE16110F2642900FA139B /* dbcommands_admin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dbcommands_admin.cpp; path = db/dbcommands_admin.cpp; sourceTree = "<group>"; }; + 93BCE1D310F26CDA00FA139B /* fsync2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = fsync2.js; sourceTree = "<group>"; }; + 93BCE35F10F2BD8300FA139B /* clienttests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = clienttests.cpp; sourceTree = "<group>"; }; + 93BCE36010F2BD8300FA139B /* framework.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = framework.cpp; sourceTree = "<group>"; }; + 93BCE36110F2BD8300FA139B /* framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = framework.h; sourceTree = "<group>"; }; + 93BCE36210F2BD8300FA139B /* threadedtests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = threadedtests.cpp; sourceTree = "<group>"; }; + 93BCE36310F2BD8300FA139B /* updatetests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = updatetests.cpp; sourceTree = "<group>"; }; + 93BCE41810F3AF1B00FA139B /* capped2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = capped2.js; sourceTree = "<group>"; }; + 93BCE4B510F3C8DB00FA139B /* allops.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = allops.js; sourceTree = "<group>"; }; + 93BCE5A510F3F8E900FA139B /* manyclients.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = manyclients.js; sourceTree = "<group>"; }; + 93BCE5A610F3FB5200FA139B /* basicPlus.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basicPlus.js; sourceTree = "<group>"; }; + 93C38E940FA66622007D6E4A /* basictests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = basictests.cpp; sourceTree = "<group>"; }; + 93D0C1520EF1D377005253B7 /* jsobjtests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsobjtests.cpp; sourceTree = "<group>"; }; + 93D0C1FB0EF1E267005253B7 /* namespacetests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = namespacetests.cpp; sourceTree = "<group>"; }; + 93D6BBF70F265E1100FE5722 /* matchertests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = matchertests.cpp; sourceTree = "<group>"; }; + 93D6BC9B0F266FC300FE5722 /* querytests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = querytests.cpp; sourceTree = "<group>"; }; + 93DCDBD30F9515AF005349BC /* file_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_allocator.h; sourceTree = "<group>"; }; + 93E5B88710D7FF730044F9E4 /* mongo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mongo.cpp; sourceTree = "<group>"; }; + 93E5B88810D7FF730044F9E4 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = "<group>"; }; + 93E5B88910D7FF890044F9E4 /* engine_spidermonkey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine_spidermonkey.h; sourceTree = "<group>"; }; + 93E5B88A10D7FF890044F9E4 /* v8_db.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = v8_db.cpp; sourceTree = "<group>"; }; + 93E5B88B10D7FF890044F9E4 /* v8_db.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v8_db.h; sourceTree = "<group>"; }; + 93E5B88C10D7FF890044F9E4 /* v8_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = v8_utils.cpp; sourceTree = "<group>"; }; + 93E5B88D10D7FF890044F9E4 /* v8_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v8_utils.h; sourceTree = "<group>"; }; + 93E5B88E10D7FF890044F9E4 /* v8_wrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = v8_wrapper.cpp; sourceTree = "<group>"; }; + 93E5B88F10D7FF890044F9E4 /* v8_wrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = v8_wrapper.h; sourceTree = "<group>"; }; + 93E727090F4B5B5B004F9B5D /* shardkey.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shardkey.cpp; sourceTree = "<group>"; }; + 93E7270A0F4B5B5B004F9B5D /* shardkey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shardkey.h; sourceTree = "<group>"; }; + 93F0957010E165E50053380C /* basic.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = basic.js; sourceTree = "<group>"; }; + 93F095CC10E16FF70053380C /* shellfork.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shellfork.js; sourceTree = "<group>"; }; + C6859E8B029090EE04C91782 /* mongo.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = mongo.1; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* mongo */ = { + isa = PBXGroup; + children = ( + 08FB7795FE84155DC02AAC07 /* Source */, + C6859E8C029090F304C91782 /* Documentation */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = mongo; + sourceTree = "<group>"; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 93BCE16010F2642900FA139B /* database.cpp */, + 93BCE16110F2642900FA139B /* dbcommands_admin.cpp */, + 934BEB9A10DFFA9600178102 /* jstests */, + 93A479F20FAF2A5000E760DD /* scripting */, + 932AC4310F4A5E9D005BF8B0 /* SConstruct */, + 93A13A320F4620E500AF1B0D /* tools */, + 93A13A200F4620A500AF1B0D /* s */, + 9302D9920F30AB8C00DFA4EF /* shell */, + 9342232A0EF16D4F00608550 /* client */, + 9342238F0EF16DB400608550 /* db */, + 934223850EF16D7000608550 /* dbtests */, + 934DD87B0EFAD23B00459CC1 /* util */, + ); + name = Source; + sourceTree = "<group>"; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + ); + name = Products; + sourceTree = "<group>"; + }; + 9302D9920F30AB8C00DFA4EF /* shell */ = { + isa = PBXGroup; + children = ( + 93E5B88710D7FF730044F9E4 /* mongo.cpp */, + 93E5B88810D7FF730044F9E4 /* utils.h */, + 93A71DB610D06CAD003C9E90 /* mr.js */, + 931979810FBC67FB001FE537 /* utils.cpp */, + 9302D9930F30AB8C00DFA4EF /* collection.js */, + 9302D9940F30AB8C00DFA4EF /* db.js */, + 9302D9950F30AB8C00DFA4EF /* dbshell.cpp */, + 9302D9980F30AB8C00DFA4EF /* mongo.js */, + 9302D9990F30AB8C00DFA4EF /* mongo.jsall */, + 9302D99E0F30AB8C00DFA4EF /* query.js */, + 9302D9A20F30AB8C00DFA4EF /* utils.js */, + ); + path = shell; + sourceTree = "<group>"; + }; + 9303D1B410E1415C00294FAC /* modules */ = { + isa = PBXGroup; + children = ( + 9303D1B510E1415C00294FAC /* mms.cpp */, + 9303D1B610E1415C00294FAC /* mms.o */, + ); + path = modules; + sourceTree = "<group>"; + }; + 933A4D120F55A68600145C4B /* examples */ = { + isa = PBXGroup; + children = ( + 933A4D130F55A68600145C4B /* authTest.cpp */, + 933A4D150F55A68600145C4B /* clientTest.cpp */, + 933A4D170F55A68600145C4B /* first.cpp */, + 933A4D190F55A68600145C4B /* second.cpp */, + 933A4D1B0F55A68600145C4B /* tail.cpp */, + 933A4D1C0F55A68600145C4B /* tutorial.cpp */, + 933A4D1D0F55A68600145C4B /* whereExample.cpp */, + ); + path = examples; + sourceTree = "<group>"; + }; + 933E22100F4327B2000209E3 /* perf */ = { + isa = PBXGroup; + children = ( + 933E22110F4327B2000209E3 /* perftest.cpp */, + 933E22120F4327B2000209E3 /* perftest.o */, + ); + path = perf; + sourceTree = "<group>"; + }; + 9342232A0EF16D4F00608550 /* client */ = { + isa = PBXGroup; + children = ( + 93278F570F72D32900844664 /* gridfs.cpp */, + 93278F580F72D32900844664 /* gridfs.h */, + 933A4D120F55A68600145C4B /* examples */, + 9342232B0EF16D4F00608550 /* connpool.cpp */, + 9342232C0EF16D4F00608550 /* connpool.h */, + 9342232D0EF16D4F00608550 /* dbclient.cpp */, + 9342232E0EF16D4F00608550 /* dbclient.h */, + 934223300EF16D4F00608550 /* model.cpp */, + 934223310EF16D4F00608550 /* model.h */, + ); + path = client; + sourceTree = "<group>"; + }; + 934223850EF16D7000608550 /* dbtests */ = { + isa = PBXGroup; + children = ( + 93BCE35F10F2BD8300FA139B /* clienttests.cpp */, + 93BCE36010F2BD8300FA139B /* framework.cpp */, + 93BCE36110F2BD8300FA139B /* framework.h */, + 93BCE36210F2BD8300FA139B /* threadedtests.cpp */, + 93BCE36310F2BD8300FA139B /* updatetests.cpp */, + 93BC2AE10FB87662006BC285 /* cursortests.cpp */, + 93BC2AE20FB87662006BC285 /* jstests.cpp */, + 93C38E940FA66622007D6E4A /* basictests.cpp */, + 932AC3EB0F4A5B34005BF8B0 /* queryoptimizertests.cpp */, + 933E22100F4327B2000209E3 /* perf */, + 937D0E340F28CB070071FFA9 /* repltests.cpp */, + 937CACE90F27BF4900C57AA6 /* socktests.cpp */, + 93D6BC9B0F266FC300FE5722 /* querytests.cpp */, + 93D6BBF70F265E1100FE5722 /* matchertests.cpp */, + 93AF75500F216D0300994C66 /* jsontests.cpp */, + 93B4A82A0F1C0256000C862C /* pdfiletests.cpp */, + 93D0C1FB0EF1E267005253B7 /* namespacetests.cpp */, + 93D0C1520EF1D377005253B7 /* jsobjtests.cpp */, + 934223860EF16D7000608550 /* btreetests.cpp */, + 934223880EF16D7000608550 /* dbtests.cpp */, + 934223890EF16D7000608550 /* dbtests.h */, + 9342238C0EF16D7000608550 /* mockdbclient.h */, + 9342238D0EF16D7000608550 /* pairingtests.cpp */, + ); + path = dbtests; + sourceTree = "<group>"; + }; + 9342238F0EF16DB400608550 /* db */ = { + isa = PBXGroup; + children = ( + 9303D1AB10E1415C00294FAC /* client.cpp */, + 9303D1AC10E1415C00294FAC /* client.h */, + 9303D1AD10E1415C00294FAC /* cmdline.h */, + 9303D1AE10E1415C00294FAC /* curop.h */, + 9303D1AF10E1415C00294FAC /* extsort.cpp */, + 9303D1B010E1415C00294FAC /* extsort.h */, + 9303D1B110E1415C00294FAC /* filever.h */, + 9303D1B210E1415C00294FAC /* module.cpp */, + 9303D1B310E1415C00294FAC /* module.h */, + 9303D1B410E1415C00294FAC /* modules */, + 9303D1B710E1415C00294FAC /* mr.cpp */, + 9303D1B810E1415C00294FAC /* update.cpp */, + 9303D1B910E1415C00294FAC /* update.h */, + 931A027A0F58AA4400147C0E /* jsobjmanipulator.h */, + 938A7A430F54873600FB7A07 /* concurrency.h */, + 938A7A440F54873600FB7A07 /* queryutil.cpp */, + 938A7A450F54873600FB7A07 /* queryutil.h */, + 938A7A460F54873600FB7A07 /* rec.h */, + 938A7A470F54873600FB7A07 /* reccache.cpp */, + 938A7A480F54873600FB7A07 /* reccache.h */, + 938A7A490F54873600FB7A07 /* reci.h */, + 938A7A4A0F54873600FB7A07 /* recstore.h */, + 938A7A420F54871000FB7A07 /* storage.cpp */, + 93A8CD190F33B7AF00C92B85 /* mmap_win.cpp */, + 93A8CD180F33B7A000C92B85 /* mmap_posix.cpp */, + 93A8CD170F33B78D00C92B85 /* mmap_mm.cpp */, + 937D14AC0F2A226E0071FFA9 /* nonce.cpp */, + 937D14AB0F2A225F0071FFA9 /* nonce.h */, + 93A6E10F0F24CFEA00DA4EBF /* security_commands.cpp */, + 93A6E10E0F24CFD300DA4EBF /* security.h */, + 93A6E10D0F24CFB100DA4EBF /* flushtest.cpp */, + 93A6E10C0F24CF9800DA4EBF /* lasterror.h */, + 93B4A8290F1C024C000C862C /* cursor.cpp */, + 93B4A81B0F1C01D8000C862C /* lasterror.cpp */, + 93B4A81A0F1C01B4000C862C /* security.cpp */, + 934223900EF16DB400608550 /* btree.cpp */, + 934223910EF16DB400608550 /* btree.h */, + 934223920EF16DB400608550 /* btreecursor.cpp */, + 934223930EF16DB400608550 /* clientcursor.cpp */, + 934223940EF16DB400608550 /* clientcursor.h */, + 934223950EF16DB400608550 /* cloner.cpp */, + 934223960EF16DB400608550 /* commands.cpp */, + 934223970EF16DB400608550 /* commands.h */, + 934223980EF16DB400608550 /* cursor.h */, + 934223990EF16DB400608550 /* database.h */, + 9342239A0EF16DB400608550 /* db.cpp */, + 9342239B0EF16DB400608550 /* db.h */, + 9342239F0EF16DB400608550 /* dbcommands.cpp */, + 934223A00EF16DB400608550 /* dbeval.cpp */, + 934223A10EF16DB400608550 /* dbhelpers.cpp */, + 934223A20EF16DB400608550 /* dbhelpers.h */, + 934223A30EF16DB400608550 /* dbinfo.cpp */, + 934223A40EF16DB400608550 /* dbinfo.h */, + 934223A50EF16DB400608550 /* dbmessage.h */, + 934223A60EF16DB400608550 /* dbwebserver.cpp */, + 934223A70EF16DB400608550 /* instance.cpp */, + 934223A80EF16DB400608550 /* instance.h */, + 934223A90EF16DB400608550 /* introspect.cpp */, + 934223AA0EF16DB400608550 /* introspect.h */, + 934223AD0EF16DB400608550 /* javatest.cpp */, + 934223AE0EF16DB400608550 /* jsobj.cpp */, + 934223AF0EF16DB400608550 /* jsobj.h */, + 934223B00EF16DB400608550 /* json.cpp */, + 934223B10EF16DB400608550 /* json.h */, + 934223B70EF16DB400608550 /* matcher.cpp */, + 934223B80EF16DB400608550 /* matcher.h */, + 934223B90EF16DB400608550 /* minilex.h */, + 934223BA0EF16DB400608550 /* namespace.cpp */, + 934223BB0EF16DB400608550 /* namespace.h */, + 934223BD0EF16DB400608550 /* pdfile.cpp */, + 934223BE0EF16DB400608550 /* pdfile.h */, + 934223BF0EF16DB400608550 /* query.cpp */, + 934223C00EF16DB400608550 /* query.h */, + 934223C10EF16DB400608550 /* queryoptimizer.cpp */, + 934223C20EF16DB400608550 /* queryoptimizer.h */, + 934223C30EF16DB400608550 /* repl.cpp */, + 934223C40EF16DB400608550 /* repl.h */, + 934223C50EF16DB400608550 /* replset.h */, + 934223C60EF16DB400608550 /* resource.h */, + 934223C70EF16DB400608550 /* scanandorder.h */, + 934223C80EF16DB400608550 /* storage.h */, + 934223C90EF16DB400608550 /* tests.cpp */, + ); + path = db; + sourceTree = "<group>"; + }; + 934BEB9A10DFFA9600178102 /* jstests */ = { + isa = PBXGroup; + children = ( + 93BCE41810F3AF1B00FA139B /* capped2.js */, + 93BCE1D310F26CDA00FA139B /* fsync2.js */, + 93BCE15610F25DFE00FA139B /* arrayfind1.js */, + 93BCE15710F25DFE00FA139B /* dbadmin.js */, + 93BCE15810F25DFE00FA139B /* error4.js */, + 93BCE15910F25DFE00FA139B /* find_and_modify.js */, + 93BCE15A10F25DFE00FA139B /* fsync.js */, + 93BCE15B10F25DFE00FA139B /* regex5.js */, + 93BCE15C10F25DFE00FA139B /* rename3.js */, + 93F0956F10E165E50053380C /* parallel */, + 934BEB9B10DFFA9600178102 /* _lodeRunner.js */, + 934BEB9C10DFFA9600178102 /* _runner.js */, + 934BEB9D10DFFA9600178102 /* _runner_leak.js */, + 934BEB9E10DFFA9600178102 /* _runner_leak_nojni.js */, + 934BEB9F10DFFA9600178102 /* _runner_sharding.js */, + 934BEBA010DFFA9600178102 /* all.js */, + 934BEBA110DFFA9600178102 /* all2.js */, + 934BEBA210DFFA9600178102 /* apitest_db.js */, + 934BEBA310DFFA9600178102 /* apitest_dbcollection.js */, + 934BEBA410DFFA9600178102 /* array1.js */, + 934BEBA510DFFA9600178102 /* array3.js */, + 934BEBA610DFFA9600178102 /* auth1.js */, + 934BEBA710DFFA9600178102 /* auth2.js */, + 934BEBA810DFFA9600178102 /* autoid.js */, + 934BEBA910DFFA9600178102 /* basic1.js */, + 934BEBAA10DFFA9600178102 /* basic2.js */, + 934BEBAB10DFFA9600178102 /* basic3.js */, + 934BEBAC10DFFA9600178102 /* basic4.js */, + 934BEBAD10DFFA9600178102 /* basic5.js */, + 934BEBAE10DFFA9600178102 /* basic6.js */, + 934BEBAF10DFFA9600178102 /* basic7.js */, + 934BEBB010DFFA9600178102 /* basic8.js */, + 934BEBB110DFFA9600178102 /* basic9.js */, + 934BEBB210DFFA9600178102 /* basica.js */, + 934BEBB310DFFA9600178102 /* basicb.js */, + 934BEBB410DFFA9600178102 /* capped.js */, + 934BEBB510DFFA9600178102 /* capped1.js */, + 934BEBB610DFFA9600178102 /* btree.h */, + 934BEBB710DFFA9600178102 /* capped3.js */, + 934BEBB810DFFA9600178102 /* capped4.js */, + 934BEBB910DFFA9600178102 /* clone */, + 934BEBBB10DFFA9600178102 /* copydb.js */, + 934BEBBC10DFFA9600178102 /* count.js */, + 934BEBBD10DFFA9600178102 /* count2.js */, + 934BEBBE10DFFA9600178102 /* count3.js */, + 934BEBBF10DFFA9600178102 /* count4.js */, + 934BEBC010DFFA9600178102 /* count5.js */, + 934BEBC110DFFA9600178102 /* cursor1.js */, + 934BEBC210DFFA9600178102 /* cursor2.js */, + 934BEBC310DFFA9600178102 /* cursor3.js */, + 934BEBC410DFFA9600178102 /* cursor4.js */, + 934BEBC510DFFA9600178102 /* cursor5.js */, + 934BEBC610DFFA9600178102 /* cursor6.js */, + 934BEBC710DFFA9600178102 /* cursor7.js */, + 934BEBC810DFFA9600178102 /* cursor8.js */, + 934BEBC910DFFA9600178102 /* datasize.js */, + 934BEBCA10DFFA9600178102 /* date1.js */, + 934BEBCB10DFFA9600178102 /* dbref1.js */, + 934BEBCC10DFFA9600178102 /* dbref2.js */, + 934BEBCD10DFFA9600178102 /* disk */, + 934BEBD110DFFA9600178102 /* distinct1.js */, + 934BEBD210DFFA9600178102 /* distinct2.js */, + 934BEBD310DFFA9600178102 /* drop.js */, + 934BEBD410DFFA9600178102 /* error1.js */, + 934BEBD510DFFA9600178102 /* error2.js */, + 934BEBD610DFFA9600178102 /* error3.js */, + 934BEBD710DFFA9600178102 /* eval0.js */, + 934BEBD810DFFA9600178102 /* eval1.js */, + 934BEBD910DFFA9600178102 /* eval2.js */, + 934BEBDA10DFFA9600178102 /* eval3.js */, + 934BEBDB10DFFA9600178102 /* eval4.js */, + 934BEBDC10DFFA9600178102 /* eval5.js */, + 934BEBDD10DFFA9600178102 /* eval6.js */, + 934BEBDE10DFFA9600178102 /* eval7.js */, + 934BEBDF10DFFA9600178102 /* eval8.js */, + 934BEBE010DFFA9600178102 /* eval9.js */, + 934BEBE110DFFA9600178102 /* evala.js */, + 934BEBE210DFFA9600178102 /* evalb.js */, + 934BEBE310DFFA9600178102 /* exists.js */, + 934BEBE410DFFA9600178102 /* explain1.js */, + 934BEBE510DFFA9600178102 /* find1.js */, + 934BEBE610DFFA9600178102 /* find2.js */, + 934BEBE710DFFA9600178102 /* find3.js */, + 934BEBE810DFFA9600178102 /* find4.js */, + 934BEBE910DFFA9600178102 /* find5.js */, + 934BEBEA10DFFA9600178102 /* find6.js */, + 934BEBEB10DFFA9600178102 /* fm1.js */, + 934BEBEC10DFFA9600178102 /* fm2.js */, + 934BEBED10DFFA9600178102 /* fm3.js */, + 934BEBEE10DFFA9600178102 /* group1.js */, + 934BEBEF10DFFA9600178102 /* group2.js */, + 934BEBF010DFFA9600178102 /* group3.js */, + 934BEBF110DFFA9600178102 /* group4.js */, + 934BEBF210DFFA9600178102 /* group5.js */, + 934BEBF310DFFA9600178102 /* hint1.js */, + 934BEBF410DFFA9600178102 /* id1.js */, + 934BEBF510DFFA9600178102 /* in.js */, + 934BEBF610DFFA9600178102 /* in2.js */, + 934BEBF710DFFA9600178102 /* inc1.js */, + 934BEBF810DFFA9600178102 /* inc2.js */, + 934BEBF910DFFA9600178102 /* inc3.js */, + 934BEBFA10DFFA9600178102 /* index1.js */, + 934BEBFB10DFFA9600178102 /* index10.js */, + 934BEBFC10DFFA9600178102 /* index2.js */, + 934BEBFD10DFFA9600178102 /* index3.js */, + 934BEBFE10DFFA9600178102 /* index4.js */, + 934BEBFF10DFFA9600178102 /* index5.js */, + 934BEC0010DFFA9600178102 /* index6.js */, + 934BEC0110DFFA9600178102 /* index7.js */, + 934BEC0210DFFA9600178102 /* index8.js */, + 934BEC0310DFFA9600178102 /* index9.js */, + 934BEC0410DFFA9600178102 /* index_check1.js */, + 934BEC0510DFFA9600178102 /* index_check2.js */, + 934BEC0610DFFA9600178102 /* index_check3.js */, + 934BEC0710DFFA9600178102 /* index_check5.js */, + 934BEC0810DFFA9600178102 /* index_check6.js */, + 934BEC0910DFFA9600178102 /* index_check7.js */, + 934BEC0A10DFFA9600178102 /* index_many.js */, + 934BEC0B10DFFA9600178102 /* indexa.js */, + 934BEC0C10DFFA9600178102 /* indexapi.js */, + 934BEC0D10DFFA9600178102 /* indexb.js */, + 934BEC0E10DFFA9600178102 /* indexc.js */, + 934BEC0F10DFFA9600178102 /* indexd.js */, + 934BEC1010DFFA9600178102 /* indexe.js */, + 934BEC1110DFFA9600178102 /* jni1.js */, + 934BEC1210DFFA9600178102 /* jni2.js */, + 934BEC1310DFFA9600178102 /* jni3.js */, + 934BEC1410DFFA9600178102 /* jni4.js */, + 934BEC1510DFFA9600178102 /* jni5.js */, + 934BEC1610DFFA9600178102 /* jni7.js */, + 934BEC1710DFFA9600178102 /* jni8.js */, + 934BEC1810DFFA9600178102 /* jni9.js */, + 934BEC1910DFFA9600178102 /* json1.js */, + 934BEC1A10DFFA9600178102 /* map1.js */, + 934BEC1B10DFFA9600178102 /* median.js */, + 934BEC1C10DFFA9600178102 /* minmax.js */, + 934BEC1D10DFFA9600178102 /* mod1.js */, + 934BEC1E10DFFA9600178102 /* mr1.js */, + 934BEC1F10DFFA9600178102 /* mr2.js */, + 934BEC2010DFFA9600178102 /* mr3.js */, + 934BEC2110DFFA9600178102 /* mr4.js */, + 934BEC2210DFFA9600178102 /* mr5.js */, + 934BEC2310DFFA9600178102 /* multi.js */, + 934BEC2410DFFA9600178102 /* multi2.js */, + 934BEC2510DFFA9600178102 /* ne1.js */, + 934BEC2610DFFA9600178102 /* nin.js */, + 934BEC2710DFFA9600178102 /* not1.js */, + 934BEC2810DFFA9600178102 /* null.js */, + 934BEC2910DFFA9600178102 /* objid1.js */, + 934BEC2A10DFFA9600178102 /* objid2.js */, + 934BEC2B10DFFA9600178102 /* objid3.js */, + 934BEC2C10DFFA9600178102 /* objid4.js */, + 934BEC2D10DFFA9600178102 /* objid5.js */, + 934BEC2E10DFFA9600178102 /* perf */, + 934BEC3210DFFA9600178102 /* profile1.js */, + 934BEC3310DFFA9600178102 /* pull.js */, + 934BEC3410DFFA9600178102 /* pull2.js */, + 934BEC3510DFFA9600178102 /* pullall.js */, + 934BEC3610DFFA9600178102 /* push.js */, + 934BEC3710DFFA9600178102 /* pushall.js */, + 934BEC3810DFFA9600178102 /* query1.js */, + 934BEC3910DFFA9600178102 /* queryoptimizer1.js */, + 934BEC3A10DFFA9600178102 /* quota */, + 934BEC3C10DFFA9600178102 /* recstore.js */, + 934BEC3D10DFFA9600178102 /* ref.js */, + 934BEC3E10DFFA9600178102 /* ref2.js */, + 934BEC3F10DFFA9600178102 /* ref3.js */, + 934BEC4010DFFA9600178102 /* ref4.js */, + 934BEC4110DFFA9600178102 /* regex.js */, + 934BEC4210DFFA9600178102 /* regex2.js */, + 934BEC4310DFFA9600178102 /* regex3.js */, + 934BEC4410DFFA9600178102 /* regex4.js */, + 934BEC4510DFFA9600178102 /* remove.js */, + 934BEC4610DFFA9600178102 /* remove2.js */, + 934BEC4710DFFA9600178102 /* remove3.js */, + 934BEC4810DFFA9600178102 /* remove4.js */, + 934BEC4910DFFA9600178102 /* remove5.js */, + 934BEC4A10DFFA9600178102 /* remove6.js */, + 934BEC4B10DFFA9600178102 /* remove7.js */, + 934BEC4C10DFFA9600178102 /* remove8.js */, + 934BEC4D10DFFA9600178102 /* rename.js */, + 934BEC4E10DFFA9600178102 /* rename2.js */, + 934BEC4F10DFFA9600178102 /* repair.js */, + 934BEC5010DFFA9600178102 /* repl */, + 934BEC6310DFFA9700178102 /* set1.js */, + 934BEC6410DFFA9700178102 /* set2.js */, + 934BEC6510DFFA9700178102 /* set3.js */, + 934BEC6610DFFA9700178102 /* sharding */, + 934BEC7C10DFFA9700178102 /* shellspawn.js */, + 934BEC7D10DFFA9700178102 /* sort1.js */, + 934BEC7E10DFFA9700178102 /* sort2.js */, + 934BEC7F10DFFA9700178102 /* sort3.js */, + 934BEC8010DFFA9700178102 /* sort4.js */, + 934BEC8110DFFA9700178102 /* sort5.js */, + 934BEC8210DFFA9700178102 /* sort_numeric.js */, + 934BEC8310DFFA9700178102 /* stats.js */, + 934BEC8410DFFA9700178102 /* storefunc.js */, + 934BEC8510DFFA9700178102 /* sub1.js */, + 934BEC8610DFFA9700178102 /* tool */, + 934BEC8D10DFFA9700178102 /* type1.js */, + 934BEC8E10DFFA9700178102 /* unique2.js */, + 934BEC8F10DFFA9700178102 /* uniqueness.js */, + 934BEC9010DFFA9700178102 /* unset.js */, + 934BEC9110DFFA9700178102 /* update.js */, + 934BEC9210DFFA9700178102 /* update2.js */, + 934BEC9310DFFA9700178102 /* update3.js */, + 934BEC9410DFFA9700178102 /* update4.js */, + 934BEC9510DFFA9700178102 /* update5.js */, + 934BEC9610DFFA9700178102 /* update6.js */, + 934BEC9710DFFA9700178102 /* update7.js */, + 934BEC9810DFFA9700178102 /* update8.js */, + 934BEC9910DFFA9700178102 /* update9.js */, + 934BEC9A10DFFA9700178102 /* updatea.js */, + 934BEC9B10DFFA9700178102 /* where1.js */, + 934BEC9C10DFFA9700178102 /* where2.js */, + ); + path = jstests; + sourceTree = "<group>"; + }; + 934BEBB910DFFA9600178102 /* clone */ = { + isa = PBXGroup; + children = ( + 934BEBBA10DFFA9600178102 /* clonecollection.js */, + ); + path = clone; + sourceTree = "<group>"; + }; + 934BEBCD10DFFA9600178102 /* disk */ = { + isa = PBXGroup; + children = ( + 935C941B1106709800439EB1 /* preallocate.js */, + 934BEBCE10DFFA9600178102 /* dbNoCreate.js */, + 934BEBCF10DFFA9600178102 /* diskfull.js */, + 934BEBD010DFFA9600178102 /* norepeat.js */, + ); + path = disk; + sourceTree = "<group>"; + }; + 934BEC2E10DFFA9600178102 /* perf */ = { + isa = PBXGroup; + children = ( + 934BEC2F10DFFA9600178102 /* find1.js */, + 934BEC3010DFFA9600178102 /* index1.js */, + 934BEC3110DFFA9600178102 /* remove1.js */, + ); + path = perf; + sourceTree = "<group>"; + }; + 934BEC3A10DFFA9600178102 /* quota */ = { + isa = PBXGroup; + children = ( + 934BEC3B10DFFA9600178102 /* quota1.js */, + ); + path = quota; + sourceTree = "<group>"; + }; + 934BEC5010DFFA9600178102 /* repl */ = { + isa = PBXGroup; + children = ( + 934BEC5110DFFA9600178102 /* basic1.js */, + 934BEC5210DFFA9600178102 /* pair1.js */, + 934BEC5310DFFA9600178102 /* pair2.js */, + 934BEC5410DFFA9600178102 /* pair3.js */, + 934BEC5510DFFA9600178102 /* pair4.js */, + 934BEC5610DFFA9600178102 /* pair5.js */, + 934BEC5710DFFA9600178102 /* pair6.js */, + 934BEC5810DFFA9600178102 /* repl1.js */, + 934BEC5910DFFA9600178102 /* repl2.js */, + 934BEC5A10DFFA9600178102 /* repl3.js */, + 934BEC5B10DFFA9600178102 /* repl4.js */, + 934BEC5C10DFFA9600178102 /* repl5.js */, + 934BEC5D10DFFA9600178102 /* repl6.js */, + 934BEC5E10DFFA9600178102 /* repl7.js */, + 934BEC5F10DFFA9600178102 /* repl8.js */, + 934BEC6010DFFA9600178102 /* repl9.js */, + 934BEC6110DFFA9600178102 /* replacePeer1.js */, + 934BEC6210DFFA9700178102 /* replacePeer2.js */, + ); + path = repl; + sourceTree = "<group>"; + }; + 934BEC6610DFFA9700178102 /* sharding */ = { + isa = PBXGroup; + children = ( + 934BEC6710DFFA9700178102 /* auto1.js */, + 934BEC6810DFFA9700178102 /* auto2.js */, + 934BEC6910DFFA9700178102 /* count1.js */, + 934BEC6A10DFFA9700178102 /* diffservers1.js */, + 934BEC6B10DFFA9700178102 /* error1.js */, + 934BEC6C10DFFA9700178102 /* features1.js */, + 934BEC6D10DFFA9700178102 /* features2.js */, + 934BEC6E10DFFA9700178102 /* key_many.js */, + 934BEC6F10DFFA9700178102 /* key_string.js */, + 934BEC7010DFFA9700178102 /* movePrimary1.js */, + 934BEC7110DFFA9700178102 /* moveshard1.js */, + 934BEC7210DFFA9700178102 /* passthrough1.js */, + 934BEC7310DFFA9700178102 /* shard1.js */, + 934BEC7410DFFA9700178102 /* shard2.js */, + 934BEC7510DFFA9700178102 /* shard3.js */, + 934BEC7610DFFA9700178102 /* shard4.js */, + 934BEC7710DFFA9700178102 /* shard5.js */, + 934BEC7810DFFA9700178102 /* shard6.js */, + 934BEC7910DFFA9700178102 /* splitpick.js */, + 934BEC7A10DFFA9700178102 /* version1.js */, + 934BEC7B10DFFA9700178102 /* version2.js */, + ); + path = sharding; + sourceTree = "<group>"; + }; + 934BEC8610DFFA9700178102 /* tool */ = { + isa = PBXGroup; + children = ( + 934BEC8710DFFA9700178102 /* csv1.js */, + 934BEC8810DFFA9700178102 /* dumprestore1.js */, + 934BEC8910DFFA9700178102 /* dumprestore2.js */, + 934BEC8A10DFFA9700178102 /* exportimport1.js */, + 934BEC8B10DFFA9700178102 /* exportimport2.js */, + 934BEC8C10DFFA9700178102 /* tool1.js */, + ); + path = tool; + sourceTree = "<group>"; + }; + 934DD87B0EFAD23B00459CC1 /* util */ = { + isa = PBXGroup; + children = ( + 934BEE8C10E050A500178102 /* allocator.h */, + 934BEE8D10E050A500178102 /* assert_util.cpp */, + 934BEE8E10E050A500178102 /* assert_util.h */, + 934BEE8F10E050A500178102 /* base64.cpp */, + 934BEE9010E050A500178102 /* base64.h */, + 934BEE9110E050A500178102 /* debug_util.cpp */, + 934BEE9210E050A500178102 /* debug_util.h */, + 934BEE9310E050A500178102 /* embedded_builder.h */, + 934BEE9410E050A500178102 /* httpclient.cpp */, + 934BEE9510E050A500178102 /* httpclient.h */, + 934BEE9610E050A500178102 /* md5main.cpp */, + 934BEE9710E050A500178102 /* message_server.h */, + 934BEE9810E050A500178102 /* message_server_asio.cpp */, + 934BEE9910E050A500178102 /* mvar.h */, + 934BEE9A10E050A500178102 /* ntservice.cpp */, + 934BEE9B10E050A500178102 /* ntservice.h */, + 934BEE9C10E050A500178102 /* processinfo.h */, + 934BEE9D10E050A500178102 /* processinfo_darwin.cpp */, + 934BEE9E10E050A500178102 /* processinfo_linux2.cpp */, + 934BEE9F10E050A500178102 /* processinfo_none.cpp */, + 934BEEA010E050A500178102 /* queue.h */, + 930B844D0FA10D1C00F22B4B /* optime.h */, + 93DCDBD30F9515AF005349BC /* file_allocator.h */, + 931184DC0F83C95800A6DC44 /* message_server_port.cpp */, + 936B89590F4C899400934AF2 /* file.h */, + 936B895A0F4C899400934AF2 /* md5.c */, + 936B895B0F4C899400934AF2 /* md5.h */, + 936B895C0F4C899400934AF2 /* md5.hpp */, + 936B895D0F4C899400934AF2 /* md5main.c */, + 936B895E0F4C899400934AF2 /* message.cpp */, + 936B895F0F4C899400934AF2 /* message.h */, + 936B89600F4C899400934AF2 /* top.h */, + 934DD87C0EFAD23B00459CC1 /* background.cpp */, + 934DD87D0EFAD23B00459CC1 /* background.h */, + 934DD87F0EFAD23B00459CC1 /* builder.h */, + 934DD8800EFAD23B00459CC1 /* goodies.h */, + 934DD8810EFAD23B00459CC1 /* hashtab.h */, + 934DD8820EFAD23B00459CC1 /* log.h */, + 934DD8830EFAD23B00459CC1 /* lruishmap.h */, + 934DD8840EFAD23B00459CC1 /* miniwebserver.cpp */, + 934DD8850EFAD23B00459CC1 /* miniwebserver.h */, + 934DD8870EFAD23B00459CC1 /* mmap.cpp */, + 934DD8880EFAD23B00459CC1 /* mmap.h */, + 934DD88A0EFAD23B00459CC1 /* sock.cpp */, + 934DD88B0EFAD23B00459CC1 /* sock.h */, + 934DD88D0EFAD23B00459CC1 /* unittest.h */, + 934DD88E0EFAD23B00459CC1 /* util.cpp */, + ); + path = util; + sourceTree = "<group>"; + }; + 93A13A200F4620A500AF1B0D /* s */ = { + isa = PBXGroup; + children = ( + 93278F610F72D39400844664 /* cursors.cpp */, + 93278F620F72D39400844664 /* cursors.h */, + 93278F630F72D39400844664 /* d_logic.cpp */, + 93278F640F72D39400844664 /* d_logic.h */, + 93278F650F72D39400844664 /* strategy.cpp */, + 93278F660F72D39400844664 /* strategy.h */, + 93278F670F72D39400844664 /* strategy_shard.cpp */, + 93E727090F4B5B5B004F9B5D /* shardkey.cpp */, + 93E7270A0F4B5B5B004F9B5D /* shardkey.h */, + 93A13A210F4620A500AF1B0D /* commands.cpp */, + 93A13A230F4620A500AF1B0D /* config.cpp */, + 93A13A240F4620A500AF1B0D /* config.h */, + 93A13A270F4620A500AF1B0D /* request.cpp */, + 93A13A280F4620A500AF1B0D /* request.h */, + 93A13A2A0F4620A500AF1B0D /* server.cpp */, + 93A13A2B0F4620A500AF1B0D /* server.h */, + 93A13A2D0F4620A500AF1B0D /* shard.cpp */, + 93A13A2E0F4620A500AF1B0D /* shard.h */, + 93A13A300F4620A500AF1B0D /* strategy_single.cpp */, + ); + path = s; + sourceTree = "<group>"; + }; + 93A13A320F4620E500AF1B0D /* tools */ = { + isa = PBXGroup; + children = ( + 931186FB0F8535FF00A6DC44 /* bridge.cpp */, + 93A13A330F4620E500AF1B0D /* dump.cpp */, + 93A13A350F4620E500AF1B0D /* export.cpp */, + 93A13A370F4620E500AF1B0D /* files.cpp */, + 93A13A390F4620E500AF1B0D /* importJSON.cpp */, + 93A13A3B0F4620E500AF1B0D /* restore.cpp */, + 93A13A3D0F4620E500AF1B0D /* sniffer.cpp */, + 93A13A3F0F4620E500AF1B0D /* Tool.cpp */, + 93A13A400F4620E500AF1B0D /* Tool.h */, + ); + path = tools; + sourceTree = "<group>"; + }; + 93A479F20FAF2A5000E760DD /* scripting */ = { + isa = PBXGroup; + children = ( + 93E5B88910D7FF890044F9E4 /* engine_spidermonkey.h */, + 93E5B88A10D7FF890044F9E4 /* v8_db.cpp */, + 93E5B88B10D7FF890044F9E4 /* v8_db.h */, + 93E5B88C10D7FF890044F9E4 /* v8_utils.cpp */, + 93E5B88D10D7FF890044F9E4 /* v8_utils.h */, + 93E5B88E10D7FF890044F9E4 /* v8_wrapper.cpp */, + 93E5B88F10D7FF890044F9E4 /* v8_wrapper.h */, + 93A47AA60FAF41B200E760DD /* engine_v8.h */, + 93A47AA50FAF416F00E760DD /* engine_v8.cpp */, + 93A479F30FAF2A5000E760DD /* engine.cpp */, + 93A479F40FAF2A5000E760DD /* engine.h */, + 93A479F60FAF2A5000E760DD /* engine_java.cpp */, + 93A479F70FAF2A5000E760DD /* engine_java.h */, + 93A479F90FAF2A5000E760DD /* engine_none.cpp */, + 93A479FA0FAF2A5000E760DD /* engine_spidermonkey.cpp */, + ); + path = scripting; + sourceTree = "<group>"; + }; + 93F0956F10E165E50053380C /* parallel */ = { + isa = PBXGroup; + children = ( + 93BCE5A610F3FB5200FA139B /* basicPlus.js */, + 93BCE5A510F3F8E900FA139B /* manyclients.js */, + 93BCE4B510F3C8DB00FA139B /* allops.js */, + 93AEC57A10E94749005DF720 /* insert.js */, + 93F095CC10E16FF70053380C /* shellfork.js */, + 93F0957010E165E50053380C /* basic.js */, + ); + path = parallel; + sourceTree = "<group>"; + }; + C6859E8C029090F304C91782 /* Documentation */ = { + isa = PBXGroup; + children = ( + C6859E8B029090EE04C91782 /* mongo.1 */, + ); + name = Documentation; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + 9302D74A0F2E6E4000DFA4EF /* scons db 64 */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "--64 -j2"; + buildConfigurationList = 9302D74B0F2E6E4000DFA4EF /* Build configuration list for PBXLegacyTarget "scons db 64" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + buildWorkingDirectory = ""; + dependencies = ( + ); + name = "scons db 64"; + passBuildSettingsInEnvironment = 1; + productName = "scons db"; + }; + 9302D74E0F2E6E4400DFA4EF /* scons all 64 */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "--64 -j2 ."; + buildConfigurationList = 9302D74F0F2E6E4400DFA4EF /* Build configuration list for PBXLegacyTarget "scons all 64" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + dependencies = ( + ); + name = "scons all 64"; + passBuildSettingsInEnvironment = 1; + productName = "scons all"; + }; + 9302D7520F2E6E4600DFA4EF /* scons test 64 */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "--64 -j2 test"; + buildConfigurationList = 9302D7530F2E6E4600DFA4EF /* Build configuration list for PBXLegacyTarget "scons test 64" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + dependencies = ( + ); + name = "scons test 64"; + passBuildSettingsInEnvironment = 1; + productName = "scons test"; + }; + 932F1DCC0F213B06008FA2E7 /* scons db */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2"; + buildConfigurationList = 932F1DD40F213B0F008FA2E7 /* Build configuration list for PBXLegacyTarget "scons db" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + buildWorkingDirectory = ""; + dependencies = ( + ); + name = "scons db"; + passBuildSettingsInEnvironment = 1; + productName = "scons db"; + }; + 934BEB2E10DFED2700178102 /* scons debug test v8 */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2 --d --usev8 test"; + buildConfigurationList = 934BEB2F10DFED2700178102 /* Build configuration list for PBXLegacyTarget "scons debug test v8" */; + buildPhases = ( + ); + buildToolPath = /opt/local/bin/scons; + dependencies = ( + ); + name = "scons debug test v8"; + passBuildSettingsInEnvironment = 1; + productName = "scons test"; + }; + 93A47B070FAF46EB00E760DD /* scons debug all */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2 --d ."; + buildConfigurationList = 93A47B0F0FAF474A00E760DD /* Build configuration list for PBXLegacyTarget "scons debug all" */; + buildPhases = ( + ); + buildToolPath = /opt/local/bin/scons; + dependencies = ( + ); + name = "scons debug all"; + passBuildSettingsInEnvironment = 1; + productName = "scons debug all"; + }; + 93A8D0700F36AE0200C92B85 /* scons debug test */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2 --d test"; + buildConfigurationList = 93A8D0710F36AE0200C92B85 /* Build configuration list for PBXLegacyTarget "scons debug test" */; + buildPhases = ( + ); + buildToolPath = /opt/local/bin/scons; + dependencies = ( + ); + name = "scons debug test"; + passBuildSettingsInEnvironment = 1; + productName = "scons test"; + }; + 93AF75120F213CC500994C66 /* scons all */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2 ."; + buildConfigurationList = 93AF75250F213D2500994C66 /* Build configuration list for PBXLegacyTarget "scons all" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + dependencies = ( + ); + name = "scons all"; + passBuildSettingsInEnvironment = 1; + productName = "scons all"; + }; + 93AF75170F213CFB00994C66 /* scons test */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "-j2 test"; + buildConfigurationList = 93AF75260F213D2500994C66 /* Build configuration list for PBXLegacyTarget "scons test" */; + buildPhases = ( + ); + buildToolPath = /usr/local/bin/scons; + dependencies = ( + ); + name = "scons test"; + passBuildSettingsInEnvironment = 1; + productName = "scons test"; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "mongo" */; + compatibilityVersion = "Xcode 3.0"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* mongo */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 932F1DCC0F213B06008FA2E7 /* scons db */, + 93AF75120F213CC500994C66 /* scons all */, + 93AF75170F213CFB00994C66 /* scons test */, + 9302D74A0F2E6E4000DFA4EF /* scons db 64 */, + 9302D74E0F2E6E4400DFA4EF /* scons all 64 */, + 9302D7520F2E6E4600DFA4EF /* scons test 64 */, + 93A8D0700F36AE0200C92B85 /* scons debug test */, + 93A47B070FAF46EB00E760DD /* scons debug all */, + 934BEB2E10DFED2700178102 /* scons debug test v8 */, + ); + }; +/* End PBXProject section */ + +/* Begin XCBuildConfiguration section */ + 1DEB923608733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk"; + }; + name = Debug; + }; + 1DEB923708733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk"; + }; + name = Release; + }; + 9302D74C0F2E6E4000DFA4EF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons db"; + }; + name = Debug; + }; + 9302D74D0F2E6E4000DFA4EF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons db"; + ZERO_LINK = NO; + }; + name = Release; + }; + 9302D7500F2E6E4400DFA4EF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons all"; + }; + name = Debug; + }; + 9302D7510F2E6E4400DFA4EF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons all"; + ZERO_LINK = NO; + }; + name = Release; + }; + 9302D7540F2E6E4600DFA4EF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons test"; + }; + name = Debug; + }; + 9302D7550F2E6E4600DFA4EF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons test"; + ZERO_LINK = NO; + }; + name = Release; + }; + 932F1DCD0F213B06008FA2E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons db"; + }; + name = Debug; + }; + 932F1DCE0F213B06008FA2E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons db"; + ZERO_LINK = NO; + }; + name = Release; + }; + 934BEB3010DFED2700178102 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons debug test"; + }; + name = Debug; + }; + 934BEB3110DFED2700178102 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons test"; + ZERO_LINK = NO; + }; + name = Release; + }; + 93A47B080FAF46EB00E760DD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons debug all"; + }; + name = Debug; + }; + 93A47B090FAF46EB00E760DD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons debug all"; + ZERO_LINK = NO; + }; + name = Release; + }; + 93A8D0720F36AE0200C92B85 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons debug test"; + }; + name = Debug; + }; + 93A8D0730F36AE0200C92B85 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons test"; + ZERO_LINK = NO; + }; + name = Release; + }; + 93AF75130F213CC500994C66 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons all"; + }; + name = Debug; + }; + 93AF75140F213CC500994C66 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons all"; + ZERO_LINK = NO; + }; + name = Release; + }; + 93AF75180F213CFC00994C66 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "scons test"; + }; + name = Debug; + }; + 93AF75190F213CFC00994C66 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "scons test"; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "mongo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923608733DC60010E9CD /* Debug */, + 1DEB923708733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9302D74B0F2E6E4000DFA4EF /* Build configuration list for PBXLegacyTarget "scons db 64" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9302D74C0F2E6E4000DFA4EF /* Debug */, + 9302D74D0F2E6E4000DFA4EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9302D74F0F2E6E4400DFA4EF /* Build configuration list for PBXLegacyTarget "scons all 64" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9302D7500F2E6E4400DFA4EF /* Debug */, + 9302D7510F2E6E4400DFA4EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9302D7530F2E6E4600DFA4EF /* Build configuration list for PBXLegacyTarget "scons test 64" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9302D7540F2E6E4600DFA4EF /* Debug */, + 9302D7550F2E6E4600DFA4EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 932F1DD40F213B0F008FA2E7 /* Build configuration list for PBXLegacyTarget "scons db" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 932F1DCD0F213B06008FA2E7 /* Debug */, + 932F1DCE0F213B06008FA2E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 934BEB2F10DFED2700178102 /* Build configuration list for PBXLegacyTarget "scons debug test v8" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 934BEB3010DFED2700178102 /* Debug */, + 934BEB3110DFED2700178102 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93A47B0F0FAF474A00E760DD /* Build configuration list for PBXLegacyTarget "scons debug all" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93A47B080FAF46EB00E760DD /* Debug */, + 93A47B090FAF46EB00E760DD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93A8D0710F36AE0200C92B85 /* Build configuration list for PBXLegacyTarget "scons debug test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93A8D0720F36AE0200C92B85 /* Debug */, + 93A8D0730F36AE0200C92B85 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93AF75250F213D2500994C66 /* Build configuration list for PBXLegacyTarget "scons all" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93AF75130F213CC500994C66 /* Debug */, + 93AF75140F213CC500994C66 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93AF75260F213D2500994C66 /* Build configuration list for PBXLegacyTarget "scons test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93AF75180F213CFC00994C66 /* Debug */, + 93AF75190F213CFC00994C66 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/msvc/README b/msvc/README new file mode 100644 index 0000000..1a7221a --- /dev/null +++ b/msvc/README @@ -0,0 +1,54 @@ + +Instructions for compiling MongoDB in Visual Studio 2008 +======================================================== + +Visual Studio Solution: +----------------------- + +mongo.sln -> MongoDB solution that contains all projects necessary for building applications and libraries. + + + +Static Library Projects: +------------------------ + +mongo_common -> common MongoDB files +core_server -> score server files +server_only -> files for building server-only applications +shard_server -> shard server files + + +Console Application Projects: +----------------------------- + +mongod -> MongoDB server (links mongo_common and server_only) +mongo -> MongoDB shell (links mongo_common) +mongobridge -> MongoDB bridge server shell (links mongo_common and server_only) +mongodump -> MongoDB dump application (links mongo_common and server_only) +mongoexport -> MongoDB export application (links mongo_common and server_only) +mongofiles -> MongoDB files application (links mongo_common and server_only) +mongoimportjson -> MongoDB import json application (links mongo_common and server_only) +mongorestore -> MongoDB restore application (links mongo_common and server_only) +mongos -> MongoDB shard server (links mongo_common, core_server and shard_server) + + +Client Driver Library: +----------------------------- + +mongoclient -> static library containing client driver files + + + +Notes: +====== + +1) All static libraries derive project settings from Project Property Sheet "mongo_lib" +(View->Other Windows->Property Manager). Settings configured in this Property Sheet will +be inherited by all static library projects (Include Directories, Library Directories, etc). + +2) All console applications derive project settings from "mongo_app". + +3) msvc_scripting.cpp is used to control the javascript library to use - to change, simply +modify the "Preprocessor" project setting in the Property Sheets to reflect the required +javascript option (USESM or NOJNI). + diff --git a/msvc/bin/Debug/README b/msvc/bin/Debug/README new file mode 100644 index 0000000..edf36a2 --- /dev/null +++ b/msvc/bin/Debug/README @@ -0,0 +1 @@ +This is a dummy file to prevent Git from ignoring this empty directory.
\ No newline at end of file diff --git a/msvc/bin/Release/README b/msvc/bin/Release/README new file mode 100644 index 0000000..edf36a2 --- /dev/null +++ b/msvc/bin/Release/README @@ -0,0 +1 @@ +This is a dummy file to prevent Git from ignoring this empty directory.
\ No newline at end of file diff --git a/msvc/core_server/core_server.vcproj b/msvc/core_server/core_server.vcproj new file mode 100644 index 0000000..5def133 --- /dev/null +++ b/msvc/core_server/core_server.vcproj @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="core_server"
+ ProjectGUID="{8DA99072-BDC8-4204-8DE2-67B5A5466D40}"
+ RootNamespace="core_server"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""c:\Program Files\boost\boost_1_35_0";"..\..\pcre-7.4";..\..\.;..\..\..\js\src"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="util"
+ >
+ <File
+ RelativePath="..\..\util\message_server.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\message_server_asio.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\util\message_server_port.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/lib/Debug/README b/msvc/lib/Debug/README new file mode 100644 index 0000000..edf36a2 --- /dev/null +++ b/msvc/lib/Debug/README @@ -0,0 +1 @@ +This is a dummy file to prevent Git from ignoring this empty directory.
\ No newline at end of file diff --git a/msvc/lib/Release/README b/msvc/lib/Release/README new file mode 100644 index 0000000..edf36a2 --- /dev/null +++ b/msvc/lib/Release/README @@ -0,0 +1 @@ +This is a dummy file to prevent Git from ignoring this empty directory.
\ No newline at end of file diff --git a/msvc/mongo.sln b/msvc/mongo.sln new file mode 100644 index 0000000..6b0b1cc --- /dev/null +++ b/msvc/mongo.sln @@ -0,0 +1,138 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongo_common", "mongo_common\mongo_common.vcproj", "{69E92318-D8DA-434E-B3D6-F172E88ADC95}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongod", "mongod\mongod.vcproj", "{0811084D-41C1-49E6-998B-3B1230227FF0}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + {8DA99072-BDC8-4204-8DE2-67B5A5466D40} = {8DA99072-BDC8-4204-8DE2-67B5A5466D40} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongo", "mongo\mongo.vcproj", "{34348125-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_server", "core_server\core_server.vcproj", "{8DA99072-BDC8-4204-8DE2-67B5A5466D40}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "server_only", "server_only\server_only.vcproj", "{3FEF2C0D-6B49-469F-94C4-0673291D58CE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shard_server", "shard_server\shard_server.vcproj", "{9D2CD1F3-973E-49E6-A0E2-95C834562263}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongos", "mongos\mongos.vcproj", "{942113AE-678B-4C7B-BC78-D91AB9C52390}" + ProjectSection(ProjectDependencies) = postProject + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + {8DA99072-BDC8-4204-8DE2-67B5A5466D40} = {8DA99072-BDC8-4204-8DE2-67B5A5466D40} + {9D2CD1F3-973E-49E6-A0E2-95C834562263} = {9D2CD1F3-973E-49E6-A0E2-95C834562263} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongodump", "mongodump\mongodump.vcproj", "{44348125-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongorestore", "mongorestore\mongorestore.vcproj", "{45348125-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongoexport", "mongoexport\mongoexport.vcproj", "{45448125-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongoimportjson", "mongoimportjson\mongoimportjson.vcproj", "{45459135-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongofiles", "mongofiles\mongofiles.vcproj", "{45459125-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongobridge", "mongobridge\mongobridge.vcproj", "{45458225-2F31-459F-AA95-A1525903AE2B}" + ProjectSection(ProjectDependencies) = postProject + {3FEF2C0D-6B49-469F-94C4-0673291D58CE} = {3FEF2C0D-6B49-469F-94C4-0673291D58CE} + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mongoclient", "mongoclient\mongoclient.vcproj", "{36AAAE5C-4750-4713-9240-A43BE30BA8D3}" + ProjectSection(ProjectDependencies) = postProject + {69E92318-D8DA-434E-B3D6-F172E88ADC95} = {69E92318-D8DA-434E-B3D6-F172E88ADC95} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {69E92318-D8DA-434E-B3D6-F172E88ADC95}.Debug|Win32.ActiveCfg = Debug|Win32 + {69E92318-D8DA-434E-B3D6-F172E88ADC95}.Debug|Win32.Build.0 = Debug|Win32 + {69E92318-D8DA-434E-B3D6-F172E88ADC95}.Release|Win32.ActiveCfg = Release|Win32 + {69E92318-D8DA-434E-B3D6-F172E88ADC95}.Release|Win32.Build.0 = Release|Win32 + {0811084D-41C1-49E6-998B-3B1230227FF0}.Debug|Win32.ActiveCfg = Debug|Win32 + {0811084D-41C1-49E6-998B-3B1230227FF0}.Debug|Win32.Build.0 = Debug|Win32 + {0811084D-41C1-49E6-998B-3B1230227FF0}.Release|Win32.ActiveCfg = Release|Win32 + {0811084D-41C1-49E6-998B-3B1230227FF0}.Release|Win32.Build.0 = Release|Win32 + {34348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {34348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {34348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {34348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {8DA99072-BDC8-4204-8DE2-67B5A5466D40}.Debug|Win32.ActiveCfg = Debug|Win32 + {8DA99072-BDC8-4204-8DE2-67B5A5466D40}.Debug|Win32.Build.0 = Debug|Win32 + {8DA99072-BDC8-4204-8DE2-67B5A5466D40}.Release|Win32.ActiveCfg = Release|Win32 + {8DA99072-BDC8-4204-8DE2-67B5A5466D40}.Release|Win32.Build.0 = Release|Win32 + {3FEF2C0D-6B49-469F-94C4-0673291D58CE}.Debug|Win32.ActiveCfg = Debug|Win32 + {3FEF2C0D-6B49-469F-94C4-0673291D58CE}.Debug|Win32.Build.0 = Debug|Win32 + {3FEF2C0D-6B49-469F-94C4-0673291D58CE}.Release|Win32.ActiveCfg = Release|Win32 + {3FEF2C0D-6B49-469F-94C4-0673291D58CE}.Release|Win32.Build.0 = Release|Win32 + {9D2CD1F3-973E-49E6-A0E2-95C834562263}.Debug|Win32.ActiveCfg = Debug|Win32 + {9D2CD1F3-973E-49E6-A0E2-95C834562263}.Debug|Win32.Build.0 = Debug|Win32 + {9D2CD1F3-973E-49E6-A0E2-95C834562263}.Release|Win32.ActiveCfg = Release|Win32 + {9D2CD1F3-973E-49E6-A0E2-95C834562263}.Release|Win32.Build.0 = Release|Win32 + {942113AE-678B-4C7B-BC78-D91AB9C52390}.Debug|Win32.ActiveCfg = Debug|Win32 + {942113AE-678B-4C7B-BC78-D91AB9C52390}.Debug|Win32.Build.0 = Debug|Win32 + {942113AE-678B-4C7B-BC78-D91AB9C52390}.Release|Win32.ActiveCfg = Release|Win32 + {942113AE-678B-4C7B-BC78-D91AB9C52390}.Release|Win32.Build.0 = Release|Win32 + {44348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {44348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {44348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {44348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {45348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {45348125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {45348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {45348125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {45448125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {45448125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {45448125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {45448125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {45459135-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {45459135-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {45459135-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {45459135-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {45459125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {45459125-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {45459125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {45459125-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {45458225-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {45458225-2F31-459F-AA95-A1525903AE2B}.Debug|Win32.Build.0 = Debug|Win32 + {45458225-2F31-459F-AA95-A1525903AE2B}.Release|Win32.ActiveCfg = Release|Win32 + {45458225-2F31-459F-AA95-A1525903AE2B}.Release|Win32.Build.0 = Release|Win32 + {36AAAE5C-4750-4713-9240-A43BE30BA8D3}.Debug|Win32.ActiveCfg = Debug|Win32 + {36AAAE5C-4750-4713-9240-A43BE30BA8D3}.Debug|Win32.Build.0 = Debug|Win32 + {36AAAE5C-4750-4713-9240-A43BE30BA8D3}.Release|Win32.ActiveCfg = Release|Win32 + {36AAAE5C-4750-4713-9240-A43BE30BA8D3}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/msvc/mongo/mongo.vcproj b/msvc/mongo/mongo.vcproj new file mode 100644 index 0000000..1dd951d --- /dev/null +++ b/msvc/mongo/mongo.vcproj @@ -0,0 +1,312 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongo"
+ ProjectGUID="{34348125-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongo"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib ws2_32.lib js.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib ws2_32.lib js.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\clientOnly.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="shell"
+ >
+ <File
+ RelativePath="..\..\shell\dbshell.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\shell\utils.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongo_app.vsprops b/msvc/mongo_app.vsprops new file mode 100644 index 0000000..22980f5 --- /dev/null +++ b/msvc/mongo_app.vsprops @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="mongo_app" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""c:\Program Files\boost\boost_1_35_0";"..\..\pcre-7.4";..\..\.;..\..\..\js\src;"C:\Program Files\Java\jdk\lib"" + PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;USESM;OLDJS;STATIC_JS_API;XP_WIN;USE_ASIO" + UsePrecompiledHeader="0" + DisableSpecificWarnings="4355;4800;4244;4996" + /> + <Tool + Name="VCLinkerTool" + AdditionalLibraryDirectories=""..\lib\$(ConfigurationName)";"..\..\..\js\js\$(ConfigurationName)";"c:\Program Files\boost\boost_1_35_0\lib"" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\."" + /> +</VisualStudioPropertySheet> diff --git a/msvc/mongo_common/mongo_common.vcproj b/msvc/mongo_common/mongo_common.vcproj new file mode 100644 index 0000000..e107723 --- /dev/null +++ b/msvc/mongo_common/mongo_common.vcproj @@ -0,0 +1,940 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongo_common"
+ ProjectGUID="{69E92318-D8DA-434E-B3D6-F172E88ADC95}"
+ RootNamespace="mongo_common"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="pcre"
+ >
+ <File
+ RelativePath="..\..\pcre-7.4\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_chartables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_compile.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_config.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_dfa_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_exec.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_fullinfo.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_get.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_globals.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_info.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_internal.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_maketables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_newline.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_ord2utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_refcount.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_scanner.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_scanner.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_stringpiece.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_stringpiece.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_study.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_tables.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_try_flipped.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_ucp_searchfuncs.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_valid_utf8.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_version.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcre_xclass.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcrecpp.cc"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcrecpp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcrecpp_internal.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\pcrecpparg.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\ucp.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\ucpinternal.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\pcre-7.4\ucptable.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="scipting"
+ >
+ <File
+ RelativePath="..\..\scripting\engine.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\scripting\engine.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\shell\mongo_vstudio.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\msvc_scripting.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="db"
+ >
+ <File
+ RelativePath="..\..\db\commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\commands.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\jsobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\jsobj.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\json.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\json.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\lasterror.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\lasterror.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\nonce.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\nonce.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\queryutil.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\queryutil.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="util"
+ >
+ <File
+ RelativePath="..\..\util\allocator.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\assert_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\assert_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\background.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\debug_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\goodies.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\md5.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\util\md5.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\md5.hpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\md5main.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\util\message.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\message.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\mmap.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\mmap.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\mmap_win.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\processinfo.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\processinfo_none.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\sock.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\sock.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\util.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\connpool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\client\connpool.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\client\dbclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\client\dbclient.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\client\model.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\client\model.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongo_lib.vsprops b/msvc/mongo_lib.vsprops new file mode 100644 index 0000000..d2caf0a --- /dev/null +++ b/msvc/mongo_lib.vsprops @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioPropertySheet + ProjectType="Visual C++" + Version="8.00" + Name="mongo_lib" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories=""c:\Program Files\boost\boost_1_35_0";"..\..\pcre-7.4";..\..\.;..\..\..\js\src;"C:\Program Files\Java\jdk\lib"" + PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;USESM;OLDJS;STATIC_JS_API;XP_WIN;USE_ASIO" + UsePrecompiledHeader="0" + DisableSpecificWarnings="4355;4800;4244;4996" + /> + <Tool + Name="VCLibrarianTool" + AdditionalLibraryDirectories="..\lib\$(ConfigurationName)" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\."" + /> +</VisualStudioPropertySheet> diff --git a/msvc/mongobridge/mongobridge.vcproj b/msvc/mongobridge/mongobridge.vcproj new file mode 100644 index 0000000..250f569 --- /dev/null +++ b/msvc/mongobridge/mongobridge.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongobridge"
+ ProjectGUID="{45458225-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongobridge"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\bridge.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongoclient/mongoclient.vcproj b/msvc/mongoclient/mongoclient.vcproj new file mode 100644 index 0000000..ad2703c --- /dev/null +++ b/msvc/mongoclient/mongoclient.vcproj @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongoclient"
+ ProjectGUID="{36AAAE5C-4750-4713-9240-A43BE30BA8D3}"
+ RootNamespace="mongoclient"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalDependencies="mongo_common.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalDependencies="mongo_common.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\clientOnly.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongod/mongod.vcproj b/msvc/mongod/mongod.vcproj new file mode 100644 index 0000000..4ff3db0 --- /dev/null +++ b/msvc/mongod/mongod.vcproj @@ -0,0 +1,232 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongod"
+ ProjectGUID="{0811084D-41C1-49E6-998B-3B1230227FF0}"
+ RootNamespace="mongod"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/IGNORE:4099"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib js.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/IGNORE:4099"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib js.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="db"
+ >
+ <File
+ RelativePath="..\..\db\db.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongodump/mongodump.vcproj b/msvc/mongodump/mongodump.vcproj new file mode 100644 index 0000000..7d331b0 --- /dev/null +++ b/msvc/mongodump/mongodump.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongodump"
+ ProjectGUID="{44348125-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongodump"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\dump.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongoexport/mongoexport.vcproj b/msvc/mongoexport/mongoexport.vcproj new file mode 100644 index 0000000..0225031 --- /dev/null +++ b/msvc/mongoexport/mongoexport.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongoexport"
+ ProjectGUID="{45448125-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongoexport"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\export.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongofiles/mongofiles.vcproj b/msvc/mongofiles/mongofiles.vcproj new file mode 100644 index 0000000..a36f442 --- /dev/null +++ b/msvc/mongofiles/mongofiles.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongofiles"
+ ProjectGUID="{45459125-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongofiles"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\files.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongoimportjson/mongoimportjson.vcproj b/msvc/mongoimportjson/mongoimportjson.vcproj new file mode 100644 index 0000000..f2b2f7f --- /dev/null +++ b/msvc/mongoimportjson/mongoimportjson.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongoimportjson"
+ ProjectGUID="{45459135-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongoimportjson"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\importJSON.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongorestore/mongorestore.vcproj b/msvc/mongorestore/mongorestore.vcproj new file mode 100644 index 0000000..e40ea92 --- /dev/null +++ b/msvc/mongorestore/mongorestore.vcproj @@ -0,0 +1,296 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongorestore"
+ ProjectGUID="{45348125-2F31-459F-AA95-A1525903AE2B}"
+ RootNamespace="mongorestore"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib server_only.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\..\client\gridfs.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\client\gridfs.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="tools"
+ >
+ <File
+ RelativePath="..\..\tools\restore.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\tools\Tool.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/mongos/mongos.vcproj b/msvc/mongos/mongos.vcproj new file mode 100644 index 0000000..058fa1e --- /dev/null +++ b/msvc/mongos/mongos.vcproj @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongos"
+ ProjectGUID="{942113AE-678B-4C7B-BC78-D91AB9C52390}"
+ RootNamespace="mongos"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib core_server.lib shard_server.lib ws2_32.lib"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="..\mongo_app.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="mongo_common.lib core_server.lib shard_server.lib ws2_32.lib"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).exe" "..\bin\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="s"
+ >
+ <File
+ RelativePath="..\..\s\server.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\server.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/msvc_scripting.cpp b/msvc/msvc_scripting.cpp new file mode 100644 index 0000000..0b4e21f --- /dev/null +++ b/msvc/msvc_scripting.cpp @@ -0,0 +1,10 @@ + +#include "stdafx.h" + +#if defined(USESM) +#include "..\scripting\engine_spidermonkey.cpp" +#elif defined(NOJNI) +#include "..\scripting\engine_java.cpp" +#else +#include "..\scripting\engine_none.cpp" +#endif
\ No newline at end of file diff --git a/msvc/server_only/server_only.vcproj b/msvc/server_only/server_only.vcproj new file mode 100644 index 0000000..cbafeff --- /dev/null +++ b/msvc/server_only/server_only.vcproj @@ -0,0 +1,362 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="server_only"
+ ProjectGUID="{3FEF2C0D-6B49-469F-94C4-0673291D58CE}"
+ RootNamespace="server_only"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""c:\Program Files\boost\boost_1_35_0";"..\..\pcre-7.4";..\..\.;..\..\..\js\src"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="db"
+ >
+ <File
+ RelativePath="..\..\db\btree.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\btree.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\btreecursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\clientcursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\clientcursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\cloner.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\cursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\cursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbcommands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbeval.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbhelpers.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbhelpers.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbinfo.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbinfo.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\dbwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\instance.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\instance.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\introspect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\introspect.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\matcher.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\matcher.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\namespace.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\namespace.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\pdfile.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\pdfile.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\query.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\query.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\queryoptimizer.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\queryoptimizer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\reccache.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\reccache.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\repl.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\repl.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\security.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\security.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\security_commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\storage.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\storage.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\db\tests.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="util"
+ >
+ <File
+ RelativePath="..\..\util\miniwebserver.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\miniwebserver.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\ntservice.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\util\ntservice.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="s"
+ >
+ <File
+ RelativePath="..\..\s\d_logic.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\d_logic.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/msvc/shard_server/shard_server.vcproj b/msvc/shard_server/shard_server.vcproj new file mode 100644 index 0000000..e208903 --- /dev/null +++ b/msvc/shard_server/shard_server.vcproj @@ -0,0 +1,262 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="shard_server"
+ ProjectGUID="{9D2CD1F3-973E-49E6-A0E2-95C834562263}"
+ RootNamespace="shard_server"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\mongo_lib.vsprops"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ WholeProgramOptimization="false"
+ AdditionalIncludeDirectories=""c:\Program Files\boost\boost_1_35_0";"..\..\pcre-7.4";..\..\.;..\..\..\js\src"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ CommandLine="copy "$(OutDir)\$(ProjectName).lib" "..\lib\$(ConfigurationName)\.""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\..\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\targetver.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="s"
+ >
+ <File
+ RelativePath="..\..\s\commands_admin.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\commands_public.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\config.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\cursors.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\cursors.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\request.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\request.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\shard.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\shard.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\shardkey.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\shardkey.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\strategy.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\strategy.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\strategy_shard.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\s\strategy_single.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ >
+ <File
+ RelativePath="..\..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/pcre-7.4/config-cmake.h.in b/pcre-7.4/config-cmake.h.in new file mode 100644 index 0000000..27a2d02 --- /dev/null +++ b/pcre-7.4/config-cmake.h.in @@ -0,0 +1,31 @@ +/* config.h for CMake builds */ + +#cmakedefine HAVE_DIRENT_H +#cmakedefine HAVE_UNISTD_H +#cmakedefine HAVE_SYS_STAT_H +#cmakedefine HAVE_SYS_TYPES_H +#cmakedefine HAVE_TYPE_TRAITS_H +#cmakedefine HAVE_BITS_TYPE_TRAITS_H + +#cmakedefine HAVE_BCOPY +#cmakedefine HAVE_MEMMOVE +#cmakedefine HAVE_STRERROR + +#cmakedefine PCRE_STATIC + +#cmakedefine SUPPORT_UTF8 +#cmakedefine SUPPORT_UCP +#cmakedefine EBCDIC +#cmakedefine BSR_ANYCRLF +#cmakedefine NO_RECURSE + +#define NEWLINE @NEWLINE@ +#define POSIX_MALLOC_THRESHOLD @PCRE_POSIX_MALLOC_THRESHOLD@ +#define LINK_SIZE @PCRE_LINK_SIZE@ +#define MATCH_LIMIT @PCRE_MATCH_LIMIT@ +#define MATCH_LIMIT_RECURSION @PCRE_MATCH_LIMIT_RECURSION@ + +#define MAX_NAME_SIZE 32 +#define MAX_NAME_COUNT 10000 + +/* end config.h for CMake builds */ diff --git a/pcre-7.4/config.h b/pcre-7.4/config.h new file mode 100644 index 0000000..14e14bf --- /dev/null +++ b/pcre-7.4/config.h @@ -0,0 +1,224 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + + +/* On Unix-like systems config.h.in is converted by "configure" into config.h. +Some other environments also support the use of "configure". PCRE is written in +Standard C, but there are a few non-standard things it can cope with, allowing +it to run on SunOS4 and other "close to standard" systems. + +If you are going to build PCRE "by hand" on a system without "configure" you +should copy the distributed config.h.generic to config.h, and then set up the +macro definitions the way you need them. You must then add -DHAVE_CONFIG_H to +all of your compile commands, so that config.h is included at the start of +every source. + +Alternatively, you can avoid editing by using -D on the compiler command line +to set the macro values. In this case, you do not have to set -DHAVE_CONFIG_H. + +PCRE uses memmove() if HAVE_MEMMOVE is set to 1; otherwise it uses bcopy() if +HAVE_BCOPY is set to 1. If your system has neither bcopy() nor memmove(), set +them both to 0; an emulation function will be used. */ + +/* By default, the \R escape sequence matches any Unicode line ending + character or sequence of characters. If BSR_ANYCRLF is defined, this is + changed so that backslash-R matches only CR, LF, or CRLF. The build- time + default can be overridden by the user of PCRE at runtime. On systems that + support it, "configure" can be used to override the default. */ +/* #undef BSR_ANYCRLF */ + +/* If you are compiling for a system that uses EBCDIC instead of ASCII + character codes, define this macro as 1. On systems that can use + "configure", this can be done via --enable-ebcdic. */ +/* #undef EBCDIC */ + +/* Define to 1 if you have the `bcopy' function. */ +#define HAVE_BCOPY 1 + +/* Define to 1 if you have the <bits/type_traits.h> header file. */ +/* #undef HAVE_BITS_TYPE_TRAITS_H */ + +/* Define to 1 if you have the <dirent.h> header file. */ +// #define HAVE_DIRENT_H 1 ERH + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <limits.h> header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if the system has the type `long long'. */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if you have the `memmove' function. */ +#define HAVE_MEMMOVE 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the <string> header file. */ +#define HAVE_STRING 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strtoll' function. */ +// dm: visual studio +//#define HAVE_STRTOLL 1 + +/* Define to 1 if you have the `strtoq' function. */ +// dm: visual studio +//#define HAVE_STRTOQ 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <type_traits.h> header file. */ +/* #undef HAVE_TYPE_TRAITS_H */ + +/* Define to 1 if you have the <unistd.h> header file. */ +//#define HAVE_UNISTD_H 1 ERH + +/* Define to 1 if the system has the type `unsigned long long'. */ +#define HAVE_UNSIGNED_LONG_LONG 1 + +/* Define to 1 if you have the <windows.h> header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the `_strtoi64' function. */ +/* #undef HAVE__STRTOI64 */ +// dm: visual studio +#define HAVE__STRTOI64 1 + +/* The value of LINK_SIZE determines the number of bytes used to store links + as offsets within the compiled regex. The default is 2, which allows for + compiled patterns up to 64K long. This covers the vast majority of cases. + However, PCRE can also be compiled to use 3 or 4 bytes instead. This allows + for longer patterns in extreme cases. On systems that support it, + "configure" can be used to override this default. */ +#define LINK_SIZE 2 + +/* The value of MATCH_LIMIT determines the default number of times the + internal match() function can be called during a single execution of + pcre_exec(). There is a runtime interface for setting a different limit. + The limit exists in order to catch runaway regular expressions that take + for ever to determine that they do not match. The default is set very large + so that it does not accidentally catch legitimate cases. On systems that + support it, "configure" can be used to override this default default. */ +#define MATCH_LIMIT 200000 + +/* The above limit applies to all calls of match(), whether or not they + increase the recursion depth. In some environments it is desirable to limit + the depth of recursive calls of match() more strictly, in order to restrict + the maximum amount of stack (or heap, if NO_RECURSE is defined) that is + used. The value of MATCH_LIMIT_RECURSION applies only to recursive calls of + match(). To have any useful effect, it must be less than the value of + MATCH_LIMIT. The default is to use the same value as MATCH_LIMIT. There is + a runtime method for setting a different limit. On systems that support it, + "configure" can be used to override the default. */ +#define MATCH_LIMIT_RECURSION 4000 + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#define MAX_NAME_COUNT 10000 + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#define MAX_NAME_SIZE 32 + +/* The value of NEWLINE determines the newline character sequence. On systems + that support it, "configure" can be used to override the default, which is + 10. The possible values are 10 (LF), 13 (CR), 3338 (CRLF), -1 (ANY), or -2 + (ANYCRLF). */ +#define NEWLINE 10 + +/* PCRE uses recursive function calls to handle backtracking while matching. + This can sometimes be a problem on systems that have stacks of limited + size. Define NO_RECURSE to get a version that doesn't use recursion in the + match() function; instead it creates its own stack by steam using + pcre_recurse_malloc() to obtain memory from the heap. For more detail, see + the comments and other stuff just above the match() function. On systems + that support it, "configure" can be used to set this in the Makefile (use + --disable-stack-for-recursion). */ +/* #undef NO_RECURSE */ + +/* Name of package */ +#define PACKAGE "pcre" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "PCRE" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "PCRE 7.4" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "pcre" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "7.4" + + +/* If you are compiling for a system other than a Unix-like system or + Win32, and it needs some magic to be inserted before the definition + of a function that is exported by the library, define this macro to + contain the relevant magic. If you do not define this macro, it + defaults to "extern" for a C compiler and "extern C" for a C++ + compiler on non-Win32 systems. This macro apears at the start of + every exported function that is part of the external API. It does + not appear on functions that are "external" in the C sense, but + which are internal to the library. */ +/* #undef PCRE_EXP_DEFN */ + +/* Define if linking statically (TODO: make nice with Libtool) */ +/* #undef PCRE_STATIC */ + +/* When calling PCRE via the POSIX interface, additional working storage is + required for holding the pointers to capturing substrings because PCRE + requires three integers per substring, whereas the POSIX interface provides + only two. If the number of expected substrings is small, the wrapper + function uses space on the stack, because this is faster than using + malloc() for each call. The threshold above which the stack is no longer + used is defined by POSIX_MALLOC_THRESHOLD. On systems that support it, + "configure" can be used to override this default. */ +#define POSIX_MALLOC_THRESHOLD 10 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to enable support for Unicode properties */ +/* #undef SUPPORT_UCP */ + +/* Define to enable support for the UTF-8 Unicode encoding. */ +#define SUPPORT_UTF8 + +/* Version number of package */ +#define VERSION "7.4" + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +/* #undef size_t */ diff --git a/pcre-7.4/config.h.generic b/pcre-7.4/config.h.generic new file mode 100644 index 0000000..b6d7ab8 --- /dev/null +++ b/pcre-7.4/config.h.generic @@ -0,0 +1,278 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + + +/* On Unix-like systems config.h.in is converted by "configure" into config.h. +Some other environments also support the use of "configure". PCRE is written in +Standard C, but there are a few non-standard things it can cope with, allowing +it to run on SunOS4 and other "close to standard" systems. + +If you are going to build PCRE "by hand" on a system without "configure" you +should copy the distributed config.h.generic to config.h, and then set up the +macro definitions the way you need them. You must then add -DHAVE_CONFIG_H to +all of your compile commands, so that config.h is included at the start of +every source. + +Alternatively, you can avoid editing by using -D on the compiler command line +to set the macro values. In this case, you do not have to set -DHAVE_CONFIG_H. + +PCRE uses memmove() if HAVE_MEMMOVE is set to 1; otherwise it uses bcopy() if +HAVE_BCOPY is set to 1. If your system has neither bcopy() nor memmove(), set +them both to 0; an emulation function will be used. */ + +/* By default, the \R escape sequence matches any Unicode line ending + character or sequence of characters. If BSR_ANYCRLF is defined, this is + changed so that backslash-R matches only CR, LF, or CRLF. The build- time + default can be overridden by the user of PCRE at runtime. On systems that + support it, "configure" can be used to override the default. */ +/* #undef BSR_ANYCRLF */ + +/* If you are compiling for a system that uses EBCDIC instead of ASCII + character codes, define this macro as 1. On systems that can use + "configure", this can be done via --enable-ebcdic. */ +/* #undef EBCDIC */ + +/* Define to 1 if you have the `bcopy' function. */ +#ifndef HAVE_BCOPY +#define HAVE_BCOPY 1 +#endif + +/* Define to 1 if you have the <bits/type_traits.h> header file. */ +/* #undef HAVE_BITS_TYPE_TRAITS_H */ + +/* Define to 1 if you have the <dirent.h> header file. */ +#ifndef HAVE_DIRENT_H +#define HAVE_DIRENT_H 1 +#endif + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#ifndef HAVE_DLFCN_H +#define HAVE_DLFCN_H 1 +#endif + +/* Define to 1 if you have the <inttypes.h> header file. */ +#ifndef HAVE_INTTYPES_H +#define HAVE_INTTYPES_H 1 +#endif + +/* Define to 1 if you have the <limits.h> header file. */ +#ifndef HAVE_LIMITS_H +#define HAVE_LIMITS_H 1 +#endif + +/* Define to 1 if the system has the type `long long'. */ +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG 1 +#endif + +/* Define to 1 if you have the `memmove' function. */ +#ifndef HAVE_MEMMOVE +#define HAVE_MEMMOVE 1 +#endif + +/* Define to 1 if you have the <memory.h> header file. */ +#ifndef HAVE_MEMORY_H +#define HAVE_MEMORY_H 1 +#endif + +/* Define to 1 if you have the <stdint.h> header file. */ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H 1 +#endif + +/* Define to 1 if you have the <stdlib.h> header file. */ +#ifndef HAVE_STDLIB_H +#define HAVE_STDLIB_H 1 +#endif + +/* Define to 1 if you have the `strerror' function. */ +#ifndef HAVE_STRERROR +#define HAVE_STRERROR 1 +#endif + +/* Define to 1 if you have the <string> header file. */ +#ifndef HAVE_STRING +#define HAVE_STRING 1 +#endif + +/* Define to 1 if you have the <strings.h> header file. */ +#ifndef HAVE_STRINGS_H +#define HAVE_STRINGS_H 1 +#endif + +/* Define to 1 if you have the <string.h> header file. */ +#ifndef HAVE_STRING_H +#define HAVE_STRING_H 1 +#endif + +/* Define to 1 if you have the `strtoll' function. */ +#ifndef HAVE_STRTOLL +#define HAVE_STRTOLL 1 +#endif + +/* Define to 1 if you have the `strtoq' function. */ +#ifndef HAVE_STRTOQ +#define HAVE_STRTOQ 1 +#endif + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#ifndef HAVE_SYS_STAT_H +#define HAVE_SYS_STAT_H 1 +#endif + +/* Define to 1 if you have the <sys/types.h> header file. */ +#ifndef HAVE_SYS_TYPES_H +#define HAVE_SYS_TYPES_H 1 +#endif + +/* Define to 1 if you have the <type_traits.h> header file. */ +/* #undef HAVE_TYPE_TRAITS_H */ + +/* Define to 1 if you have the <unistd.h> header file. */ +#ifndef HAVE_UNISTD_H +#define HAVE_UNISTD_H 1 +#endif + +/* Define to 1 if the system has the type `unsigned long long'. */ +#ifndef HAVE_UNSIGNED_LONG_LONG +#define HAVE_UNSIGNED_LONG_LONG 1 +#endif + +/* Define to 1 if you have the <windows.h> header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the `_strtoi64' function. */ +/* #undef HAVE__STRTOI64 */ + +/* The value of LINK_SIZE determines the number of bytes used to store links + as offsets within the compiled regex. The default is 2, which allows for + compiled patterns up to 64K long. This covers the vast majority of cases. + However, PCRE can also be compiled to use 3 or 4 bytes instead. This allows + for longer patterns in extreme cases. On systems that support it, + "configure" can be used to override this default. */ +#ifndef LINK_SIZE +#define LINK_SIZE 2 +#endif + +/* The value of MATCH_LIMIT determines the default number of times the + internal match() function can be called during a single execution of + pcre_exec(). There is a runtime interface for setting a different limit. + The limit exists in order to catch runaway regular expressions that take + for ever to determine that they do not match. The default is set very large + so that it does not accidentally catch legitimate cases. On systems that + support it, "configure" can be used to override this default default. */ +#ifndef MATCH_LIMIT +#define MATCH_LIMIT 10000000 +#endif + +/* The above limit applies to all calls of match(), whether or not they + increase the recursion depth. In some environments it is desirable to limit + the depth of recursive calls of match() more strictly, in order to restrict + the maximum amount of stack (or heap, if NO_RECURSE is defined) that is + used. The value of MATCH_LIMIT_RECURSION applies only to recursive calls of + match(). To have any useful effect, it must be less than the value of + MATCH_LIMIT. The default is to use the same value as MATCH_LIMIT. There is + a runtime method for setting a different limit. On systems that support it, + "configure" can be used to override the default. */ +#ifndef MATCH_LIMIT_RECURSION +#define MATCH_LIMIT_RECURSION MATCH_LIMIT +#endif + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#ifndef MAX_NAME_COUNT +#define MAX_NAME_COUNT 10000 +#endif + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#ifndef MAX_NAME_SIZE +#define MAX_NAME_SIZE 32 +#endif + +/* The value of NEWLINE determines the newline character sequence. On systems + that support it, "configure" can be used to override the default, which is + 10. The possible values are 10 (LF), 13 (CR), 3338 (CRLF), -1 (ANY), or -2 + (ANYCRLF). */ +#ifndef NEWLINE +#define NEWLINE 10 +#endif + +/* PCRE uses recursive function calls to handle backtracking while matching. + This can sometimes be a problem on systems that have stacks of limited + size. Define NO_RECURSE to get a version that doesn't use recursion in the + match() function; instead it creates its own stack by steam using + pcre_recurse_malloc() to obtain memory from the heap. For more detail, see + the comments and other stuff just above the match() function. On systems + that support it, "configure" can be used to set this in the Makefile (use + --disable-stack-for-recursion). */ +/* #undef NO_RECURSE */ + +/* Name of package */ +#define PACKAGE "pcre" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "PCRE" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "PCRE 7.4" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "pcre" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "7.4" + + +/* If you are compiling for a system other than a Unix-like system or + Win32, and it needs some magic to be inserted before the definition + of a function that is exported by the library, define this macro to + contain the relevant magic. If you do not define this macro, it + defaults to "extern" for a C compiler and "extern C" for a C++ + compiler on non-Win32 systems. This macro apears at the start of + every exported function that is part of the external API. It does + not appear on functions that are "external" in the C sense, but + which are internal to the library. */ +/* #undef PCRE_EXP_DEFN */ + +/* Define if linking statically (TODO: make nice with Libtool) */ +/* #undef PCRE_STATIC */ + +/* When calling PCRE via the POSIX interface, additional working storage is + required for holding the pointers to capturing substrings because PCRE + requires three integers per substring, whereas the POSIX interface provides + only two. If the number of expected substrings is small, the wrapper + function uses space on the stack, because this is faster than using + malloc() for each call. The threshold above which the stack is no longer + used is defined by POSIX_MALLOC_THRESHOLD. On systems that support it, + "configure" can be used to override this default. */ +#ifndef POSIX_MALLOC_THRESHOLD +#define POSIX_MALLOC_THRESHOLD 10 +#endif + +/* Define to 1 if you have the ANSI C header files. */ +#ifndef STDC_HEADERS +#define STDC_HEADERS 1 +#endif + +/* Define to enable support for Unicode properties */ +/* #undef SUPPORT_UCP */ + +/* Define to enable support for the UTF-8 Unicode encoding. */ +/* #undef SUPPORT_UTF8 */ + +/* Version number of package */ +#ifndef VERSION +#define VERSION "7.4" +#endif + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +/* #undef size_t */ diff --git a/pcre-7.4/config.h.in b/pcre-7.4/config.h.in new file mode 100644 index 0000000..c4ea3c4 --- /dev/null +++ b/pcre-7.4/config.h.in @@ -0,0 +1,219 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + + +/* On Unix-like systems config.h.in is converted by "configure" into config.h. +Some other environments also support the use of "configure". PCRE is written in +Standard C, but there are a few non-standard things it can cope with, allowing +it to run on SunOS4 and other "close to standard" systems. + +If you are going to build PCRE "by hand" on a system without "configure" you +should copy the distributed config.h.generic to config.h, and then set up the +macro definitions the way you need them. You must then add -DHAVE_CONFIG_H to +all of your compile commands, so that config.h is included at the start of +every source. + +Alternatively, you can avoid editing by using -D on the compiler command line +to set the macro values. In this case, you do not have to set -DHAVE_CONFIG_H. + +PCRE uses memmove() if HAVE_MEMMOVE is set to 1; otherwise it uses bcopy() if +HAVE_BCOPY is set to 1. If your system has neither bcopy() nor memmove(), set +them both to 0; an emulation function will be used. */ + +/* By default, the \R escape sequence matches any Unicode line ending + character or sequence of characters. If BSR_ANYCRLF is defined, this is + changed so that backslash-R matches only CR, LF, or CRLF. The build- time + default can be overridden by the user of PCRE at runtime. On systems that + support it, "configure" can be used to override the default. */ +#undef BSR_ANYCRLF + +/* If you are compiling for a system that uses EBCDIC instead of ASCII + character codes, define this macro as 1. On systems that can use + "configure", this can be done via --enable-ebcdic. */ +#undef EBCDIC + +/* Define to 1 if you have the `bcopy' function. */ +#undef HAVE_BCOPY + +/* Define to 1 if you have the <bits/type_traits.h> header file. */ +#undef HAVE_BITS_TYPE_TRAITS_H + +/* Define to 1 if you have the <dirent.h> header file. */ +#undef HAVE_DIRENT_H + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the <limits.h> header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if the system has the type `long long'. */ +#undef HAVE_LONG_LONG + +/* Define to 1 if you have the `memmove' function. */ +#undef HAVE_MEMMOVE + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the <string> header file. */ +#undef HAVE_STRING + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strtoll' function. */ +#undef HAVE_STRTOLL + +/* Define to 1 if you have the `strtoq' function. */ +#undef HAVE_STRTOQ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <type_traits.h> header file. */ +#undef HAVE_TYPE_TRAITS_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if the system has the type `unsigned long long'. */ +#undef HAVE_UNSIGNED_LONG_LONG + +/* Define to 1 if you have the <windows.h> header file. */ +#undef HAVE_WINDOWS_H + +/* Define to 1 if you have the `_strtoi64' function. */ +#undef HAVE__STRTOI64 + +/* The value of LINK_SIZE determines the number of bytes used to store links + as offsets within the compiled regex. The default is 2, which allows for + compiled patterns up to 64K long. This covers the vast majority of cases. + However, PCRE can also be compiled to use 3 or 4 bytes instead. This allows + for longer patterns in extreme cases. On systems that support it, + "configure" can be used to override this default. */ +#undef LINK_SIZE + +/* The value of MATCH_LIMIT determines the default number of times the + internal match() function can be called during a single execution of + pcre_exec(). There is a runtime interface for setting a different limit. + The limit exists in order to catch runaway regular expressions that take + for ever to determine that they do not match. The default is set very large + so that it does not accidentally catch legitimate cases. On systems that + support it, "configure" can be used to override this default default. */ +#undef MATCH_LIMIT + +/* The above limit applies to all calls of match(), whether or not they + increase the recursion depth. In some environments it is desirable to limit + the depth of recursive calls of match() more strictly, in order to restrict + the maximum amount of stack (or heap, if NO_RECURSE is defined) that is + used. The value of MATCH_LIMIT_RECURSION applies only to recursive calls of + match(). To have any useful effect, it must be less than the value of + MATCH_LIMIT. The default is to use the same value as MATCH_LIMIT. There is + a runtime method for setting a different limit. On systems that support it, + "configure" can be used to override the default. */ +#undef MATCH_LIMIT_RECURSION + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#undef MAX_NAME_COUNT + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#undef MAX_NAME_SIZE + +/* The value of NEWLINE determines the newline character sequence. On systems + that support it, "configure" can be used to override the default, which is + 10. The possible values are 10 (LF), 13 (CR), 3338 (CRLF), -1 (ANY), or -2 + (ANYCRLF). */ +#undef NEWLINE + +/* PCRE uses recursive function calls to handle backtracking while matching. + This can sometimes be a problem on systems that have stacks of limited + size. Define NO_RECURSE to get a version that doesn't use recursion in the + match() function; instead it creates its own stack by steam using + pcre_recurse_malloc() to obtain memory from the heap. For more detail, see + the comments and other stuff just above the match() function. On systems + that support it, "configure" can be used to set this in the Makefile (use + --disable-stack-for-recursion). */ +#undef NO_RECURSE + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + + +/* If you are compiling for a system other than a Unix-like system or + Win32, and it needs some magic to be inserted before the definition + of a function that is exported by the library, define this macro to + contain the relevant magic. If you do not define this macro, it + defaults to "extern" for a C compiler and "extern C" for a C++ + compiler on non-Win32 systems. This macro apears at the start of + every exported function that is part of the external API. It does + not appear on functions that are "external" in the C sense, but + which are internal to the library. */ +#undef PCRE_EXP_DEFN + +/* Define if linking statically (TODO: make nice with Libtool) */ +#undef PCRE_STATIC + +/* When calling PCRE via the POSIX interface, additional working storage is + required for holding the pointers to capturing substrings because PCRE + requires three integers per substring, whereas the POSIX interface provides + only two. If the number of expected substrings is small, the wrapper + function uses space on the stack, because this is faster than using + malloc() for each call. The threshold above which the stack is no longer + used is defined by POSIX_MALLOC_THRESHOLD. On systems that support it, + "configure" can be used to override this default. */ +#undef POSIX_MALLOC_THRESHOLD + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to enable support for Unicode properties */ +#undef SUPPORT_UCP + +/* Define to enable support for the UTF-8 Unicode encoding. */ +#undef SUPPORT_UTF8 + +/* Version number of package */ +#undef VERSION + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#undef size_t diff --git a/pcre-7.4/dftables.c b/pcre-7.4/dftables.c new file mode 100644 index 0000000..67bca53 --- /dev/null +++ b/pcre-7.4/dftables.c @@ -0,0 +1,199 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This is a freestanding support program to generate a file containing +character tables for PCRE. The tables are built according to the current +locale. Now that pcre_maketables is a function visible to the outside world, we +make use of its code from here in order to be consistent. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <locale.h> + +#include "pcre_internal.h" + +#define DFTABLES /* pcre_maketables.c notices this */ +#include "pcre_maketables.c" + + +int main(int argc, char **argv) +{ +FILE *f; +int i = 1; +const unsigned char *tables; +const unsigned char *base_of_tables; + +/* By default, the default C locale is used rather than what the building user +happens to have set. However, if the -L option is given, set the locale from +the LC_xxx environment variables. */ + +if (argc > 1 && strcmp(argv[1], "-L") == 0) + { + setlocale(LC_ALL, ""); /* Set from environment variables */ + i++; + } + +if (argc < i + 1) + { + fprintf(stderr, "dftables: one filename argument is required\n"); + return 1; + } + +tables = pcre_maketables(); +base_of_tables = tables; + +f = fopen(argv[i], "wb"); +if (f == NULL) + { + fprintf(stderr, "dftables: failed to open %s for writing\n", argv[1]); + return 1; + } + +/* There are several fprintf() calls here, because gcc in pedantic mode +complains about the very long string otherwise. */ + +fprintf(f, + "/*************************************************\n" + "* Perl-Compatible Regular Expressions *\n" + "*************************************************/\n\n" + "/* This file was automatically written by the dftables auxiliary\n" + "program. It contains character tables that are used when no external\n" + "tables are passed to PCRE by the application that calls it. The tables\n" + "are used only for characters whose code values are less than 256.\n\n"); +fprintf(f, + "The following #includes are present because without them gcc 4.x may remove\n" + "the array definition from the final binary if PCRE is built into a static\n" + "library and dead code stripping is activated. This leads to link errors.\n" + "Pulling in the header ensures that the array gets flagged as \"someone\n" + "outside this compilation unit might reference this\" and so it will always\n" + "be supplied to the linker. */\n\n" + "#ifdef HAVE_CONFIG_H\n" + "#include \"config.h\"\n" + "#endif\n\n" + "#include \"pcre_internal.h\"\n\n"); +fprintf(f, + "const unsigned char _pcre_default_tables[] = {\n\n" + "/* This table is a lower casing table. */\n\n"); + +fprintf(f, " "); +for (i = 0; i < 256; i++) + { + if ((i & 7) == 0 && i != 0) fprintf(f, "\n "); + fprintf(f, "%3d", *tables++); + if (i != 255) fprintf(f, ","); + } +fprintf(f, ",\n\n"); + +fprintf(f, "/* This table is a case flipping table. */\n\n"); + +fprintf(f, " "); +for (i = 0; i < 256; i++) + { + if ((i & 7) == 0 && i != 0) fprintf(f, "\n "); + fprintf(f, "%3d", *tables++); + if (i != 255) fprintf(f, ","); + } +fprintf(f, ",\n\n"); + +fprintf(f, + "/* This table contains bit maps for various character classes.\n" + "Each map is 32 bytes long and the bits run from the least\n" + "significant end of each byte. The classes that have their own\n" + "maps are: space, xdigit, digit, upper, lower, word, graph\n" + "print, punct, and cntrl. Other classes are built from combinations. */\n\n"); + +fprintf(f, " "); +for (i = 0; i < cbit_length; i++) + { + if ((i & 7) == 0 && i != 0) + { + if ((i & 31) == 0) fprintf(f, "\n"); + fprintf(f, "\n "); + } + fprintf(f, "0x%02x", *tables++); + if (i != cbit_length - 1) fprintf(f, ","); + } +fprintf(f, ",\n\n"); + +fprintf(f, + "/* This table identifies various classes of character by individual bits:\n" + " 0x%02x white space character\n" + " 0x%02x letter\n" + " 0x%02x decimal digit\n" + " 0x%02x hexadecimal digit\n" + " 0x%02x alphanumeric or '_'\n" + " 0x%02x regular expression metacharacter or binary zero\n*/\n\n", + ctype_space, ctype_letter, ctype_digit, ctype_xdigit, ctype_word, + ctype_meta); + +fprintf(f, " "); +for (i = 0; i < 256; i++) + { + if ((i & 7) == 0 && i != 0) + { + fprintf(f, " /* "); + if (isprint(i-8)) fprintf(f, " %c -", i-8); + else fprintf(f, "%3d-", i-8); + if (isprint(i-1)) fprintf(f, " %c ", i-1); + else fprintf(f, "%3d", i-1); + fprintf(f, " */\n "); + } + fprintf(f, "0x%02x", *tables++); + if (i != 255) fprintf(f, ","); + } + +fprintf(f, "};/* "); +if (isprint(i-8)) fprintf(f, " %c -", i-8); + else fprintf(f, "%3d-", i-8); +if (isprint(i-1)) fprintf(f, " %c ", i-1); + else fprintf(f, "%3d", i-1); +fprintf(f, " */\n\n/* End of pcre_chartables.c */\n"); + +fclose(f); +free((void *)base_of_tables); +return 0; +} + +/* End of dftables.c */ diff --git a/pcre-7.4/pcre.h b/pcre-7.4/pcre.h new file mode 100644 index 0000000..701699b --- /dev/null +++ b/pcre-7.4/pcre.h @@ -0,0 +1,304 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This is the public header file for the PCRE library, to be #included by +applications that call the PCRE functions. + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifndef _PCRE_H +#define _PCRE_H + +/* The current PCRE version information. */ + +#define PCRE_MAJOR 7 +#define PCRE_MINOR 4 +#define PCRE_PRERELEASE +#define PCRE_DATE 2007-09-21 + +/* When an application links to a PCRE DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE, the appropriate +export setting is defined in pcre_internal.h, which includes this file. So we +don't change existing definitions of PCRE_EXP_DECL and PCRECPP_EXP_DECL. */ + +/*#if defined(_WIN32) && !defined(PCRE_STATIC) +#error why are we here? +# ifndef PCRE_EXP_DECL +# define PCRE_EXP_DECL extern __declspec(dllimport) +# endif +# ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern __declspec(dllimport) +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN __declspec(dllimport) +# endif +# endif +#endif*/ + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCRE_EXP_DECL +# ifdef __cplusplus +# define PCRE_EXP_DECL extern "C" +# else +# define PCRE_EXP_DECL extern +# endif +#endif + +#ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN +# endif +#endif + +/* Have to include stdlib.h in order to ensure that size_t is defined; +it is needed here for malloc. */ + +#include <stdlib.h> + +/* Allow for C++ users */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Options */ + +#define PCRE_CASELESS 0x00000001 +#define PCRE_MULTILINE 0x00000002 +#define PCRE_DOTALL 0x00000004 +#define PCRE_EXTENDED 0x00000008 +#define PCRE_ANCHORED 0x00000010 +#define PCRE_DOLLAR_ENDONLY 0x00000020 +#define PCRE_EXTRA 0x00000040 +#define PCRE_NOTBOL 0x00000080 +#define PCRE_NOTEOL 0x00000100 +#define PCRE_UNGREEDY 0x00000200 +#define PCRE_NOTEMPTY 0x00000400 +#define PCRE_UTF8 0x00000800 +#define PCRE_NO_AUTO_CAPTURE 0x00001000 +#define PCRE_NO_UTF8_CHECK 0x00002000 +#define PCRE_AUTO_CALLOUT 0x00004000 +#define PCRE_PARTIAL 0x00008000 +#define PCRE_DFA_SHORTEST 0x00010000 +#define PCRE_DFA_RESTART 0x00020000 +#define PCRE_FIRSTLINE 0x00040000 +#define PCRE_DUPNAMES 0x00080000 +#define PCRE_NEWLINE_CR 0x00100000 +#define PCRE_NEWLINE_LF 0x00200000 +#define PCRE_NEWLINE_CRLF 0x00300000 +#define PCRE_NEWLINE_ANY 0x00400000 +#define PCRE_NEWLINE_ANYCRLF 0x00500000 +#define PCRE_BSR_ANYCRLF 0x00800000 +#define PCRE_BSR_UNICODE 0x01000000 + +/* Exec-time and get/set-time error codes */ + +#define PCRE_ERROR_NOMATCH (-1) +#define PCRE_ERROR_NULL (-2) +#define PCRE_ERROR_BADOPTION (-3) +#define PCRE_ERROR_BADMAGIC (-4) +#define PCRE_ERROR_UNKNOWN_OPCODE (-5) +#define PCRE_ERROR_UNKNOWN_NODE (-5) /* For backward compatibility */ +#define PCRE_ERROR_NOMEMORY (-6) +#define PCRE_ERROR_NOSUBSTRING (-7) +#define PCRE_ERROR_MATCHLIMIT (-8) +#define PCRE_ERROR_CALLOUT (-9) /* Never used by PCRE itself */ +#define PCRE_ERROR_BADUTF8 (-10) +#define PCRE_ERROR_BADUTF8_OFFSET (-11) +#define PCRE_ERROR_PARTIAL (-12) +#define PCRE_ERROR_BADPARTIAL (-13) +#define PCRE_ERROR_INTERNAL (-14) +#define PCRE_ERROR_BADCOUNT (-15) +#define PCRE_ERROR_DFA_UITEM (-16) +#define PCRE_ERROR_DFA_UCOND (-17) +#define PCRE_ERROR_DFA_UMLIMIT (-18) +#define PCRE_ERROR_DFA_WSSIZE (-19) +#define PCRE_ERROR_DFA_RECURSE (-20) +#define PCRE_ERROR_RECURSIONLIMIT (-21) +#define PCRE_ERROR_NULLWSLIMIT (-22) /* No longer actually used */ +#define PCRE_ERROR_BADNEWLINE (-23) + +/* Request types for pcre_fullinfo() */ + +#define PCRE_INFO_OPTIONS 0 +#define PCRE_INFO_SIZE 1 +#define PCRE_INFO_CAPTURECOUNT 2 +#define PCRE_INFO_BACKREFMAX 3 +#define PCRE_INFO_FIRSTBYTE 4 +#define PCRE_INFO_FIRSTCHAR 4 /* For backwards compatibility */ +#define PCRE_INFO_FIRSTTABLE 5 +#define PCRE_INFO_LASTLITERAL 6 +#define PCRE_INFO_NAMEENTRYSIZE 7 +#define PCRE_INFO_NAMECOUNT 8 +#define PCRE_INFO_NAMETABLE 9 +#define PCRE_INFO_STUDYSIZE 10 +#define PCRE_INFO_DEFAULT_TABLES 11 +#define PCRE_INFO_OKPARTIAL 12 +#define PCRE_INFO_JCHANGED 13 +#define PCRE_INFO_HASCRORLF 14 + +/* Request types for pcre_config(). Do not re-arrange, in order to remain +compatible. */ + +#define PCRE_CONFIG_UTF8 0 +#define PCRE_CONFIG_NEWLINE 1 +#define PCRE_CONFIG_LINK_SIZE 2 +#define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD 3 +#define PCRE_CONFIG_MATCH_LIMIT 4 +#define PCRE_CONFIG_STACKRECURSE 5 +#define PCRE_CONFIG_UNICODE_PROPERTIES 6 +#define PCRE_CONFIG_MATCH_LIMIT_RECURSION 7 +#define PCRE_CONFIG_BSR 8 + +/* Bit flags for the pcre_extra structure. Do not re-arrange or redefine +these bits, just add new ones on the end, in order to remain compatible. */ + +#define PCRE_EXTRA_STUDY_DATA 0x0001 +#define PCRE_EXTRA_MATCH_LIMIT 0x0002 +#define PCRE_EXTRA_CALLOUT_DATA 0x0004 +#define PCRE_EXTRA_TABLES 0x0008 +#define PCRE_EXTRA_MATCH_LIMIT_RECURSION 0x0010 + +/* Types */ + +struct real_pcre; /* declaration; the definition is private */ +typedef struct real_pcre pcre; + +/* When PCRE is compiled as a C++ library, the subject pointer type can be +replaced with a custom type. For conventional use, the public interface is a +const char *. */ + +#ifndef PCRE_SPTR +#define PCRE_SPTR const char * +#endif + +/* The structure for passing additional data to pcre_exec(). This is defined in +such as way as to be extensible. Always add new fields at the end, in order to +remain compatible. */ + +typedef struct pcre_extra { + unsigned long int flags; /* Bits for which fields are set */ + void *study_data; /* Opaque data from pcre_study() */ + unsigned long int match_limit; /* Maximum number of calls to match() */ + void *callout_data; /* Data passed back in callouts */ + const unsigned char *tables; /* Pointer to character tables */ + unsigned long int match_limit_recursion; /* Max recursive calls to match() */ +} pcre_extra; + +/* The structure for passing out data via the pcre_callout_function. We use a +structure so that new fields can be added on the end in future versions, +without changing the API of the function, thereby allowing old databases to work +without modification. */ + +typedef struct pcre_callout_block { + int version; /* Identifies version of block */ + /* ------------------------ Version 0 ------------------------------- */ + int callout_number; /* Number compiled into pattern */ + int *offset_vector; /* The offset vector */ + PCRE_SPTR subject; /* The subject being matched */ + int subject_length; /* The length of the subject */ + int start_match; /* Offset to start of this match attempt */ + int current_position; /* Where we currently are in the subject */ + int capture_top; /* Max current capture */ + int capture_last; /* Most recently closed capture */ + void *callout_data; /* Data passed in with the call */ + /* ------------------- Added for Version 1 -------------------------- */ + int pattern_position; /* Offset to next item in the pattern */ + int next_item_length; /* Length of next item in the pattern */ + /* ------------------------------------------------------------------ */ +} pcre_callout_block; + +/* Indirection for store get and free functions. These can be set to +alternative malloc/free functions if required. Special ones are used in the +non-recursive case for "frames". There is also an optional callout function +that is triggered by the (?) regex item. For Virtual Pascal, these definitions +have to take another form. */ + +#ifndef VPCOMPAT +PCRE_EXP_DECL void *(*pcre_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_free)(void *); +PCRE_EXP_DECL void *(*pcre_stack_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_stack_free)(void *); +PCRE_EXP_DECL int (*pcre_callout)(pcre_callout_block *); +#else /* VPCOMPAT */ +PCRE_EXP_DECL void *pcre_malloc(size_t); +PCRE_EXP_DECL void pcre_free(void *); +PCRE_EXP_DECL void *pcre_stack_malloc(size_t); +PCRE_EXP_DECL void pcre_stack_free(void *); +PCRE_EXP_DECL int pcre_callout(pcre_callout_block *); +#endif /* VPCOMPAT */ + +/* Exported PCRE functions */ + +PCRE_EXP_DECL pcre *pcre_compile(const char *, int, const char **, int *, + const unsigned char *); +PCRE_EXP_DECL pcre *pcre_compile2(const char *, int, int *, const char **, + int *, const unsigned char *); +PCRE_EXP_DECL int pcre_config(int, void *); +PCRE_EXP_DECL int pcre_copy_named_substring(const pcre *, const char *, + int *, int, const char *, char *, int); +PCRE_EXP_DECL int pcre_copy_substring(const char *, int *, int, int, char *, + int); +PCRE_EXP_DECL int pcre_dfa_exec(const pcre *, const pcre_extra *, + const char *, int, int, int, int *, int , int *, int); +PCRE_EXP_DECL int pcre_exec(const pcre *, const pcre_extra *, PCRE_SPTR, + int, int, int, int *, int); +PCRE_EXP_DECL void pcre_free_substring(const char *); +PCRE_EXP_DECL void pcre_free_substring_list(const char **); +PCRE_EXP_DECL int pcre_fullinfo(const pcre *, const pcre_extra *, int, + void *); +PCRE_EXP_DECL int pcre_get_named_substring(const pcre *, const char *, + int *, int, const char *, const char **); +PCRE_EXP_DECL int pcre_get_stringnumber(const pcre *, const char *); +PCRE_EXP_DECL int pcre_get_stringtable_entries(const pcre *, const char *, + char **, char **); +PCRE_EXP_DECL int pcre_get_substring(const char *, int *, int, int, + const char **); +PCRE_EXP_DECL int pcre_get_substring_list(const char *, int *, int, + const char ***); +PCRE_EXP_DECL int pcre_info(const pcre *, int *, int *); +PCRE_EXP_DECL const unsigned char *pcre_maketables(void); +PCRE_EXP_DECL int pcre_refcount(pcre *, int); +PCRE_EXP_DECL pcre_extra *pcre_study(const pcre *, int, const char **); +PCRE_EXP_DECL const char *pcre_version(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* End of pcre.h */ diff --git a/pcre-7.4/pcre.h.generic b/pcre-7.4/pcre.h.generic new file mode 100644 index 0000000..58a83c3 --- /dev/null +++ b/pcre-7.4/pcre.h.generic @@ -0,0 +1,303 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This is the public header file for the PCRE library, to be #included by +applications that call the PCRE functions. + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifndef _PCRE_H +#define _PCRE_H + +/* The current PCRE version information. */ + +#define PCRE_MAJOR 7 +#define PCRE_MINOR 4 +#define PCRE_PRERELEASE +#define PCRE_DATE 2007-09-21 + +/* When an application links to a PCRE DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE, the appropriate +export setting is defined in pcre_internal.h, which includes this file. So we +don't change existing definitions of PCRE_EXP_DECL and PCRECPP_EXP_DECL. */ + +#if defined(_WIN32) && !defined(PCRE_STATIC) +# ifndef PCRE_EXP_DECL +# define PCRE_EXP_DECL extern __declspec(dllimport) +# endif +# ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern __declspec(dllimport) +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN __declspec(dllimport) +# endif +# endif +#endif + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCRE_EXP_DECL +# ifdef __cplusplus +# define PCRE_EXP_DECL extern "C" +# else +# define PCRE_EXP_DECL extern +# endif +#endif + +#ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN +# endif +#endif + +/* Have to include stdlib.h in order to ensure that size_t is defined; +it is needed here for malloc. */ + +#include <stdlib.h> + +/* Allow for C++ users */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Options */ + +#define PCRE_CASELESS 0x00000001 +#define PCRE_MULTILINE 0x00000002 +#define PCRE_DOTALL 0x00000004 +#define PCRE_EXTENDED 0x00000008 +#define PCRE_ANCHORED 0x00000010 +#define PCRE_DOLLAR_ENDONLY 0x00000020 +#define PCRE_EXTRA 0x00000040 +#define PCRE_NOTBOL 0x00000080 +#define PCRE_NOTEOL 0x00000100 +#define PCRE_UNGREEDY 0x00000200 +#define PCRE_NOTEMPTY 0x00000400 +#define PCRE_UTF8 0x00000800 +#define PCRE_NO_AUTO_CAPTURE 0x00001000 +#define PCRE_NO_UTF8_CHECK 0x00002000 +#define PCRE_AUTO_CALLOUT 0x00004000 +#define PCRE_PARTIAL 0x00008000 +#define PCRE_DFA_SHORTEST 0x00010000 +#define PCRE_DFA_RESTART 0x00020000 +#define PCRE_FIRSTLINE 0x00040000 +#define PCRE_DUPNAMES 0x00080000 +#define PCRE_NEWLINE_CR 0x00100000 +#define PCRE_NEWLINE_LF 0x00200000 +#define PCRE_NEWLINE_CRLF 0x00300000 +#define PCRE_NEWLINE_ANY 0x00400000 +#define PCRE_NEWLINE_ANYCRLF 0x00500000 +#define PCRE_BSR_ANYCRLF 0x00800000 +#define PCRE_BSR_UNICODE 0x01000000 + +/* Exec-time and get/set-time error codes */ + +#define PCRE_ERROR_NOMATCH (-1) +#define PCRE_ERROR_NULL (-2) +#define PCRE_ERROR_BADOPTION (-3) +#define PCRE_ERROR_BADMAGIC (-4) +#define PCRE_ERROR_UNKNOWN_OPCODE (-5) +#define PCRE_ERROR_UNKNOWN_NODE (-5) /* For backward compatibility */ +#define PCRE_ERROR_NOMEMORY (-6) +#define PCRE_ERROR_NOSUBSTRING (-7) +#define PCRE_ERROR_MATCHLIMIT (-8) +#define PCRE_ERROR_CALLOUT (-9) /* Never used by PCRE itself */ +#define PCRE_ERROR_BADUTF8 (-10) +#define PCRE_ERROR_BADUTF8_OFFSET (-11) +#define PCRE_ERROR_PARTIAL (-12) +#define PCRE_ERROR_BADPARTIAL (-13) +#define PCRE_ERROR_INTERNAL (-14) +#define PCRE_ERROR_BADCOUNT (-15) +#define PCRE_ERROR_DFA_UITEM (-16) +#define PCRE_ERROR_DFA_UCOND (-17) +#define PCRE_ERROR_DFA_UMLIMIT (-18) +#define PCRE_ERROR_DFA_WSSIZE (-19) +#define PCRE_ERROR_DFA_RECURSE (-20) +#define PCRE_ERROR_RECURSIONLIMIT (-21) +#define PCRE_ERROR_NULLWSLIMIT (-22) /* No longer actually used */ +#define PCRE_ERROR_BADNEWLINE (-23) + +/* Request types for pcre_fullinfo() */ + +#define PCRE_INFO_OPTIONS 0 +#define PCRE_INFO_SIZE 1 +#define PCRE_INFO_CAPTURECOUNT 2 +#define PCRE_INFO_BACKREFMAX 3 +#define PCRE_INFO_FIRSTBYTE 4 +#define PCRE_INFO_FIRSTCHAR 4 /* For backwards compatibility */ +#define PCRE_INFO_FIRSTTABLE 5 +#define PCRE_INFO_LASTLITERAL 6 +#define PCRE_INFO_NAMEENTRYSIZE 7 +#define PCRE_INFO_NAMECOUNT 8 +#define PCRE_INFO_NAMETABLE 9 +#define PCRE_INFO_STUDYSIZE 10 +#define PCRE_INFO_DEFAULT_TABLES 11 +#define PCRE_INFO_OKPARTIAL 12 +#define PCRE_INFO_JCHANGED 13 +#define PCRE_INFO_HASCRORLF 14 + +/* Request types for pcre_config(). Do not re-arrange, in order to remain +compatible. */ + +#define PCRE_CONFIG_UTF8 0 +#define PCRE_CONFIG_NEWLINE 1 +#define PCRE_CONFIG_LINK_SIZE 2 +#define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD 3 +#define PCRE_CONFIG_MATCH_LIMIT 4 +#define PCRE_CONFIG_STACKRECURSE 5 +#define PCRE_CONFIG_UNICODE_PROPERTIES 6 +#define PCRE_CONFIG_MATCH_LIMIT_RECURSION 7 +#define PCRE_CONFIG_BSR 8 + +/* Bit flags for the pcre_extra structure. Do not re-arrange or redefine +these bits, just add new ones on the end, in order to remain compatible. */ + +#define PCRE_EXTRA_STUDY_DATA 0x0001 +#define PCRE_EXTRA_MATCH_LIMIT 0x0002 +#define PCRE_EXTRA_CALLOUT_DATA 0x0004 +#define PCRE_EXTRA_TABLES 0x0008 +#define PCRE_EXTRA_MATCH_LIMIT_RECURSION 0x0010 + +/* Types */ + +struct real_pcre; /* declaration; the definition is private */ +typedef struct real_pcre pcre; + +/* When PCRE is compiled as a C++ library, the subject pointer type can be +replaced with a custom type. For conventional use, the public interface is a +const char *. */ + +#ifndef PCRE_SPTR +#define PCRE_SPTR const char * +#endif + +/* The structure for passing additional data to pcre_exec(). This is defined in +such as way as to be extensible. Always add new fields at the end, in order to +remain compatible. */ + +typedef struct pcre_extra { + unsigned long int flags; /* Bits for which fields are set */ + void *study_data; /* Opaque data from pcre_study() */ + unsigned long int match_limit; /* Maximum number of calls to match() */ + void *callout_data; /* Data passed back in callouts */ + const unsigned char *tables; /* Pointer to character tables */ + unsigned long int match_limit_recursion; /* Max recursive calls to match() */ +} pcre_extra; + +/* The structure for passing out data via the pcre_callout_function. We use a +structure so that new fields can be added on the end in future versions, +without changing the API of the function, thereby allowing old clients to work +without modification. */ + +typedef struct pcre_callout_block { + int version; /* Identifies version of block */ + /* ------------------------ Version 0 ------------------------------- */ + int callout_number; /* Number compiled into pattern */ + int *offset_vector; /* The offset vector */ + PCRE_SPTR subject; /* The subject being matched */ + int subject_length; /* The length of the subject */ + int start_match; /* Offset to start of this match attempt */ + int current_position; /* Where we currently are in the subject */ + int capture_top; /* Max current capture */ + int capture_last; /* Most recently closed capture */ + void *callout_data; /* Data passed in with the call */ + /* ------------------- Added for Version 1 -------------------------- */ + int pattern_position; /* Offset to next item in the pattern */ + int next_item_length; /* Length of next item in the pattern */ + /* ------------------------------------------------------------------ */ +} pcre_callout_block; + +/* Indirection for store get and free functions. These can be set to +alternative malloc/free functions if required. Special ones are used in the +non-recursive case for "frames". There is also an optional callout function +that is triggered by the (?) regex item. For Virtual Pascal, these definitions +have to take another form. */ + +#ifndef VPCOMPAT +PCRE_EXP_DECL void *(*pcre_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_free)(void *); +PCRE_EXP_DECL void *(*pcre_stack_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_stack_free)(void *); +PCRE_EXP_DECL int (*pcre_callout)(pcre_callout_block *); +#else /* VPCOMPAT */ +PCRE_EXP_DECL void *pcre_malloc(size_t); +PCRE_EXP_DECL void pcre_free(void *); +PCRE_EXP_DECL void *pcre_stack_malloc(size_t); +PCRE_EXP_DECL void pcre_stack_free(void *); +PCRE_EXP_DECL int pcre_callout(pcre_callout_block *); +#endif /* VPCOMPAT */ + +/* Exported PCRE functions */ + +PCRE_EXP_DECL pcre *pcre_compile(const char *, int, const char **, int *, + const unsigned char *); +PCRE_EXP_DECL pcre *pcre_compile2(const char *, int, int *, const char **, + int *, const unsigned char *); +PCRE_EXP_DECL int pcre_config(int, void *); +PCRE_EXP_DECL int pcre_copy_named_substring(const pcre *, const char *, + int *, int, const char *, char *, int); +PCRE_EXP_DECL int pcre_copy_substring(const char *, int *, int, int, char *, + int); +PCRE_EXP_DECL int pcre_dfa_exec(const pcre *, const pcre_extra *, + const char *, int, int, int, int *, int , int *, int); +PCRE_EXP_DECL int pcre_exec(const pcre *, const pcre_extra *, PCRE_SPTR, + int, int, int, int *, int); +PCRE_EXP_DECL void pcre_free_substring(const char *); +PCRE_EXP_DECL void pcre_free_substring_list(const char **); +PCRE_EXP_DECL int pcre_fullinfo(const pcre *, const pcre_extra *, int, + void *); +PCRE_EXP_DECL int pcre_get_named_substring(const pcre *, const char *, + int *, int, const char *, const char **); +PCRE_EXP_DECL int pcre_get_stringnumber(const pcre *, const char *); +PCRE_EXP_DECL int pcre_get_stringtable_entries(const pcre *, const char *, + char **, char **); +PCRE_EXP_DECL int pcre_get_substring(const char *, int *, int, int, + const char **); +PCRE_EXP_DECL int pcre_get_substring_list(const char *, int *, int, + const char ***); +PCRE_EXP_DECL int pcre_info(const pcre *, int *, int *); +PCRE_EXP_DECL const unsigned char *pcre_maketables(void); +PCRE_EXP_DECL int pcre_refcount(pcre *, int); +PCRE_EXP_DECL pcre_extra *pcre_study(const pcre *, int, const char **); +PCRE_EXP_DECL const char *pcre_version(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* End of pcre.h */ diff --git a/pcre-7.4/pcre.h.in b/pcre-7.4/pcre.h.in new file mode 100644 index 0000000..8bebbb4 --- /dev/null +++ b/pcre-7.4/pcre.h.in @@ -0,0 +1,303 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This is the public header file for the PCRE library, to be #included by +applications that call the PCRE functions. + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifndef _PCRE_H +#define _PCRE_H + +/* The current PCRE version information. */ + +#define PCRE_MAJOR @PCRE_MAJOR@ +#define PCRE_MINOR @PCRE_MINOR@ +#define PCRE_PRERELEASE @PCRE_PRERELEASE@ +#define PCRE_DATE @PCRE_DATE@ + +/* When an application links to a PCRE DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE, the appropriate +export setting is defined in pcre_internal.h, which includes this file. So we +don't change existing definitions of PCRE_EXP_DECL and PCRECPP_EXP_DECL. */ + +#if defined(_WIN32) && !defined(PCRE_STATIC) +# ifndef PCRE_EXP_DECL +# define PCRE_EXP_DECL extern __declspec(dllimport) +# endif +# ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern __declspec(dllimport) +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN __declspec(dllimport) +# endif +# endif +#endif + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCRE_EXP_DECL +# ifdef __cplusplus +# define PCRE_EXP_DECL extern "C" +# else +# define PCRE_EXP_DECL extern +# endif +#endif + +#ifdef __cplusplus +# ifndef PCRECPP_EXP_DECL +# define PCRECPP_EXP_DECL extern +# endif +# ifndef PCRECPP_EXP_DEFN +# define PCRECPP_EXP_DEFN +# endif +#endif + +/* Have to include stdlib.h in order to ensure that size_t is defined; +it is needed here for malloc. */ + +#include <stdlib.h> + +/* Allow for C++ users */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Options */ + +#define PCRE_CASELESS 0x00000001 +#define PCRE_MULTILINE 0x00000002 +#define PCRE_DOTALL 0x00000004 +#define PCRE_EXTENDED 0x00000008 +#define PCRE_ANCHORED 0x00000010 +#define PCRE_DOLLAR_ENDONLY 0x00000020 +#define PCRE_EXTRA 0x00000040 +#define PCRE_NOTBOL 0x00000080 +#define PCRE_NOTEOL 0x00000100 +#define PCRE_UNGREEDY 0x00000200 +#define PCRE_NOTEMPTY 0x00000400 +#define PCRE_UTF8 0x00000800 +#define PCRE_NO_AUTO_CAPTURE 0x00001000 +#define PCRE_NO_UTF8_CHECK 0x00002000 +#define PCRE_AUTO_CALLOUT 0x00004000 +#define PCRE_PARTIAL 0x00008000 +#define PCRE_DFA_SHORTEST 0x00010000 +#define PCRE_DFA_RESTART 0x00020000 +#define PCRE_FIRSTLINE 0x00040000 +#define PCRE_DUPNAMES 0x00080000 +#define PCRE_NEWLINE_CR 0x00100000 +#define PCRE_NEWLINE_LF 0x00200000 +#define PCRE_NEWLINE_CRLF 0x00300000 +#define PCRE_NEWLINE_ANY 0x00400000 +#define PCRE_NEWLINE_ANYCRLF 0x00500000 +#define PCRE_BSR_ANYCRLF 0x00800000 +#define PCRE_BSR_UNICODE 0x01000000 + +/* Exec-time and get/set-time error codes */ + +#define PCRE_ERROR_NOMATCH (-1) +#define PCRE_ERROR_NULL (-2) +#define PCRE_ERROR_BADOPTION (-3) +#define PCRE_ERROR_BADMAGIC (-4) +#define PCRE_ERROR_UNKNOWN_OPCODE (-5) +#define PCRE_ERROR_UNKNOWN_NODE (-5) /* For backward compatibility */ +#define PCRE_ERROR_NOMEMORY (-6) +#define PCRE_ERROR_NOSUBSTRING (-7) +#define PCRE_ERROR_MATCHLIMIT (-8) +#define PCRE_ERROR_CALLOUT (-9) /* Never used by PCRE itself */ +#define PCRE_ERROR_BADUTF8 (-10) +#define PCRE_ERROR_BADUTF8_OFFSET (-11) +#define PCRE_ERROR_PARTIAL (-12) +#define PCRE_ERROR_BADPARTIAL (-13) +#define PCRE_ERROR_INTERNAL (-14) +#define PCRE_ERROR_BADCOUNT (-15) +#define PCRE_ERROR_DFA_UITEM (-16) +#define PCRE_ERROR_DFA_UCOND (-17) +#define PCRE_ERROR_DFA_UMLIMIT (-18) +#define PCRE_ERROR_DFA_WSSIZE (-19) +#define PCRE_ERROR_DFA_RECURSE (-20) +#define PCRE_ERROR_RECURSIONLIMIT (-21) +#define PCRE_ERROR_NULLWSLIMIT (-22) /* No longer actually used */ +#define PCRE_ERROR_BADNEWLINE (-23) + +/* Request types for pcre_fullinfo() */ + +#define PCRE_INFO_OPTIONS 0 +#define PCRE_INFO_SIZE 1 +#define PCRE_INFO_CAPTURECOUNT 2 +#define PCRE_INFO_BACKREFMAX 3 +#define PCRE_INFO_FIRSTBYTE 4 +#define PCRE_INFO_FIRSTCHAR 4 /* For backwards compatibility */ +#define PCRE_INFO_FIRSTTABLE 5 +#define PCRE_INFO_LASTLITERAL 6 +#define PCRE_INFO_NAMEENTRYSIZE 7 +#define PCRE_INFO_NAMECOUNT 8 +#define PCRE_INFO_NAMETABLE 9 +#define PCRE_INFO_STUDYSIZE 10 +#define PCRE_INFO_DEFAULT_TABLES 11 +#define PCRE_INFO_OKPARTIAL 12 +#define PCRE_INFO_JCHANGED 13 +#define PCRE_INFO_HASCRORLF 14 + +/* Request types for pcre_config(). Do not re-arrange, in order to remain +compatible. */ + +#define PCRE_CONFIG_UTF8 0 +#define PCRE_CONFIG_NEWLINE 1 +#define PCRE_CONFIG_LINK_SIZE 2 +#define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD 3 +#define PCRE_CONFIG_MATCH_LIMIT 4 +#define PCRE_CONFIG_STACKRECURSE 5 +#define PCRE_CONFIG_UNICODE_PROPERTIES 6 +#define PCRE_CONFIG_MATCH_LIMIT_RECURSION 7 +#define PCRE_CONFIG_BSR 8 + +/* Bit flags for the pcre_extra structure. Do not re-arrange or redefine +these bits, just add new ones on the end, in order to remain compatible. */ + +#define PCRE_EXTRA_STUDY_DATA 0x0001 +#define PCRE_EXTRA_MATCH_LIMIT 0x0002 +#define PCRE_EXTRA_CALLOUT_DATA 0x0004 +#define PCRE_EXTRA_TABLES 0x0008 +#define PCRE_EXTRA_MATCH_LIMIT_RECURSION 0x0010 + +/* Types */ + +struct real_pcre; /* declaration; the definition is private */ +typedef struct real_pcre pcre; + +/* When PCRE is compiled as a C++ library, the subject pointer type can be +replaced with a custom type. For conventional use, the public interface is a +const char *. */ + +#ifndef PCRE_SPTR +#define PCRE_SPTR const char * +#endif + +/* The structure for passing additional data to pcre_exec(). This is defined in +such as way as to be extensible. Always add new fields at the end, in order to +remain compatible. */ + +typedef struct pcre_extra { + unsigned long int flags; /* Bits for which fields are set */ + void *study_data; /* Opaque data from pcre_study() */ + unsigned long int match_limit; /* Maximum number of calls to match() */ + void *callout_data; /* Data passed back in callouts */ + const unsigned char *tables; /* Pointer to character tables */ + unsigned long int match_limit_recursion; /* Max recursive calls to match() */ +} pcre_extra; + +/* The structure for passing out data via the pcre_callout_function. We use a +structure so that new fields can be added on the end in future versions, +without changing the API of the function, thereby allowing old clients to work +without modification. */ + +typedef struct pcre_callout_block { + int version; /* Identifies version of block */ + /* ------------------------ Version 0 ------------------------------- */ + int callout_number; /* Number compiled into pattern */ + int *offset_vector; /* The offset vector */ + PCRE_SPTR subject; /* The subject being matched */ + int subject_length; /* The length of the subject */ + int start_match; /* Offset to start of this match attempt */ + int current_position; /* Where we currently are in the subject */ + int capture_top; /* Max current capture */ + int capture_last; /* Most recently closed capture */ + void *callout_data; /* Data passed in with the call */ + /* ------------------- Added for Version 1 -------------------------- */ + int pattern_position; /* Offset to next item in the pattern */ + int next_item_length; /* Length of next item in the pattern */ + /* ------------------------------------------------------------------ */ +} pcre_callout_block; + +/* Indirection for store get and free functions. These can be set to +alternative malloc/free functions if required. Special ones are used in the +non-recursive case for "frames". There is also an optional callout function +that is triggered by the (?) regex item. For Virtual Pascal, these definitions +have to take another form. */ + +#ifndef VPCOMPAT +PCRE_EXP_DECL void *(*pcre_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_free)(void *); +PCRE_EXP_DECL void *(*pcre_stack_malloc)(size_t); +PCRE_EXP_DECL void (*pcre_stack_free)(void *); +PCRE_EXP_DECL int (*pcre_callout)(pcre_callout_block *); +#else /* VPCOMPAT */ +PCRE_EXP_DECL void *pcre_malloc(size_t); +PCRE_EXP_DECL void pcre_free(void *); +PCRE_EXP_DECL void *pcre_stack_malloc(size_t); +PCRE_EXP_DECL void pcre_stack_free(void *); +PCRE_EXP_DECL int pcre_callout(pcre_callout_block *); +#endif /* VPCOMPAT */ + +/* Exported PCRE functions */ + +PCRE_EXP_DECL pcre *pcre_compile(const char *, int, const char **, int *, + const unsigned char *); +PCRE_EXP_DECL pcre *pcre_compile2(const char *, int, int *, const char **, + int *, const unsigned char *); +PCRE_EXP_DECL int pcre_config(int, void *); +PCRE_EXP_DECL int pcre_copy_named_substring(const pcre *, const char *, + int *, int, const char *, char *, int); +PCRE_EXP_DECL int pcre_copy_substring(const char *, int *, int, int, char *, + int); +PCRE_EXP_DECL int pcre_dfa_exec(const pcre *, const pcre_extra *, + const char *, int, int, int, int *, int , int *, int); +PCRE_EXP_DECL int pcre_exec(const pcre *, const pcre_extra *, PCRE_SPTR, + int, int, int, int *, int); +PCRE_EXP_DECL void pcre_free_substring(const char *); +PCRE_EXP_DECL void pcre_free_substring_list(const char **); +PCRE_EXP_DECL int pcre_fullinfo(const pcre *, const pcre_extra *, int, + void *); +PCRE_EXP_DECL int pcre_get_named_substring(const pcre *, const char *, + int *, int, const char *, const char **); +PCRE_EXP_DECL int pcre_get_stringnumber(const pcre *, const char *); +PCRE_EXP_DECL int pcre_get_stringtable_entries(const pcre *, const char *, + char **, char **); +PCRE_EXP_DECL int pcre_get_substring(const char *, int *, int, int, + const char **); +PCRE_EXP_DECL int pcre_get_substring_list(const char *, int *, int, + const char ***); +PCRE_EXP_DECL int pcre_info(const pcre *, int *, int *); +PCRE_EXP_DECL const unsigned char *pcre_maketables(void); +PCRE_EXP_DECL int pcre_refcount(pcre *, int); +PCRE_EXP_DECL pcre_extra *pcre_study(const pcre *, int, const char **); +PCRE_EXP_DECL const char *pcre_version(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* End of pcre.h */ diff --git a/pcre-7.4/pcre_chartables.c b/pcre-7.4/pcre_chartables.c new file mode 100644 index 0000000..ae45db0 --- /dev/null +++ b/pcre-7.4/pcre_chartables.c @@ -0,0 +1,198 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This file contains character tables that are used when no external tables +are passed to PCRE by the application that calls it. The tables are used only +for characters whose code values are less than 256. + +This is a default version of the tables that assumes ASCII encoding. A program +called dftables (which is distributed with PCRE) can be used to build +alternative versions of this file. This is necessary if you are running in an +EBCDIC environment, or if you want to default to a different encoding, for +example ISO-8859-1. When dftables is run, it creates these tables in the +current locale. If PCRE is configured with --enable-rebuild-chartables, this +happens automatically. + +The following #includes are present because without the gcc 4.x may remove the +array definition from the final binary if PCRE is built into a static library +and dead code stripping is activated. This leads to link errors. Pulling in the +header ensures that the array gets flagged as "someone outside this compilation +unit might reference this" and so it will always be supplied to the linker. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + +const unsigned char _pcre_default_tables[] = { + +/* This table is a lower casing table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table is a case flipping table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table contains bit maps for various character classes. Each map is 32 +bytes long and the bits run from the least significant end of each byte. The +classes that have their own maps are: space, xdigit, digit, upper, lower, word, +graph, print, punct, and cntrl. Other classes are built from combinations. */ + + 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, + 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +/* This table identifies various classes of character by individual bits: + 0x01 white space character + 0x02 letter + 0x04 decimal digit + 0x08 hexadecimal digit + 0x10 alphanumeric or '_' + 0x80 regular expression metacharacter or binary zero +*/ + + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x01,0x01,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ + 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x80,0x10, /* X - _ */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +/* End of pcre_chartables.c */ diff --git a/pcre-7.4/pcre_chartables.c.dist b/pcre-7.4/pcre_chartables.c.dist new file mode 100644 index 0000000..ae45db0 --- /dev/null +++ b/pcre-7.4/pcre_chartables.c.dist @@ -0,0 +1,198 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This file contains character tables that are used when no external tables +are passed to PCRE by the application that calls it. The tables are used only +for characters whose code values are less than 256. + +This is a default version of the tables that assumes ASCII encoding. A program +called dftables (which is distributed with PCRE) can be used to build +alternative versions of this file. This is necessary if you are running in an +EBCDIC environment, or if you want to default to a different encoding, for +example ISO-8859-1. When dftables is run, it creates these tables in the +current locale. If PCRE is configured with --enable-rebuild-chartables, this +happens automatically. + +The following #includes are present because without the gcc 4.x may remove the +array definition from the final binary if PCRE is built into a static library +and dead code stripping is activated. This leads to link errors. Pulling in the +header ensures that the array gets flagged as "someone outside this compilation +unit might reference this" and so it will always be supplied to the linker. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + +const unsigned char _pcre_default_tables[] = { + +/* This table is a lower casing table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table is a case flipping table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table contains bit maps for various character classes. Each map is 32 +bytes long and the bits run from the least significant end of each byte. The +classes that have their own maps are: space, xdigit, digit, upper, lower, word, +graph, print, punct, and cntrl. Other classes are built from combinations. */ + + 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, + 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +/* This table identifies various classes of character by individual bits: + 0x01 white space character + 0x02 letter + 0x04 decimal digit + 0x08 hexadecimal digit + 0x10 alphanumeric or '_' + 0x80 regular expression metacharacter or binary zero +*/ + + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x01,0x01,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ + 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x80,0x10, /* X - _ */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +/* End of pcre_chartables.c */ diff --git a/pcre-7.4/pcre_compile.c b/pcre-7.4/pcre_compile.c new file mode 100644 index 0000000..3994781 --- /dev/null +++ b/pcre-7.4/pcre_compile.c @@ -0,0 +1,6145 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_compile(), along with +supporting internal functions that are not used by other modules. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK cd /* Block containing newline information */ +#define PSSTART start_pattern /* Field containing processed string start */ +#define PSEND end_pattern /* Field containing processed string end */ + +#include "pcre_internal.h" + + +/* When DEBUG is defined, we need the pcre_printint() function, which is also +used by pcretest. DEBUG is not defined when building a production library. */ + +#ifdef DEBUG +#include "pcre_printint.src" +#endif + + +/* Macro for setting individual bits in class bitmaps. */ + +#define SETBIT(a,b) a[b/8] |= (1 << (b%8)) + +/* Maximum length value to check against when making sure that the integer that +holds the compiled pattern length does not overflow. We make it a bit less than +INT_MAX to allow for adding in group terminating bytes, so that we don't have +to check them every time. */ + +#define OFLOW_MAX (INT_MAX - 20) + + +/************************************************* +* Code parameters and static tables * +*************************************************/ + +/* This value specifies the size of stack workspace that is used during the +first pre-compile phase that determines how much memory is required. The regex +is partly compiled into this space, but the compiled parts are discarded as +soon as they can be, so that hopefully there will never be an overrun. The code +does, however, check for an overrun. The largest amount I've seen used is 218, +so this number is very generous. + +The same workspace is used during the second, actual compile phase for +remembering forward references to groups so that they can be filled in at the +end. Each entry in this list occupies LINK_SIZE bytes, so even when LINK_SIZE +is 4 there is plenty of room. */ + +#define COMPILE_WORK_SIZE (4096) + + +/* Table for handling escaped characters in the range '0'-'z'. Positive returns +are simple data values; negative values are for special things like \d and so +on. Zero means further processing is needed (for things like \x), or the escape +is invalid. */ + +#ifndef EBCDIC /* This is the "normal" table for ASCII systems */ +static const short int escapes[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - 7 */ + 0, 0, ':', ';', '<', '=', '>', '?', /* 8 - ? */ + '@', -ESC_A, -ESC_B, -ESC_C, -ESC_D, -ESC_E, 0, -ESC_G, /* @ - G */ +-ESC_H, 0, 0, -ESC_K, 0, 0, 0, 0, /* H - O */ +-ESC_P, -ESC_Q, -ESC_R, -ESC_S, 0, 0, -ESC_V, -ESC_W, /* P - W */ +-ESC_X, 0, -ESC_Z, '[', '\\', ']', '^', '_', /* X - _ */ + '`', 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0, /* ` - g */ +-ESC_h, 0, 0, -ESC_k, 0, 0, ESC_n, 0, /* h - o */ +-ESC_p, 0, ESC_r, -ESC_s, ESC_tee, 0, -ESC_v, -ESC_w, /* p - w */ + 0, 0, -ESC_z /* x - z */ +}; + +#else /* This is the "abnormal" table for EBCDIC systems */ +static const short int escapes[] = { +/* 48 */ 0, 0, 0, '.', '<', '(', '+', '|', +/* 50 */ '&', 0, 0, 0, 0, 0, 0, 0, +/* 58 */ 0, 0, '!', '$', '*', ')', ';', '~', +/* 60 */ '-', '/', 0, 0, 0, 0, 0, 0, +/* 68 */ 0, 0, '|', ',', '%', '_', '>', '?', +/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* 78 */ 0, '`', ':', '#', '@', '\'', '=', '"', +/* 80 */ 0, 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0, +/* 88 */-ESC_h, 0, 0, '{', 0, 0, 0, 0, +/* 90 */ 0, 0, -ESC_k, 'l', 0, ESC_n, 0, -ESC_p, +/* 98 */ 0, ESC_r, 0, '}', 0, 0, 0, 0, +/* A0 */ 0, '~', -ESC_s, ESC_tee, 0,-ESC_v, -ESC_w, 0, +/* A8 */ 0,-ESC_z, 0, 0, 0, '[', 0, 0, +/* B0 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* B8 */ 0, 0, 0, 0, 0, ']', '=', '-', +/* C0 */ '{',-ESC_A, -ESC_B, -ESC_C, -ESC_D,-ESC_E, 0, -ESC_G, +/* C8 */-ESC_H, 0, 0, 0, 0, 0, 0, 0, +/* D0 */ '}', 0, -ESC_K, 0, 0, 0, 0, -ESC_P, +/* D8 */-ESC_Q,-ESC_R, 0, 0, 0, 0, 0, 0, +/* E0 */ '\\', 0, -ESC_S, 0, 0,-ESC_V, -ESC_W, -ESC_X, +/* E8 */ 0,-ESC_Z, 0, 0, 0, 0, 0, 0, +/* F0 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* F8 */ 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif + + +/* Table of special "verbs" like (*PRUNE). This is a short table, so it is +searched linearly. Put all the names into a single string, in order to reduce +the number of relocations when a shared library is dynamically linked. */ + +typedef struct verbitem { + int len; + int op; +} verbitem; + +static const char verbnames[] = + "ACCEPT\0" + "COMMIT\0" + "F\0" + "FAIL\0" + "PRUNE\0" + "SKIP\0" + "THEN"; + +static verbitem verbs[] = { + { 6, OP_ACCEPT }, + { 6, OP_COMMIT }, + { 1, OP_FAIL }, + { 4, OP_FAIL }, + { 5, OP_PRUNE }, + { 4, OP_SKIP }, + { 4, OP_THEN } +}; + +static int verbcount = sizeof(verbs)/sizeof(verbitem); + + +/* Tables of names of POSIX character classes and their lengths. The names are +now all in a single string, to reduce the number of relocations when a shared +library is dynamically loaded. The list of lengths is terminated by a zero +length entry. The first three must be alpha, lower, upper, as this is assumed +for handling case independence. */ + +static const char posix_names[] = + "alpha\0" "lower\0" "upper\0" "alnum\0" "ascii\0" "blank\0" + "cntrl\0" "digit\0" "graph\0" "print\0" "punct\0" "space\0" + "word\0" "xdigit"; + +static const uschar posix_name_lengths[] = { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 6, 0 }; + +/* Table of class bit maps for each POSIX class. Each class is formed from a +base map, with an optional addition or removal of another map. Then, for some +classes, there is some additional tweaking: for [:blank:] the vertical space +characters are removed, and for [:alpha:] and [:alnum:] the underscore +character is removed. The triples in the table consist of the base map offset, +second map offset or -1 if no second map, and a non-negative value for map +addition or a negative value for map subtraction (if there are two maps). The +absolute value of the third field has these meanings: 0 => no tweaking, 1 => +remove vertical space characters, 2 => remove underscore. */ + +static const int posix_class_maps[] = { + cbit_word, cbit_digit, -2, /* alpha */ + cbit_lower, -1, 0, /* lower */ + cbit_upper, -1, 0, /* upper */ + cbit_word, -1, 2, /* alnum - word without underscore */ + cbit_print, cbit_cntrl, 0, /* ascii */ + cbit_space, -1, 1, /* blank - a GNU extension */ + cbit_cntrl, -1, 0, /* cntrl */ + cbit_digit, -1, 0, /* digit */ + cbit_graph, -1, 0, /* graph */ + cbit_print, -1, 0, /* print */ + cbit_punct, -1, 0, /* punct */ + cbit_space, -1, 0, /* space */ + cbit_word, -1, 0, /* word - a Perl extension */ + cbit_xdigit,-1, 0 /* xdigit */ +}; + + +#define STRING(a) # a +#define XSTRING(s) STRING(s) + +/* The texts of compile-time error messages. These are "char *" because they +are passed to the outside world. Do not ever re-use any error number, because +they are documented. Always add a new error instead. Messages marked DEAD below +are no longer used. This used to be a table of strings, but in order to reduce +the number of relocations needed when a shared library is loaded dynamically, +it is now one long string. We cannot use a table of offsets, because the +lengths of inserts such as XSTRING(MAX_NAME_SIZE) are not known. Instead, we +simply count through to the one we want - this isn't a performance issue +because these strings are used only when there is a compilation error. */ + +static const char error_texts[] = + "no error\0" + "\\ at end of pattern\0" + "\\c at end of pattern\0" + "unrecognized character follows \\\0" + "numbers out of order in {} quantifier\0" + /* 5 */ + "number too big in {} quantifier\0" + "missing terminating ] for character class\0" + "invalid escape sequence in character class\0" + "range out of order in character class\0" + "nothing to repeat\0" + /* 10 */ + "operand of unlimited repeat could match the empty string\0" /** DEAD **/ + "internal error: unexpected repeat\0" + "unrecognized character after (?\0" + "POSIX named classes are supported only within a class\0" + "missing )\0" + /* 15 */ + "reference to non-existent subpattern\0" + "erroffset passed as NULL\0" + "unknown option bit(s) set\0" + "missing ) after comment\0" + "parentheses nested too deeply\0" /** DEAD **/ + /* 20 */ + "regular expression is too large\0" + "failed to get memory\0" + "unmatched parentheses\0" + "internal error: code overflow\0" + "unrecognized character after (?<\0" + /* 25 */ + "lookbehind assertion is not fixed length\0" + "malformed number or name after (?(\0" + "conditional group contains more than two branches\0" + "assertion expected after (?(\0" + "(?R or (?[+-]digits must be followed by )\0" + /* 30 */ + "unknown POSIX class name\0" + "POSIX collating elements are not supported\0" + "this version of PCRE is not compiled with PCRE_UTF8 support\0" + "spare error\0" /** DEAD **/ + "character value in \\x{...} sequence is too large\0" + /* 35 */ + "invalid condition (?(0)\0" + "\\C not allowed in lookbehind assertion\0" + "PCRE does not support \\L, \\l, \\N, \\U, or \\u\0" + "number after (?C is > 255\0" + "closing ) for (?C expected\0" + /* 40 */ + "recursive call could loop indefinitely\0" + "unrecognized character after (?P\0" + "syntax error in subpattern name (missing terminator)\0" + "two named subpatterns have the same name\0" + "invalid UTF-8 string\0" + /* 45 */ + "support for \\P, \\p, and \\X has not been compiled\0" + "malformed \\P or \\p sequence\0" + "unknown property name after \\P or \\p\0" + "subpattern name is too long (maximum " XSTRING(MAX_NAME_SIZE) " characters)\0" + "too many named subpatterns (maximum " XSTRING(MAX_NAME_COUNT) ")\0" + /* 50 */ + "repeated subpattern is too long\0" /** DEAD **/ + "octal value is greater than \\377 (not in UTF-8 mode)\0" + "internal error: overran compiling workspace\0" + "internal error: previously-checked referenced subpattern not found\0" + "DEFINE group contains more than one branch\0" + /* 55 */ + "repeating a DEFINE group is not allowed\0" + "inconsistent NEWLINE options\0" + "\\g is not followed by a braced name or an optionally braced non-zero number\0" + "(?+ or (?- or (?(+ or (?(- must be followed by a non-zero number\0" + "(*VERB) with an argument is not supported\0" + /* 60 */ + "(*VERB) not recognized\0" + "number is too big"; + + +/* Table to identify digits and hex digits. This is used when compiling +patterns. Note that the tables in chartables are dependent on the locale, and +may mark arbitrary characters as digits - but the PCRE compiling code expects +to handle only 0-9, a-z, and A-Z as digits when compiling. That is why we have +a private table here. It costs 256 bytes, but it is a lot faster than doing +character value tests (at least in some simple cases I timed), and in some +applications one wants PCRE to compile efficiently as well as match +efficiently. + +For convenience, we use the same bit definitions as in chartables: + + 0x04 decimal digit + 0x08 hexadecimal digit + +Then we can use ctype_digit and ctype_xdigit in the code. */ + +#ifndef EBCDIC /* This is the "normal" case, for ASCII systems */ +static const unsigned char digitab[] = + { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - ' */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ( - / */ + 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 */ + 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, /* 8 - ? */ + 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* @ - G */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H - O */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* P - W */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* X - _ */ + 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* ` - g */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h - o */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* p - w */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +#else /* This is the "abnormal" case, for EBCDIC systems */ +static const unsigned char digitab[] = + { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 10 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 32- 39 20 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 30 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 40 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 72- | */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 50 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 88- 95 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 60 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 104- ? */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 70 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */ + 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* 128- g 80 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144- p 90 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160- x A0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 B0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* { - G C0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* } - P D0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* \ - X E0 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */ + 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 F0 */ + 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */ + +static const unsigned char ebcdic_chartab[] = { /* chartable partial dup */ + 0x80,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 0- 7 */ + 0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 32- 39 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */ + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 */ + 0x00,0x00,0x00,0x80,0x00,0x80,0x80,0x80, /* 72- | */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 */ + 0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00, /* 88- 95 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 */ + 0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x80, /* 104- ? */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* 128- g */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */ + 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* 144- p */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */ + 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* 160- x */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */ + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 */ + 0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x80,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* { - G */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */ + 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* } - P */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */ + 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* \ - X */ + 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */ +#endif + + +/* Definition to allow mutual recursion */ + +static BOOL + compile_regex(int, int, uschar **, const uschar **, int *, BOOL, BOOL, int, + int *, int *, branch_chain *, compile_data *, int *); + + + +/************************************************* +* Find an error text * +*************************************************/ + +/* The error texts are now all in one long string, to save on relocations. As +some of the text is of unknown length, we can't use a table of offsets. +Instead, just count through the strings. This is not a performance issue +because it happens only when there has been a compilation error. + +Argument: the error number +Returns: pointer to the error string +*/ + +static const char * +find_error_text(int n) +{ +const char *s = error_texts; +for (; n > 0; n--) while (*s++ != 0); +return s; +} + + +/************************************************* +* Handle escapes * +*************************************************/ + +/* This function is called when a \ has been encountered. It either returns a +positive value for a simple escape such as \n, or a negative value which +encodes one of the more complicated things such as \d. A backreference to group +n is returned as -(ESC_REF + n); ESC_REF is the highest ESC_xxx macro. When +UTF-8 is enabled, a positive value greater than 255 may be returned. On entry, +ptr is pointing at the \. On exit, it is on the final character of the escape +sequence. + +Arguments: + ptrptr points to the pattern position pointer + errorcodeptr points to the errorcode variable + bracount number of previous extracting brackets + options the options bits + isclass TRUE if inside a character class + +Returns: zero or positive => a data character + negative => a special escape sequence + on error, errorcodeptr is set +*/ + +static int +check_escape(const uschar **ptrptr, int *errorcodeptr, int bracount, + int options, BOOL isclass) +{ +BOOL utf8 = (options & PCRE_UTF8) != 0; +const uschar *ptr = *ptrptr + 1; +int c, i; + +GETCHARINCTEST(c, ptr); /* Get character value, increment pointer */ +ptr--; /* Set pointer back to the last byte */ + +/* If backslash is at the end of the pattern, it's an error. */ + +if (c == 0) *errorcodeptr = ERR1; + +/* Non-alphamerics are literals. For digits or letters, do an initial lookup in +a table. A non-zero result is something that can be returned immediately. +Otherwise further processing may be required. */ + +#ifndef EBCDIC /* ASCII coding */ +else if (c < '0' || c > 'z') {} /* Not alphameric */ +else if ((i = escapes[c - '0']) != 0) c = i; + +#else /* EBCDIC coding */ +else if (c < 'a' || (ebcdic_chartab[c] & 0x0E) == 0) {} /* Not alphameric */ +else if ((i = escapes[c - 0x48]) != 0) c = i; +#endif + +/* Escapes that need further processing, or are illegal. */ + +else + { + const uschar *oldptr; + BOOL braced, negated; + + switch (c) + { + /* A number of Perl escapes are not handled by PCRE. We give an explicit + error. */ + + case 'l': + case 'L': + case 'N': + case 'u': + case 'U': + *errorcodeptr = ERR37; + break; + + /* \g must be followed by a number, either plain or braced. If positive, it + is an absolute backreference. If negative, it is a relative backreference. + This is a Perl 5.10 feature. Perl 5.10 also supports \g{name} as a + reference to a named group. This is part of Perl's movement towards a + unified syntax for back references. As this is synonymous with \k{name}, we + fudge it up by pretending it really was \k. */ + + case 'g': + if (ptr[1] == '{') + { + const uschar *p; + for (p = ptr+2; *p != 0 && *p != '}'; p++) + if (*p != '-' && (digitab[*p] & ctype_digit) == 0) break; + if (*p != 0 && *p != '}') + { + c = -ESC_k; + break; + } + braced = TRUE; + ptr++; + } + else braced = FALSE; + + if (ptr[1] == '-') + { + negated = TRUE; + ptr++; + } + else negated = FALSE; + + c = 0; + while ((digitab[ptr[1]] & ctype_digit) != 0) + c = c * 10 + *(++ptr) - '0'; + + if (c < 0) + { + *errorcodeptr = ERR61; + break; + } + + if (c == 0 || (braced && *(++ptr) != '}')) + { + *errorcodeptr = ERR57; + break; + } + + if (negated) + { + if (c > bracount) + { + *errorcodeptr = ERR15; + break; + } + c = bracount - (c - 1); + } + + c = -(ESC_REF + c); + break; + + /* The handling of escape sequences consisting of a string of digits + starting with one that is not zero is not straightforward. By experiment, + the way Perl works seems to be as follows: + + Outside a character class, the digits are read as a decimal number. If the + number is less than 10, or if there are that many previous extracting + left brackets, then it is a back reference. Otherwise, up to three octal + digits are read to form an escaped byte. Thus \123 is likely to be octal + 123 (cf \0123, which is octal 012 followed by the literal 3). If the octal + value is greater than 377, the least significant 8 bits are taken. Inside a + character class, \ followed by a digit is always an octal number. */ + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + + if (!isclass) + { + oldptr = ptr; + c -= '0'; + while ((digitab[ptr[1]] & ctype_digit) != 0) + c = c * 10 + *(++ptr) - '0'; + if (c < 0) + { + *errorcodeptr = ERR61; + break; + } + if (c < 10 || c <= bracount) + { + c = -(ESC_REF + c); + break; + } + ptr = oldptr; /* Put the pointer back and fall through */ + } + + /* Handle an octal number following \. If the first digit is 8 or 9, Perl + generates a binary zero byte and treats the digit as a following literal. + Thus we have to pull back the pointer by one. */ + + if ((c = *ptr) >= '8') + { + ptr--; + c = 0; + break; + } + + /* \0 always starts an octal number, but we may drop through to here with a + larger first octal digit. The original code used just to take the least + significant 8 bits of octal numbers (I think this is what early Perls used + to do). Nowadays we allow for larger numbers in UTF-8 mode, but no more + than 3 octal digits. */ + + case '0': + c -= '0'; + while(i++ < 2 && ptr[1] >= '0' && ptr[1] <= '7') + c = c * 8 + *(++ptr) - '0'; + if (!utf8 && c > 255) *errorcodeptr = ERR51; + break; + + /* \x is complicated. \x{ddd} is a character number which can be greater + than 0xff in utf8 mode, but only if the ddd are hex digits. If not, { is + treated as a data character. */ + + case 'x': + if (ptr[1] == '{') + { + const uschar *pt = ptr + 2; + int count = 0; + + c = 0; + while ((digitab[*pt] & ctype_xdigit) != 0) + { + register int cc = *pt++; + if (c == 0 && cc == '0') continue; /* Leading zeroes */ + count++; + +#ifndef EBCDIC /* ASCII coding */ + if (cc >= 'a') cc -= 32; /* Convert to upper case */ + c = (c << 4) + cc - ((cc < 'A')? '0' : ('A' - 10)); +#else /* EBCDIC coding */ + if (cc >= 'a' && cc <= 'z') cc += 64; /* Convert to upper case */ + c = (c << 4) + cc - ((cc >= '0')? '0' : ('A' - 10)); +#endif + } + + if (*pt == '}') + { + if (c < 0 || count > (utf8? 8 : 2)) *errorcodeptr = ERR34; + ptr = pt; + break; + } + + /* If the sequence of hex digits does not end with '}', then we don't + recognize this construct; fall through to the normal \x handling. */ + } + + /* Read just a single-byte hex-defined char */ + + c = 0; + while (i++ < 2 && (digitab[ptr[1]] & ctype_xdigit) != 0) + { + int cc; /* Some compilers don't like ++ */ + cc = *(++ptr); /* in initializers */ +#ifndef EBCDIC /* ASCII coding */ + if (cc >= 'a') cc -= 32; /* Convert to upper case */ + c = c * 16 + cc - ((cc < 'A')? '0' : ('A' - 10)); +#else /* EBCDIC coding */ + if (cc <= 'z') cc += 64; /* Convert to upper case */ + c = c * 16 + cc - ((cc >= '0')? '0' : ('A' - 10)); +#endif + } + break; + + /* For \c, a following letter is upper-cased; then the 0x40 bit is flipped. + This coding is ASCII-specific, but then the whole concept of \cx is + ASCII-specific. (However, an EBCDIC equivalent has now been added.) */ + + case 'c': + c = *(++ptr); + if (c == 0) + { + *errorcodeptr = ERR2; + break; + } + +#ifndef EBCDIC /* ASCII coding */ + if (c >= 'a' && c <= 'z') c -= 32; + c ^= 0x40; +#else /* EBCDIC coding */ + if (c >= 'a' && c <= 'z') c += 64; + c ^= 0xC0; +#endif + break; + + /* PCRE_EXTRA enables extensions to Perl in the matter of escapes. Any + other alphameric following \ is an error if PCRE_EXTRA was set; otherwise, + for Perl compatibility, it is a literal. This code looks a bit odd, but + there used to be some cases other than the default, and there may be again + in future, so I haven't "optimized" it. */ + + default: + if ((options & PCRE_EXTRA) != 0) switch(c) + { + default: + *errorcodeptr = ERR3; + break; + } + break; + } + } + +*ptrptr = ptr; +return c; +} + + + +#ifdef SUPPORT_UCP +/************************************************* +* Handle \P and \p * +*************************************************/ + +/* This function is called after \P or \p has been encountered, provided that +PCRE is compiled with support for Unicode properties. On entry, ptrptr is +pointing at the P or p. On exit, it is pointing at the final character of the +escape sequence. + +Argument: + ptrptr points to the pattern position pointer + negptr points to a boolean that is set TRUE for negation else FALSE + dptr points to an int that is set to the detailed property value + errorcodeptr points to the error code variable + +Returns: type value from ucp_type_table, or -1 for an invalid type +*/ + +static int +get_ucp(const uschar **ptrptr, BOOL *negptr, int *dptr, int *errorcodeptr) +{ +int c, i, bot, top; +const uschar *ptr = *ptrptr; +char name[32]; + +c = *(++ptr); +if (c == 0) goto ERROR_RETURN; + +*negptr = FALSE; + +/* \P or \p can be followed by a name in {}, optionally preceded by ^ for +negation. */ + +if (c == '{') + { + if (ptr[1] == '^') + { + *negptr = TRUE; + ptr++; + } + for (i = 0; i < (int)sizeof(name) - 1; i++) + { + c = *(++ptr); + if (c == 0) goto ERROR_RETURN; + if (c == '}') break; + name[i] = c; + } + if (c !='}') goto ERROR_RETURN; + name[i] = 0; + } + +/* Otherwise there is just one following character */ + +else + { + name[0] = c; + name[1] = 0; + } + +*ptrptr = ptr; + +/* Search for a recognized property name using binary chop */ + +bot = 0; +top = _pcre_utt_size; + +while (bot < top) + { + i = (bot + top) >> 1; + c = strcmp(name, _pcre_utt_names + _pcre_utt[i].name_offset); + if (c == 0) + { + *dptr = _pcre_utt[i].value; + return _pcre_utt[i].type; + } + if (c > 0) bot = i + 1; else top = i; + } + +*errorcodeptr = ERR47; +*ptrptr = ptr; +return -1; + +ERROR_RETURN: +*errorcodeptr = ERR46; +*ptrptr = ptr; +return -1; +} +#endif + + + + +/************************************************* +* Check for counted repeat * +*************************************************/ + +/* This function is called when a '{' is encountered in a place where it might +start a quantifier. It looks ahead to see if it really is a quantifier or not. +It is only a quantifier if it is one of the forms {ddd} {ddd,} or {ddd,ddd} +where the ddds are digits. + +Arguments: + p pointer to the first char after '{' + +Returns: TRUE or FALSE +*/ + +static BOOL +is_counted_repeat(const uschar *p) +{ +if ((digitab[*p++] & ctype_digit) == 0) return FALSE; +while ((digitab[*p] & ctype_digit) != 0) p++; +if (*p == '}') return TRUE; + +if (*p++ != ',') return FALSE; +if (*p == '}') return TRUE; + +if ((digitab[*p++] & ctype_digit) == 0) return FALSE; +while ((digitab[*p] & ctype_digit) != 0) p++; + +return (*p == '}'); +} + + + +/************************************************* +* Read repeat counts * +*************************************************/ + +/* Read an item of the form {n,m} and return the values. This is called only +after is_counted_repeat() has confirmed that a repeat-count quantifier exists, +so the syntax is guaranteed to be correct, but we need to check the values. + +Arguments: + p pointer to first char after '{' + minp pointer to int for min + maxp pointer to int for max + returned as -1 if no max + errorcodeptr points to error code variable + +Returns: pointer to '}' on success; + current ptr on error, with errorcodeptr set non-zero +*/ + +static const uschar * +read_repeat_counts(const uschar *p, int *minp, int *maxp, int *errorcodeptr) +{ +int min = 0; +int max = -1; + +/* Read the minimum value and do a paranoid check: a negative value indicates +an integer overflow. */ + +while ((digitab[*p] & ctype_digit) != 0) min = min * 10 + *p++ - '0'; +if (min < 0 || min > 65535) + { + *errorcodeptr = ERR5; + return p; + } + +/* Read the maximum value if there is one, and again do a paranoid on its size. +Also, max must not be less than min. */ + +if (*p == '}') max = min; else + { + if (*(++p) != '}') + { + max = 0; + while((digitab[*p] & ctype_digit) != 0) max = max * 10 + *p++ - '0'; + if (max < 0 || max > 65535) + { + *errorcodeptr = ERR5; + return p; + } + if (max < min) + { + *errorcodeptr = ERR4; + return p; + } + } + } + +/* Fill in the required variables, and pass back the pointer to the terminating +'}'. */ + +*minp = min; +*maxp = max; +return p; +} + + + +/************************************************* +* Find forward referenced subpattern * +*************************************************/ + +/* This function scans along a pattern's text looking for capturing +subpatterns, and counting them. If it finds a named pattern that matches the +name it is given, it returns its number. Alternatively, if the name is NULL, it +returns when it reaches a given numbered subpattern. This is used for forward +references to subpatterns. We know that if (?P< is encountered, the name will +be terminated by '>' because that is checked in the first pass. + +Arguments: + ptr current position in the pattern + count current count of capturing parens so far encountered + name name to seek, or NULL if seeking a numbered subpattern + lorn name length, or subpattern number if name is NULL + xmode TRUE if we are in /x mode + +Returns: the number of the named subpattern, or -1 if not found +*/ + +static int +find_parens(const uschar *ptr, int count, const uschar *name, int lorn, + BOOL xmode) +{ +const uschar *thisname; + +for (; *ptr != 0; ptr++) + { + int term; + + /* Skip over backslashed characters and also entire \Q...\E */ + + if (*ptr == '\\') + { + if (*(++ptr) == 0) return -1; + if (*ptr == 'Q') for (;;) + { + while (*(++ptr) != 0 && *ptr != '\\'); + if (*ptr == 0) return -1; + if (*(++ptr) == 'E') break; + } + continue; + } + + /* Skip over character classes */ + + if (*ptr == '[') + { + while (*(++ptr) != ']') + { + if (*ptr == 0) return -1; + if (*ptr == '\\') + { + if (*(++ptr) == 0) return -1; + if (*ptr == 'Q') for (;;) + { + while (*(++ptr) != 0 && *ptr != '\\'); + if (*ptr == 0) return -1; + if (*(++ptr) == 'E') break; + } + continue; + } + } + continue; + } + + /* Skip comments in /x mode */ + + if (xmode && *ptr == '#') + { + while (*(++ptr) != 0 && *ptr != '\n'); + if (*ptr == 0) return -1; + continue; + } + + /* An opening parens must now be a real metacharacter */ + + if (*ptr != '(') continue; + if (ptr[1] != '?' && ptr[1] != '*') + { + count++; + if (name == NULL && count == lorn) return count; + continue; + } + + ptr += 2; + if (*ptr == 'P') ptr++; /* Allow optional P */ + + /* We have to disambiguate (?<! and (?<= from (?<name> */ + + if ((*ptr != '<' || ptr[1] == '!' || ptr[1] == '=') && + *ptr != '\'') + continue; + + count++; + + if (name == NULL && count == lorn) return count; + term = *ptr++; + if (term == '<') term = '>'; + thisname = ptr; + while (*ptr != term) ptr++; + if (name != NULL && lorn == ptr - thisname && + strncmp((const char *)name, (const char *)thisname, lorn) == 0) + return count; + } + +return -1; +} + + + +/************************************************* +* Find first significant op code * +*************************************************/ + +/* This is called by several functions that scan a compiled expression looking +for a fixed first character, or an anchoring op code etc. It skips over things +that do not influence this. For some calls, a change of option is important. +For some calls, it makes sense to skip negative forward and all backward +assertions, and also the \b assertion; for others it does not. + +Arguments: + code pointer to the start of the group + options pointer to external options + optbit the option bit whose changing is significant, or + zero if none are + skipassert TRUE if certain assertions are to be skipped + +Returns: pointer to the first significant opcode +*/ + +static const uschar* +first_significant_code(const uschar *code, int *options, int optbit, + BOOL skipassert) +{ +for (;;) + { + switch ((int)*code) + { + case OP_OPT: + if (optbit > 0 && ((int)code[1] & optbit) != (*options & optbit)) + *options = (int)code[1]; + code += 2; + break; + + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + if (!skipassert) return code; + do code += GET(code, 1); while (*code == OP_ALT); + code += _pcre_OP_lengths[*code]; + break; + + case OP_WORD_BOUNDARY: + case OP_NOT_WORD_BOUNDARY: + if (!skipassert) return code; + /* Fall through */ + + case OP_CALLOUT: + case OP_CREF: + case OP_RREF: + case OP_DEF: + code += _pcre_OP_lengths[*code]; + break; + + default: + return code; + } + } +/* Control never reaches here */ +} + + + + +/************************************************* +* Find the fixed length of a pattern * +*************************************************/ + +/* Scan a pattern and compute the fixed length of subject that will match it, +if the length is fixed. This is needed for dealing with backward assertions. +In UTF8 mode, the result is in characters rather than bytes. + +Arguments: + code points to the start of the pattern (the bracket) + options the compiling options + +Returns: the fixed length, or -1 if there is no fixed length, + or -2 if \C was encountered +*/ + +static int +find_fixedlength(uschar *code, int options) +{ +int length = -1; + +register int branchlength = 0; +register uschar *cc = code + 1 + LINK_SIZE; + +/* Scan along the opcodes for this branch. If we get to the end of the +branch, check the length against that of the other branches. */ + +for (;;) + { + int d; + register int op = *cc; + switch (op) + { + case OP_CBRA: + case OP_BRA: + case OP_ONCE: + case OP_COND: + d = find_fixedlength(cc + ((op == OP_CBRA)? 2:0), options); + if (d < 0) return d; + branchlength += d; + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* Reached end of a branch; if it's a ket it is the end of a nested + call. If it's ALT it is an alternation in a nested call. If it is + END it's the end of the outer call. All can be handled by the same code. */ + + case OP_ALT: + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_END: + if (length < 0) length = branchlength; + else if (length != branchlength) return -1; + if (*cc != OP_ALT) return length; + cc += 1 + LINK_SIZE; + branchlength = 0; + break; + + /* Skip over assertive subpatterns */ + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + do cc += GET(cc, 1); while (*cc == OP_ALT); + /* Fall through */ + + /* Skip over things that don't match chars */ + + case OP_REVERSE: + case OP_CREF: + case OP_RREF: + case OP_DEF: + case OP_OPT: + case OP_CALLOUT: + case OP_SOD: + case OP_SOM: + case OP_EOD: + case OP_EODN: + case OP_CIRC: + case OP_DOLL: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + cc += _pcre_OP_lengths[*cc]; + break; + + /* Handle literal characters */ + + case OP_CHAR: + case OP_CHARNC: + case OP_NOT: + branchlength++; + cc += 2; +#ifdef SUPPORT_UTF8 + if ((options & PCRE_UTF8) != 0) + { + while ((*cc & 0xc0) == 0x80) cc++; + } +#endif + break; + + /* Handle exact repetitions. The count is already in characters, but we + need to skip over a multibyte character in UTF8 mode. */ + + case OP_EXACT: + branchlength += GET2(cc,1); + cc += 4; +#ifdef SUPPORT_UTF8 + if ((options & PCRE_UTF8) != 0) + { + while((*cc & 0x80) == 0x80) cc++; + } +#endif + break; + + case OP_TYPEEXACT: + branchlength += GET2(cc,1); + if (cc[3] == OP_PROP || cc[3] == OP_NOTPROP) cc += 2; + cc += 4; + break; + + /* Handle single-char matchers */ + + case OP_PROP: + case OP_NOTPROP: + cc += 2; + /* Fall through */ + + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + branchlength++; + cc++; + break; + + /* The single-byte matcher isn't allowed */ + + case OP_ANYBYTE: + return -2; + + /* Check a class for variable quantification */ + +#ifdef SUPPORT_UTF8 + case OP_XCLASS: + cc += GET(cc, 1) - 33; + /* Fall through */ +#endif + + case OP_CLASS: + case OP_NCLASS: + cc += 33; + + switch (*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + return -1; + + case OP_CRRANGE: + case OP_CRMINRANGE: + if (GET2(cc,1) != GET2(cc,3)) return -1; + branchlength += GET2(cc,1); + cc += 5; + break; + + default: + branchlength++; + } + break; + + /* Anything else is variable length */ + + default: + return -1; + } + } +/* Control never gets here */ +} + + + + +/************************************************* +* Scan compiled regex for numbered bracket * +*************************************************/ + +/* This little function scans through a compiled pattern until it finds a +capturing bracket with the given number. + +Arguments: + code points to start of expression + utf8 TRUE in UTF-8 mode + number the required bracket number + +Returns: pointer to the opcode for the bracket, or NULL if not found +*/ + +static const uschar * +find_bracket(const uschar *code, BOOL utf8, int number) +{ +for (;;) + { + register int c = *code; + if (c == OP_END) return NULL; + + /* XCLASS is used for classes that cannot be represented just by a bit + map. This includes negated single high-valued characters. The length in + the table is zero; the actual length is stored in the compiled code. */ + + if (c == OP_XCLASS) code += GET(code, 1); + + /* Handle capturing bracket */ + + else if (c == OP_CBRA) + { + int n = GET2(code, 1+LINK_SIZE); + if (n == number) return (uschar *)code; + code += _pcre_OP_lengths[c]; + } + + /* Otherwise, we can get the item's length from the table, except that for + repeated character types, we have to test for \p and \P, which have an extra + two bytes of parameters. */ + + else + { + switch(c) + { + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSUPTO: + if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2; + break; + } + + /* Add in the fixed length from the table */ + + code += _pcre_OP_lengths[c]; + + /* In UTF-8 mode, opcodes that are followed by a character may be followed by + a multi-byte character. The length in the table is a minimum, so we have to + arrange to skip the extra bytes. */ + +#ifdef SUPPORT_UTF8 + if (utf8) switch(c) + { + case OP_CHAR: + case OP_CHARNC: + case OP_EXACT: + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + if (code[-1] >= 0xc0) code += _pcre_utf8_table4[code[-1] & 0x3f]; + break; + } +#endif + } + } +} + + + +/************************************************* +* Scan compiled regex for recursion reference * +*************************************************/ + +/* This little function scans through a compiled pattern until it finds an +instance of OP_RECURSE. + +Arguments: + code points to start of expression + utf8 TRUE in UTF-8 mode + +Returns: pointer to the opcode for OP_RECURSE, or NULL if not found +*/ + +static const uschar * +find_recurse(const uschar *code, BOOL utf8) +{ +for (;;) + { + register int c = *code; + if (c == OP_END) return NULL; + if (c == OP_RECURSE) return code; + + /* XCLASS is used for classes that cannot be represented just by a bit + map. This includes negated single high-valued characters. The length in + the table is zero; the actual length is stored in the compiled code. */ + + if (c == OP_XCLASS) code += GET(code, 1); + + /* Otherwise, we can get the item's length from the table, except that for + repeated character types, we have to test for \p and \P, which have an extra + two bytes of parameters. */ + + else + { + switch(c) + { + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + case OP_TYPEPOSUPTO: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2; + break; + } + + /* Add in the fixed length from the table */ + + code += _pcre_OP_lengths[c]; + + /* In UTF-8 mode, opcodes that are followed by a character may be followed + by a multi-byte character. The length in the table is a minimum, so we have + to arrange to skip the extra bytes. */ + +#ifdef SUPPORT_UTF8 + if (utf8) switch(c) + { + case OP_CHAR: + case OP_CHARNC: + case OP_EXACT: + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + if (code[-1] >= 0xc0) code += _pcre_utf8_table4[code[-1] & 0x3f]; + break; + } +#endif + } + } +} + + + +/************************************************* +* Scan compiled branch for non-emptiness * +*************************************************/ + +/* This function scans through a branch of a compiled pattern to see whether it +can match the empty string or not. It is called from could_be_empty() +below and from compile_branch() when checking for an unlimited repeat of a +group that can match nothing. Note that first_significant_code() skips over +assertions. If we hit an unclosed bracket, we return "empty" - this means we've +struck an inner bracket whose current branch will already have been scanned. + +Arguments: + code points to start of search + endcode points to where to stop + utf8 TRUE if in UTF8 mode + +Returns: TRUE if what is matched could be empty +*/ + +static BOOL +could_be_empty_branch(const uschar *code, const uschar *endcode, BOOL utf8) +{ +register int c; +for (code = first_significant_code(code + _pcre_OP_lengths[*code], NULL, 0, TRUE); + code < endcode; + code = first_significant_code(code + _pcre_OP_lengths[c], NULL, 0, TRUE)) + { + const uschar *ccode; + + c = *code; + + /* Groups with zero repeats can of course be empty; skip them. */ + + if (c == OP_BRAZERO || c == OP_BRAMINZERO) + { + code += _pcre_OP_lengths[c]; + do code += GET(code, 1); while (*code == OP_ALT); + c = *code; + continue; + } + + /* For other groups, scan the branches. */ + + if (c == OP_BRA || c == OP_CBRA || c == OP_ONCE || c == OP_COND) + { + BOOL empty_branch; + if (GET(code, 1) == 0) return TRUE; /* Hit unclosed bracket */ + + /* Scan a closed bracket */ + + empty_branch = FALSE; + do + { + if (!empty_branch && could_be_empty_branch(code, endcode, utf8)) + empty_branch = TRUE; + code += GET(code, 1); + } + while (*code == OP_ALT); + if (!empty_branch) return FALSE; /* All branches are non-empty */ + c = *code; + continue; + } + + /* Handle the other opcodes */ + + switch (c) + { + /* Check for quantifiers after a class. XCLASS is used for classes that + cannot be represented just by a bit map. This includes negated single + high-valued characters. The length in _pcre_OP_lengths[] is zero; the + actual length is stored in the compiled code, so we must update "code" + here. */ + +#ifdef SUPPORT_UTF8 + case OP_XCLASS: + ccode = code += GET(code, 1); + goto CHECK_CLASS_REPEAT; +#endif + + case OP_CLASS: + case OP_NCLASS: + ccode = code + 33; + +#ifdef SUPPORT_UTF8 + CHECK_CLASS_REPEAT: +#endif + + switch (*ccode) + { + case OP_CRSTAR: /* These could be empty; continue */ + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + break; + + default: /* Non-repeat => class must match */ + case OP_CRPLUS: /* These repeats aren't empty */ + case OP_CRMINPLUS: + return FALSE; + + case OP_CRRANGE: + case OP_CRMINRANGE: + if (GET2(ccode, 1) > 0) return FALSE; /* Minimum > 0 */ + break; + } + break; + + /* Opcodes that must match a character */ + + case OP_PROP: + case OP_NOTPROP: + case OP_EXTUNI: + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ANYBYTE: + case OP_CHAR: + case OP_CHARNC: + case OP_NOT: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + case OP_EXACT: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTPOSPLUS: + case OP_NOTEXACT: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + case OP_TYPEEXACT: + return FALSE; + + /* These are going to continue, as they may be empty, but we have to + fudge the length for the \p and \P cases. */ + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + /* Same for these */ + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2; + break; + + /* End of branch */ + + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_ALT: + return TRUE; + + /* In UTF-8 mode, STAR, MINSTAR, POSSTAR, QUERY, MINQUERY, POSQUERY, UPTO, + MINUPTO, and POSUPTO may be followed by a multibyte character */ + +#ifdef SUPPORT_UTF8 + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + if (utf8) while ((code[2] & 0xc0) == 0x80) code++; + break; +#endif + } + } + +return TRUE; +} + + + +/************************************************* +* Scan compiled regex for non-emptiness * +*************************************************/ + +/* This function is called to check for left recursive calls. We want to check +the current branch of the current pattern to see if it could match the empty +string. If it could, we must look outwards for branches at other levels, +stopping when we pass beyond the bracket which is the subject of the recursion. + +Arguments: + code points to start of the recursion + endcode points to where to stop (current RECURSE item) + bcptr points to the chain of current (unclosed) branch starts + utf8 TRUE if in UTF-8 mode + +Returns: TRUE if what is matched could be empty +*/ + +static BOOL +could_be_empty(const uschar *code, const uschar *endcode, branch_chain *bcptr, + BOOL utf8) +{ +while (bcptr != NULL && bcptr->current >= code) + { + if (!could_be_empty_branch(bcptr->current, endcode, utf8)) return FALSE; + bcptr = bcptr->outer; + } +return TRUE; +} + + + +/************************************************* +* Check for POSIX class syntax * +*************************************************/ + +/* This function is called when the sequence "[:" or "[." or "[=" is +encountered in a character class. It checks whether this is followed by an +optional ^ and then a sequence of letters, terminated by a matching ":]" or +".]" or "=]". + +Argument: + ptr pointer to the initial [ + endptr where to return the end pointer + cd pointer to compile data + +Returns: TRUE or FALSE +*/ + +static BOOL +check_posix_syntax(const uschar *ptr, const uschar **endptr, compile_data *cd) +{ +int terminator; /* Don't combine these lines; the Solaris cc */ +terminator = *(++ptr); /* compiler warns about "non-constant" initializer. */ +if (*(++ptr) == '^') ptr++; +while ((cd->ctypes[*ptr] & ctype_letter) != 0) ptr++; +if (*ptr == terminator && ptr[1] == ']') + { + *endptr = ptr; + return TRUE; + } +return FALSE; +} + + + + +/************************************************* +* Check POSIX class name * +*************************************************/ + +/* This function is called to check the name given in a POSIX-style class entry +such as [:alnum:]. + +Arguments: + ptr points to the first letter + len the length of the name + +Returns: a value representing the name, or -1 if unknown +*/ + +static int +check_posix_name(const uschar *ptr, int len) +{ +const char *pn = posix_names; +register int yield = 0; +while (posix_name_lengths[yield] != 0) + { + if (len == posix_name_lengths[yield] && + strncmp((const char *)ptr, pn, len) == 0) return yield; + pn += posix_name_lengths[yield] + 1; + yield++; + } +return -1; +} + + +/************************************************* +* Adjust OP_RECURSE items in repeated group * +*************************************************/ + +/* OP_RECURSE items contain an offset from the start of the regex to the group +that is referenced. This means that groups can be replicated for fixed +repetition simply by copying (because the recursion is allowed to refer to +earlier groups that are outside the current group). However, when a group is +optional (i.e. the minimum quantifier is zero), OP_BRAZERO is inserted before +it, after it has been compiled. This means that any OP_RECURSE items within it +that refer to the group itself or any contained groups have to have their +offsets adjusted. That one of the jobs of this function. Before it is called, +the partially compiled regex must be temporarily terminated with OP_END. + +This function has been extended with the possibility of forward references for +recursions and subroutine calls. It must also check the list of such references +for the group we are dealing with. If it finds that one of the recursions in +the current group is on this list, it adjusts the offset in the list, not the +value in the reference (which is a group number). + +Arguments: + group points to the start of the group + adjust the amount by which the group is to be moved + utf8 TRUE in UTF-8 mode + cd contains pointers to tables etc. + save_hwm the hwm forward reference pointer at the start of the group + +Returns: nothing +*/ + +static void +adjust_recurse(uschar *group, int adjust, BOOL utf8, compile_data *cd, + uschar *save_hwm) +{ +uschar *ptr = group; + +while ((ptr = (uschar *)find_recurse(ptr, utf8)) != NULL) + { + int offset; + uschar *hc; + + /* See if this recursion is on the forward reference list. If so, adjust the + reference. */ + + for (hc = save_hwm; hc < cd->hwm; hc += LINK_SIZE) + { + offset = GET(hc, 0); + if (cd->start_code + offset == ptr + 1) + { + PUT(hc, 0, offset + adjust); + break; + } + } + + /* Otherwise, adjust the recursion offset if it's after the start of this + group. */ + + if (hc >= cd->hwm) + { + offset = GET(ptr, 1); + if (cd->start_code + offset >= group) PUT(ptr, 1, offset + adjust); + } + + ptr += 1 + LINK_SIZE; + } +} + + + +/************************************************* +* Insert an automatic callout point * +*************************************************/ + +/* This function is called when the PCRE_AUTO_CALLOUT option is set, to insert +callout points before each pattern item. + +Arguments: + code current code pointer + ptr current pattern pointer + cd pointers to tables etc + +Returns: new code pointer +*/ + +static uschar * +auto_callout(uschar *code, const uschar *ptr, compile_data *cd) +{ +*code++ = OP_CALLOUT; +*code++ = 255; +PUT(code, 0, ptr - cd->start_pattern); /* Pattern offset */ +PUT(code, LINK_SIZE, 0); /* Default length */ +return code + 2*LINK_SIZE; +} + + + +/************************************************* +* Complete a callout item * +*************************************************/ + +/* A callout item contains the length of the next item in the pattern, which +we can't fill in till after we have reached the relevant point. This is used +for both automatic and manual callouts. + +Arguments: + previous_callout points to previous callout item + ptr current pattern pointer + cd pointers to tables etc + +Returns: nothing +*/ + +static void +complete_callout(uschar *previous_callout, const uschar *ptr, compile_data *cd) +{ +int length = ptr - cd->start_pattern - GET(previous_callout, 2); +PUT(previous_callout, 2 + LINK_SIZE, length); +} + + + +#ifdef SUPPORT_UCP +/************************************************* +* Get othercase range * +*************************************************/ + +/* This function is passed the start and end of a class range, in UTF-8 mode +with UCP support. It searches up the characters, looking for internal ranges of +characters in the "other" case. Each call returns the next one, updating the +start address. + +Arguments: + cptr points to starting character value; updated + d end value + ocptr where to put start of othercase range + odptr where to put end of othercase range + +Yield: TRUE when range returned; FALSE when no more +*/ + +static BOOL +get_othercase_range(unsigned int *cptr, unsigned int d, unsigned int *ocptr, + unsigned int *odptr) +{ +unsigned int c, othercase, next; + +for (c = *cptr; c <= d; c++) + { if ((othercase = _pcre_ucp_othercase(c)) != NOTACHAR) break; } + +if (c > d) return FALSE; + +*ocptr = othercase; +next = othercase + 1; + +for (++c; c <= d; c++) + { + if (_pcre_ucp_othercase(c) != next) break; + next++; + } + +*odptr = next - 1; +*cptr = c; + +return TRUE; +} +#endif /* SUPPORT_UCP */ + + + +/************************************************* +* Check if auto-possessifying is possible * +*************************************************/ + +/* This function is called for unlimited repeats of certain items, to see +whether the next thing could possibly match the repeated item. If not, it makes +sense to automatically possessify the repeated item. + +Arguments: + op_code the repeated op code + this data for this item, depends on the opcode + utf8 TRUE in UTF-8 mode + utf8_char used for utf8 character bytes, NULL if not relevant + ptr next character in pattern + options options bits + cd contains pointers to tables etc. + +Returns: TRUE if possessifying is wanted +*/ + +static BOOL +check_auto_possessive(int op_code, int item, BOOL utf8, uschar *utf8_char, + const uschar *ptr, int options, compile_data *cd) +{ +int next; + +/* Skip whitespace and comments in extended mode */ + +if ((options & PCRE_EXTENDED) != 0) + { + for (;;) + { + while ((cd->ctypes[*ptr] & ctype_space) != 0) ptr++; + if (*ptr == '#') + { + while (*(++ptr) != 0) + if (IS_NEWLINE(ptr)) { ptr += cd->nllen; break; } + } + else break; + } + } + +/* If the next item is one that we can handle, get its value. A non-negative +value is a character, a negative value is an escape value. */ + +if (*ptr == '\\') + { + int temperrorcode = 0; + next = check_escape(&ptr, &temperrorcode, cd->bracount, options, FALSE); + if (temperrorcode != 0) return FALSE; + ptr++; /* Point after the escape sequence */ + } + +else if ((cd->ctypes[*ptr] & ctype_meta) == 0) + { +#ifdef SUPPORT_UTF8 + if (utf8) { GETCHARINC(next, ptr); } else +#endif + next = *ptr++; + } + +else return FALSE; + +/* Skip whitespace and comments in extended mode */ + +if ((options & PCRE_EXTENDED) != 0) + { + for (;;) + { + while ((cd->ctypes[*ptr] & ctype_space) != 0) ptr++; + if (*ptr == '#') + { + while (*(++ptr) != 0) + if (IS_NEWLINE(ptr)) { ptr += cd->nllen; break; } + } + else break; + } + } + +/* If the next thing is itself optional, we have to give up. */ + +if (*ptr == '*' || *ptr == '?' || strncmp((char *)ptr, "{0,", 3) == 0) + return FALSE; + +/* Now compare the next item with the previous opcode. If the previous is a +positive single character match, "item" either contains the character or, if +"item" is greater than 127 in utf8 mode, the character's bytes are in +utf8_char. */ + + +/* Handle cases when the next item is a character. */ + +if (next >= 0) switch(op_code) + { + case OP_CHAR: +#ifdef SUPPORT_UTF8 + if (utf8 && item > 127) { GETCHAR(item, utf8_char); } +#endif + return item != next; + + /* For CHARNC (caseless character) we must check the other case. If we have + Unicode property support, we can use it to test the other case of + high-valued characters. */ + + case OP_CHARNC: +#ifdef SUPPORT_UTF8 + if (utf8 && item > 127) { GETCHAR(item, utf8_char); } +#endif + if (item == next) return FALSE; +#ifdef SUPPORT_UTF8 + if (utf8) + { + unsigned int othercase; + if (next < 128) othercase = cd->fcc[next]; else +#ifdef SUPPORT_UCP + othercase = _pcre_ucp_othercase((unsigned int)next); +#else + othercase = NOTACHAR; +#endif + return (unsigned int)item != othercase; + } + else +#endif /* SUPPORT_UTF8 */ + return (item != cd->fcc[next]); /* Non-UTF-8 mode */ + + /* For OP_NOT, "item" must be a single-byte character. */ + + case OP_NOT: + if (next < 0) return FALSE; /* Not a character */ + if (item == next) return TRUE; + if ((options & PCRE_CASELESS) == 0) return FALSE; +#ifdef SUPPORT_UTF8 + if (utf8) + { + unsigned int othercase; + if (next < 128) othercase = cd->fcc[next]; else +#ifdef SUPPORT_UCP + othercase = _pcre_ucp_othercase(next); +#else + othercase = NOTACHAR; +#endif + return (unsigned int)item == othercase; + } + else +#endif /* SUPPORT_UTF8 */ + return (item == cd->fcc[next]); /* Non-UTF-8 mode */ + + case OP_DIGIT: + return next > 127 || (cd->ctypes[next] & ctype_digit) == 0; + + case OP_NOT_DIGIT: + return next <= 127 && (cd->ctypes[next] & ctype_digit) != 0; + + case OP_WHITESPACE: + return next > 127 || (cd->ctypes[next] & ctype_space) == 0; + + case OP_NOT_WHITESPACE: + return next <= 127 && (cd->ctypes[next] & ctype_space) != 0; + + case OP_WORDCHAR: + return next > 127 || (cd->ctypes[next] & ctype_word) == 0; + + case OP_NOT_WORDCHAR: + return next <= 127 && (cd->ctypes[next] & ctype_word) != 0; + + case OP_HSPACE: + case OP_NOT_HSPACE: + switch(next) + { + case 0x09: + case 0x20: + case 0xa0: + case 0x1680: + case 0x180e: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200A: + case 0x202f: + case 0x205f: + case 0x3000: + return op_code != OP_HSPACE; + default: + return op_code == OP_HSPACE; + } + + case OP_VSPACE: + case OP_NOT_VSPACE: + switch(next) + { + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x85: + case 0x2028: + case 0x2029: + return op_code != OP_VSPACE; + default: + return op_code == OP_VSPACE; + } + + default: + return FALSE; + } + + +/* Handle the case when the next item is \d, \s, etc. */ + +switch(op_code) + { + case OP_CHAR: + case OP_CHARNC: +#ifdef SUPPORT_UTF8 + if (utf8 && item > 127) { GETCHAR(item, utf8_char); } +#endif + switch(-next) + { + case ESC_d: + return item > 127 || (cd->ctypes[item] & ctype_digit) == 0; + + case ESC_D: + return item <= 127 && (cd->ctypes[item] & ctype_digit) != 0; + + case ESC_s: + return item > 127 || (cd->ctypes[item] & ctype_space) == 0; + + case ESC_S: + return item <= 127 && (cd->ctypes[item] & ctype_space) != 0; + + case ESC_w: + return item > 127 || (cd->ctypes[item] & ctype_word) == 0; + + case ESC_W: + return item <= 127 && (cd->ctypes[item] & ctype_word) != 0; + + case ESC_h: + case ESC_H: + switch(item) + { + case 0x09: + case 0x20: + case 0xa0: + case 0x1680: + case 0x180e: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200A: + case 0x202f: + case 0x205f: + case 0x3000: + return -next != ESC_h; + default: + return -next == ESC_h; + } + + case ESC_v: + case ESC_V: + switch(item) + { + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x85: + case 0x2028: + case 0x2029: + return -next != ESC_v; + default: + return -next == ESC_v; + } + + default: + return FALSE; + } + + case OP_DIGIT: + return next == -ESC_D || next == -ESC_s || next == -ESC_W || + next == -ESC_h || next == -ESC_v; + + case OP_NOT_DIGIT: + return next == -ESC_d; + + case OP_WHITESPACE: + return next == -ESC_S || next == -ESC_d || next == -ESC_w; + + case OP_NOT_WHITESPACE: + return next == -ESC_s || next == -ESC_h || next == -ESC_v; + + case OP_HSPACE: + return next == -ESC_S || next == -ESC_H || next == -ESC_d || next == -ESC_w; + + case OP_NOT_HSPACE: + return next == -ESC_h; + + /* Can't have \S in here because VT matches \S (Perl anomaly) */ + case OP_VSPACE: + return next == -ESC_V || next == -ESC_d || next == -ESC_w; + + case OP_NOT_VSPACE: + return next == -ESC_v; + + case OP_WORDCHAR: + return next == -ESC_W || next == -ESC_s || next == -ESC_h || next == -ESC_v; + + case OP_NOT_WORDCHAR: + return next == -ESC_w || next == -ESC_d; + + default: + return FALSE; + } + +/* Control does not reach here */ +} + + + +/************************************************* +* Compile one branch * +*************************************************/ + +/* Scan the pattern, compiling it into the a vector. If the options are +changed during the branch, the pointer is used to change the external options +bits. This function is used during the pre-compile phase when we are trying +to find out the amount of memory needed, as well as during the real compile +phase. The value of lengthptr distinguishes the two phases. + +Arguments: + optionsptr pointer to the option bits + codeptr points to the pointer to the current code point + ptrptr points to the current pattern pointer + errorcodeptr points to error code variable + firstbyteptr set to initial literal character, or < 0 (REQ_UNSET, REQ_NONE) + reqbyteptr set to the last literal character required, else < 0 + bcptr points to current branch chain + cd contains pointers to tables etc. + lengthptr NULL during the real compile phase + points to length accumulator during pre-compile phase + +Returns: TRUE on success + FALSE, with *errorcodeptr set non-zero on error +*/ + +static BOOL +compile_branch(int *optionsptr, uschar **codeptr, const uschar **ptrptr, + int *errorcodeptr, int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr, + compile_data *cd, int *lengthptr) +{ +int repeat_type, op_type; +int repeat_min = 0, repeat_max = 0; /* To please picky compilers */ +int bravalue = 0; +int greedy_default, greedy_non_default; +int firstbyte, reqbyte; +int zeroreqbyte, zerofirstbyte; +int req_caseopt, reqvary, tempreqvary; +int options = *optionsptr; +int after_manual_callout = 0; +int length_prevgroup = 0; +register int c; +register uschar *code = *codeptr; +uschar *last_code = code; +uschar *orig_code = code; +uschar *tempcode; +BOOL inescq = FALSE; +BOOL groupsetfirstbyte = FALSE; +const uschar *ptr = *ptrptr; +const uschar *tempptr; +uschar *previous = NULL; +uschar *previous_callout = NULL; +uschar *save_hwm = NULL; +uschar classbits[32]; + +#ifdef SUPPORT_UTF8 +BOOL class_utf8; +BOOL utf8 = (options & PCRE_UTF8) != 0; +uschar *class_utf8data; +uschar utf8_char[6]; +#else +BOOL utf8 = FALSE; +uschar *utf8_char = NULL; +#endif + +#ifdef DEBUG +if (lengthptr != NULL) DPRINTF((">> start branch\n")); +#endif + +/* Set up the default and non-default settings for greediness */ + +greedy_default = ((options & PCRE_UNGREEDY) != 0); +greedy_non_default = greedy_default ^ 1; + +/* Initialize no first byte, no required byte. REQ_UNSET means "no char +matching encountered yet". It gets changed to REQ_NONE if we hit something that +matches a non-fixed char first char; reqbyte just remains unset if we never +find one. + +When we hit a repeat whose minimum is zero, we may have to adjust these values +to take the zero repeat into account. This is implemented by setting them to +zerofirstbyte and zeroreqbyte when such a repeat is encountered. The individual +item types that can be repeated set these backoff variables appropriately. */ + +firstbyte = reqbyte = zerofirstbyte = zeroreqbyte = REQ_UNSET; + +/* The variable req_caseopt contains either the REQ_CASELESS value or zero, +according to the current setting of the caseless flag. REQ_CASELESS is a bit +value > 255. It is added into the firstbyte or reqbyte variables to record the +case status of the value. This is used only for ASCII characters. */ + +req_caseopt = ((options & PCRE_CASELESS) != 0)? REQ_CASELESS : 0; + +/* Switch on next character until the end of the branch */ + +for (;; ptr++) + { + BOOL negate_class; + BOOL possessive_quantifier; + BOOL is_quantifier; + BOOL is_recurse; + BOOL reset_bracount; + int class_charcount; + int class_lastchar; + int newoptions; + int recno; + int refsign; + int skipbytes; + int subreqbyte; + int subfirstbyte; + int terminator; + int mclength; + uschar mcbuffer[8]; + + /* Get next byte in the pattern */ + + c = *ptr; + + /* If we are in the pre-compile phase, accumulate the length used for the + previous cycle of this loop. */ + + if (lengthptr != NULL) + { +#ifdef DEBUG + if (code > cd->hwm) cd->hwm = code; /* High water info */ +#endif + if (code > cd->start_workspace + COMPILE_WORK_SIZE) /* Check for overrun */ + { + *errorcodeptr = ERR52; + goto FAILED; + } + + /* There is at least one situation where code goes backwards: this is the + case of a zero quantifier after a class (e.g. [ab]{0}). At compile time, + the class is simply eliminated. However, it is created first, so we have to + allow memory for it. Therefore, don't ever reduce the length at this point. + */ + + if (code < last_code) code = last_code; + + /* Paranoid check for integer overflow */ + + if (OFLOW_MAX - *lengthptr < code - last_code) + { + *errorcodeptr = ERR20; + goto FAILED; + } + + *lengthptr += code - last_code; + DPRINTF(("length=%d added %d c=%c\n", *lengthptr, code - last_code, c)); + + /* If "previous" is set and it is not at the start of the work space, move + it back to there, in order to avoid filling up the work space. Otherwise, + if "previous" is NULL, reset the current code pointer to the start. */ + + if (previous != NULL) + { + if (previous > orig_code) + { + memmove(orig_code, previous, code - previous); + code -= previous - orig_code; + previous = orig_code; + } + } + else code = orig_code; + + /* Remember where this code item starts so we can pick up the length + next time round. */ + + last_code = code; + } + + /* In the real compile phase, just check the workspace used by the forward + reference list. */ + + else if (cd->hwm > cd->start_workspace + COMPILE_WORK_SIZE) + { + *errorcodeptr = ERR52; + goto FAILED; + } + + /* If in \Q...\E, check for the end; if not, we have a literal */ + + if (inescq && c != 0) + { + if (c == '\\' && ptr[1] == 'E') + { + inescq = FALSE; + ptr++; + continue; + } + else + { + if (previous_callout != NULL) + { + if (lengthptr == NULL) /* Don't attempt in pre-compile phase */ + complete_callout(previous_callout, ptr, cd); + previous_callout = NULL; + } + if ((options & PCRE_AUTO_CALLOUT) != 0) + { + previous_callout = code; + code = auto_callout(code, ptr, cd); + } + goto NORMAL_CHAR; + } + } + + /* Fill in length of a previous callout, except when the next thing is + a quantifier. */ + + is_quantifier = c == '*' || c == '+' || c == '?' || + (c == '{' && is_counted_repeat(ptr+1)); + + if (!is_quantifier && previous_callout != NULL && + after_manual_callout-- <= 0) + { + if (lengthptr == NULL) /* Don't attempt in pre-compile phase */ + complete_callout(previous_callout, ptr, cd); + previous_callout = NULL; + } + + /* In extended mode, skip white space and comments */ + + if ((options & PCRE_EXTENDED) != 0) + { + if ((cd->ctypes[c] & ctype_space) != 0) continue; + if (c == '#') + { + while (*(++ptr) != 0) + { + if (IS_NEWLINE(ptr)) { ptr += cd->nllen - 1; break; } + } + if (*ptr != 0) continue; + + /* Else fall through to handle end of string */ + c = 0; + } + } + + /* No auto callout for quantifiers. */ + + if ((options & PCRE_AUTO_CALLOUT) != 0 && !is_quantifier) + { + previous_callout = code; + code = auto_callout(code, ptr, cd); + } + + switch(c) + { + /* ===================================================================*/ + case 0: /* The branch terminates at string end */ + case '|': /* or | or ) */ + case ')': + *firstbyteptr = firstbyte; + *reqbyteptr = reqbyte; + *codeptr = code; + *ptrptr = ptr; + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < code - last_code) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += code - last_code; /* To include callout length */ + DPRINTF((">> end branch\n")); + } + return TRUE; + + + /* ===================================================================*/ + /* Handle single-character metacharacters. In multiline mode, ^ disables + the setting of any following char as a first character. */ + + case '^': + if ((options & PCRE_MULTILINE) != 0) + { + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + } + previous = NULL; + *code++ = OP_CIRC; + break; + + case '$': + previous = NULL; + *code++ = OP_DOLL; + break; + + /* There can never be a first char if '.' is first, whatever happens about + repeats. The value of reqbyte doesn't change either. */ + + case '.': + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + zerofirstbyte = firstbyte; + zeroreqbyte = reqbyte; + previous = code; + *code++ = OP_ANY; + break; + + + /* ===================================================================*/ + /* Character classes. If the included characters are all < 256, we build a + 32-byte bitmap of the permitted characters, except in the special case + where there is only one such character. For negated classes, we build the + map as usual, then invert it at the end. However, we use a different opcode + so that data characters > 255 can be handled correctly. + + If the class contains characters outside the 0-255 range, a different + opcode is compiled. It may optionally have a bit map for characters < 256, + but those above are are explicitly listed afterwards. A flag byte tells + whether the bitmap is present, and whether this is a negated class or not. + */ + + case '[': + previous = code; + + /* PCRE supports POSIX class stuff inside a class. Perl gives an error if + they are encountered at the top level, so we'll do that too. */ + + if ((ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') && + check_posix_syntax(ptr, &tempptr, cd)) + { + *errorcodeptr = (ptr[1] == ':')? ERR13 : ERR31; + goto FAILED; + } + + /* If the first character is '^', set the negation flag and skip it. Also, + if the first few characters (either before or after ^) are \Q\E or \E we + skip them too. This makes for compatibility with Perl. */ + + negate_class = FALSE; + for (;;) + { + c = *(++ptr); + if (c == '\\') + { + if (ptr[1] == 'E') ptr++; + else if (strncmp((const char *)ptr+1, "Q\\E", 3) == 0) ptr += 3; + else break; + } + else if (!negate_class && c == '^') + negate_class = TRUE; + else break; + } + + /* Keep a count of chars with values < 256 so that we can optimize the case + of just a single character (as long as it's < 256). However, For higher + valued UTF-8 characters, we don't yet do any optimization. */ + + class_charcount = 0; + class_lastchar = -1; + + /* Initialize the 32-char bit map to all zeros. We build the map in a + temporary bit of memory, in case the class contains only 1 character (less + than 256), because in that case the compiled code doesn't use the bit map. + */ + + memset(classbits, 0, 32 * sizeof(uschar)); + +#ifdef SUPPORT_UTF8 + class_utf8 = FALSE; /* No chars >= 256 */ + class_utf8data = code + LINK_SIZE + 2; /* For UTF-8 items */ +#endif + + /* Process characters until ] is reached. By writing this as a "do" it + means that an initial ] is taken as a data character. At the start of the + loop, c contains the first byte of the character. */ + + if (c != 0) do + { + const uschar *oldptr; + +#ifdef SUPPORT_UTF8 + if (utf8 && c > 127) + { /* Braces are required because the */ + GETCHARLEN(c, ptr, ptr); /* macro generates multiple statements */ + } +#endif + + /* Inside \Q...\E everything is literal except \E */ + + if (inescq) + { + if (c == '\\' && ptr[1] == 'E') /* If we are at \E */ + { + inescq = FALSE; /* Reset literal state */ + ptr++; /* Skip the 'E' */ + continue; /* Carry on with next */ + } + goto CHECK_RANGE; /* Could be range if \E follows */ + } + + /* Handle POSIX class names. Perl allows a negation extension of the + form [:^name:]. A square bracket that doesn't match the syntax is + treated as a literal. We also recognize the POSIX constructions + [.ch.] and [=ch=] ("collating elements") and fault them, as Perl + 5.6 and 5.8 do. */ + + if (c == '[' && + (ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') && + check_posix_syntax(ptr, &tempptr, cd)) + { + BOOL local_negate = FALSE; + int posix_class, taboffset, tabopt; + register const uschar *cbits = cd->cbits; + uschar pbits[32]; + + if (ptr[1] != ':') + { + *errorcodeptr = ERR31; + goto FAILED; + } + + ptr += 2; + if (*ptr == '^') + { + local_negate = TRUE; + ptr++; + } + + posix_class = check_posix_name(ptr, tempptr - ptr); + if (posix_class < 0) + { + *errorcodeptr = ERR30; + goto FAILED; + } + + /* If matching is caseless, upper and lower are converted to + alpha. This relies on the fact that the class table starts with + alpha, lower, upper as the first 3 entries. */ + + if ((options & PCRE_CASELESS) != 0 && posix_class <= 2) + posix_class = 0; + + /* We build the bit map for the POSIX class in a chunk of local store + because we may be adding and subtracting from it, and we don't want to + subtract bits that may be in the main map already. At the end we or the + result into the bit map that is being built. */ + + posix_class *= 3; + + /* Copy in the first table (always present) */ + + memcpy(pbits, cbits + posix_class_maps[posix_class], + 32 * sizeof(uschar)); + + /* If there is a second table, add or remove it as required. */ + + taboffset = posix_class_maps[posix_class + 1]; + tabopt = posix_class_maps[posix_class + 2]; + + if (taboffset >= 0) + { + if (tabopt >= 0) + for (c = 0; c < 32; c++) pbits[c] |= cbits[c + taboffset]; + else + for (c = 0; c < 32; c++) pbits[c] &= ~cbits[c + taboffset]; + } + + /* Not see if we need to remove any special characters. An option + value of 1 removes vertical space and 2 removes underscore. */ + + if (tabopt < 0) tabopt = -tabopt; + if (tabopt == 1) pbits[1] &= ~0x3c; + else if (tabopt == 2) pbits[11] &= 0x7f; + + /* Add the POSIX table or its complement into the main table that is + being built and we are done. */ + + if (local_negate) + for (c = 0; c < 32; c++) classbits[c] |= ~pbits[c]; + else + for (c = 0; c < 32; c++) classbits[c] |= pbits[c]; + + ptr = tempptr + 1; + class_charcount = 10; /* Set > 1; assumes more than 1 per class */ + continue; /* End of POSIX syntax handling */ + } + + /* Backslash may introduce a single character, or it may introduce one + of the specials, which just set a flag. The sequence \b is a special + case. Inside a class (and only there) it is treated as backspace. + Elsewhere it marks a word boundary. Other escapes have preset maps ready + to 'or' into the one we are building. We assume they have more than one + character in them, so set class_charcount bigger than one. */ + + if (c == '\\') + { + c = check_escape(&ptr, errorcodeptr, cd->bracount, options, TRUE); + if (*errorcodeptr != 0) goto FAILED; + + if (-c == ESC_b) c = '\b'; /* \b is backslash in a class */ + else if (-c == ESC_X) c = 'X'; /* \X is literal X in a class */ + else if (-c == ESC_R) c = 'R'; /* \R is literal R in a class */ + else if (-c == ESC_Q) /* Handle start of quoted string */ + { + if (ptr[1] == '\\' && ptr[2] == 'E') + { + ptr += 2; /* avoid empty string */ + } + else inescq = TRUE; + continue; + } + else if (-c == ESC_E) continue; /* Ignore orphan \E */ + + if (c < 0) + { + register const uschar *cbits = cd->cbits; + class_charcount += 2; /* Greater than 1 is what matters */ + + /* Save time by not doing this in the pre-compile phase. */ + + if (lengthptr == NULL) switch (-c) + { + case ESC_d: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_digit]; + continue; + + case ESC_D: + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_digit]; + continue; + + case ESC_w: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_word]; + continue; + + case ESC_W: + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_word]; + continue; + + case ESC_s: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_space]; + classbits[1] &= ~0x08; /* Perl 5.004 onwards omits VT from \s */ + continue; + + case ESC_S: + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_space]; + classbits[1] |= 0x08; /* Perl 5.004 onwards omits VT from \s */ + continue; + + case ESC_E: /* Perl ignores an orphan \E */ + continue; + + default: /* Not recognized; fall through */ + break; /* Need "default" setting to stop compiler warning. */ + } + + /* In the pre-compile phase, just do the recognition. */ + + else if (c == -ESC_d || c == -ESC_D || c == -ESC_w || + c == -ESC_W || c == -ESC_s || c == -ESC_S) continue; + + /* We need to deal with \H, \h, \V, and \v in both phases because + they use extra memory. */ + + if (-c == ESC_h) + { + SETBIT(classbits, 0x09); /* VT */ + SETBIT(classbits, 0x20); /* SPACE */ + SETBIT(classbits, 0xa0); /* NSBP */ +#ifdef SUPPORT_UTF8 + if (utf8) + { + class_utf8 = TRUE; + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(0x1680, class_utf8data); + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(0x180e, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x2000, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x200A, class_utf8data); + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(0x202f, class_utf8data); + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(0x205f, class_utf8data); + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(0x3000, class_utf8data); + } +#endif + continue; + } + + if (-c == ESC_H) + { + for (c = 0; c < 32; c++) + { + int x = 0xff; + switch (c) + { + case 0x09/8: x ^= 1 << (0x09%8); break; + case 0x20/8: x ^= 1 << (0x20%8); break; + case 0xa0/8: x ^= 1 << (0xa0%8); break; + default: break; + } + classbits[c] |= x; + } + +#ifdef SUPPORT_UTF8 + if (utf8) + { + class_utf8 = TRUE; + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x0100, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x167f, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x1681, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x180d, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x180f, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x1fff, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x200B, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x202e, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x2030, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x205e, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x2060, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x2fff, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x3001, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x7fffffff, class_utf8data); + } +#endif + continue; + } + + if (-c == ESC_v) + { + SETBIT(classbits, 0x0a); /* LF */ + SETBIT(classbits, 0x0b); /* VT */ + SETBIT(classbits, 0x0c); /* FF */ + SETBIT(classbits, 0x0d); /* CR */ + SETBIT(classbits, 0x85); /* NEL */ +#ifdef SUPPORT_UTF8 + if (utf8) + { + class_utf8 = TRUE; + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x2028, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x2029, class_utf8data); + } +#endif + continue; + } + + if (-c == ESC_V) + { + for (c = 0; c < 32; c++) + { + int x = 0xff; + switch (c) + { + case 0x0a/8: x ^= 1 << (0x0a%8); + x ^= 1 << (0x0b%8); + x ^= 1 << (0x0c%8); + x ^= 1 << (0x0d%8); + break; + case 0x85/8: x ^= 1 << (0x85%8); break; + default: break; + } + classbits[c] |= x; + } + +#ifdef SUPPORT_UTF8 + if (utf8) + { + class_utf8 = TRUE; + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x0100, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x2027, class_utf8data); + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(0x2029, class_utf8data); + class_utf8data += _pcre_ord2utf8(0x7fffffff, class_utf8data); + } +#endif + continue; + } + + /* We need to deal with \P and \p in both phases. */ + +#ifdef SUPPORT_UCP + if (-c == ESC_p || -c == ESC_P) + { + BOOL negated; + int pdata; + int ptype = get_ucp(&ptr, &negated, &pdata, errorcodeptr); + if (ptype < 0) goto FAILED; + class_utf8 = TRUE; + *class_utf8data++ = ((-c == ESC_p) != negated)? + XCL_PROP : XCL_NOTPROP; + *class_utf8data++ = ptype; + *class_utf8data++ = pdata; + class_charcount -= 2; /* Not a < 256 character */ + continue; + } +#endif + /* Unrecognized escapes are faulted if PCRE is running in its + strict mode. By default, for compatibility with Perl, they are + treated as literals. */ + + if ((options & PCRE_EXTRA) != 0) + { + *errorcodeptr = ERR7; + goto FAILED; + } + + class_charcount -= 2; /* Undo the default count from above */ + c = *ptr; /* Get the final character and fall through */ + } + + /* Fall through if we have a single character (c >= 0). This may be + greater than 256 in UTF-8 mode. */ + + } /* End of backslash handling */ + + /* A single character may be followed by '-' to form a range. However, + Perl does not permit ']' to be the end of the range. A '-' character + at the end is treated as a literal. Perl ignores orphaned \E sequences + entirely. The code for handling \Q and \E is messy. */ + + CHECK_RANGE: + while (ptr[1] == '\\' && ptr[2] == 'E') + { + inescq = FALSE; + ptr += 2; + } + + oldptr = ptr; + + /* Remember \r or \n */ + + if (c == '\r' || c == '\n') cd->external_flags |= PCRE_HASCRORLF; + + /* Check for range */ + + if (!inescq && ptr[1] == '-') + { + int d; + ptr += 2; + while (*ptr == '\\' && ptr[1] == 'E') ptr += 2; + + /* If we hit \Q (not followed by \E) at this point, go into escaped + mode. */ + + while (*ptr == '\\' && ptr[1] == 'Q') + { + ptr += 2; + if (*ptr == '\\' && ptr[1] == 'E') { ptr += 2; continue; } + inescq = TRUE; + break; + } + + if (*ptr == 0 || (!inescq && *ptr == ']')) + { + ptr = oldptr; + goto LONE_SINGLE_CHARACTER; + } + +#ifdef SUPPORT_UTF8 + if (utf8) + { /* Braces are required because the */ + GETCHARLEN(d, ptr, ptr); /* macro generates multiple statements */ + } + else +#endif + d = *ptr; /* Not UTF-8 mode */ + + /* The second part of a range can be a single-character escape, but + not any of the other escapes. Perl 5.6 treats a hyphen as a literal + in such circumstances. */ + + if (!inescq && d == '\\') + { + d = check_escape(&ptr, errorcodeptr, cd->bracount, options, TRUE); + if (*errorcodeptr != 0) goto FAILED; + + /* \b is backslash; \X is literal X; \R is literal R; any other + special means the '-' was literal */ + + if (d < 0) + { + if (d == -ESC_b) d = '\b'; + else if (d == -ESC_X) d = 'X'; + else if (d == -ESC_R) d = 'R'; else + { + ptr = oldptr; + goto LONE_SINGLE_CHARACTER; /* A few lines below */ + } + } + } + + /* Check that the two values are in the correct order. Optimize + one-character ranges */ + + if (d < c) + { + *errorcodeptr = ERR8; + goto FAILED; + } + + if (d == c) goto LONE_SINGLE_CHARACTER; /* A few lines below */ + + /* Remember \r or \n */ + + if (d == '\r' || d == '\n') cd->external_flags |= PCRE_HASCRORLF; + + /* In UTF-8 mode, if the upper limit is > 255, or > 127 for caseless + matching, we have to use an XCLASS with extra data items. Caseless + matching for characters > 127 is available only if UCP support is + available. */ + +#ifdef SUPPORT_UTF8 + if (utf8 && (d > 255 || ((options & PCRE_CASELESS) != 0 && d > 127))) + { + class_utf8 = TRUE; + + /* With UCP support, we can find the other case equivalents of + the relevant characters. There may be several ranges. Optimize how + they fit with the basic range. */ + +#ifdef SUPPORT_UCP + if ((options & PCRE_CASELESS) != 0) + { + unsigned int occ, ocd; + unsigned int cc = c; + unsigned int origd = d; + while (get_othercase_range(&cc, origd, &occ, &ocd)) + { + if (occ >= (unsigned int)c && + ocd <= (unsigned int)d) + continue; /* Skip embedded ranges */ + + if (occ < (unsigned int)c && + ocd >= (unsigned int)c - 1) /* Extend the basic range */ + { /* if there is overlap, */ + c = occ; /* noting that if occ < c */ + continue; /* we can't have ocd > d */ + } /* because a subrange is */ + if (ocd > (unsigned int)d && + occ <= (unsigned int)d + 1) /* always shorter than */ + { /* the basic range. */ + d = ocd; + continue; + } + + if (occ == ocd) + { + *class_utf8data++ = XCL_SINGLE; + } + else + { + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(occ, class_utf8data); + } + class_utf8data += _pcre_ord2utf8(ocd, class_utf8data); + } + } +#endif /* SUPPORT_UCP */ + + /* Now record the original range, possibly modified for UCP caseless + overlapping ranges. */ + + *class_utf8data++ = XCL_RANGE; + class_utf8data += _pcre_ord2utf8(c, class_utf8data); + class_utf8data += _pcre_ord2utf8(d, class_utf8data); + + /* With UCP support, we are done. Without UCP support, there is no + caseless matching for UTF-8 characters > 127; we can use the bit map + for the smaller ones. */ + +#ifdef SUPPORT_UCP + continue; /* With next character in the class */ +#else + if ((options & PCRE_CASELESS) == 0 || c > 127) continue; + + /* Adjust upper limit and fall through to set up the map */ + + d = 127; + +#endif /* SUPPORT_UCP */ + } +#endif /* SUPPORT_UTF8 */ + + /* We use the bit map for all cases when not in UTF-8 mode; else + ranges that lie entirely within 0-127 when there is UCP support; else + for partial ranges without UCP support. */ + + class_charcount += d - c + 1; + class_lastchar = d; + + /* We can save a bit of time by skipping this in the pre-compile. */ + + if (lengthptr == NULL) for (; c <= d; c++) + { + classbits[c/8] |= (1 << (c&7)); + if ((options & PCRE_CASELESS) != 0) + { + int uc = cd->fcc[c]; /* flip case */ + classbits[uc/8] |= (1 << (uc&7)); + } + } + + continue; /* Go get the next char in the class */ + } + + /* Handle a lone single character - we can get here for a normal + non-escape char, or after \ that introduces a single character or for an + apparent range that isn't. */ + + LONE_SINGLE_CHARACTER: + + /* Handle a character that cannot go in the bit map */ + +#ifdef SUPPORT_UTF8 + if (utf8 && (c > 255 || ((options & PCRE_CASELESS) != 0 && c > 127))) + { + class_utf8 = TRUE; + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(c, class_utf8data); + +#ifdef SUPPORT_UCP + if ((options & PCRE_CASELESS) != 0) + { + unsigned int othercase; + if ((othercase = _pcre_ucp_othercase(c)) != NOTACHAR) + { + *class_utf8data++ = XCL_SINGLE; + class_utf8data += _pcre_ord2utf8(othercase, class_utf8data); + } + } +#endif /* SUPPORT_UCP */ + + } + else +#endif /* SUPPORT_UTF8 */ + + /* Handle a single-byte character */ + { + classbits[c/8] |= (1 << (c&7)); + if ((options & PCRE_CASELESS) != 0) + { + c = cd->fcc[c]; /* flip case */ + classbits[c/8] |= (1 << (c&7)); + } + class_charcount++; + class_lastchar = c; + } + } + + /* Loop until ']' reached. This "while" is the end of the "do" above. */ + + while ((c = *(++ptr)) != 0 && (c != ']' || inescq)); + + if (c == 0) /* Missing terminating ']' */ + { + *errorcodeptr = ERR6; + goto FAILED; + } + + +/* This code has been disabled because it would mean that \s counts as +an explicit \r or \n reference, and that's not really what is wanted. Now +we set the flag only if there is a literal "\r" or "\n" in the class. */ + +#if 0 + /* Remember whether \r or \n are in this class */ + + if (negate_class) + { + if ((classbits[1] & 0x24) != 0x24) cd->external_flags |= PCRE_HASCRORLF; + } + else + { + if ((classbits[1] & 0x24) != 0) cd->external_flags |= PCRE_HASCRORLF; + } +#endif + + + /* If class_charcount is 1, we saw precisely one character whose value is + less than 256. As long as there were no characters >= 128 and there was no + use of \p or \P, in other words, no use of any XCLASS features, we can + optimize. + + In UTF-8 mode, we can optimize the negative case only if there were no + characters >= 128 because OP_NOT and the related opcodes like OP_NOTSTAR + operate on single-bytes only. This is an historical hangover. Maybe one day + we can tidy these opcodes to handle multi-byte characters. + + The optimization throws away the bit map. We turn the item into a + 1-character OP_CHAR[NC] if it's positive, or OP_NOT if it's negative. Note + that OP_NOT does not support multibyte characters. In the positive case, it + can cause firstbyte to be set. Otherwise, there can be no first char if + this item is first, whatever repeat count may follow. In the case of + reqbyte, save the previous value for reinstating. */ + +#ifdef SUPPORT_UTF8 + if (class_charcount == 1 && !class_utf8 && + (!utf8 || !negate_class || class_lastchar < 128)) +#else + if (class_charcount == 1) +#endif + { + zeroreqbyte = reqbyte; + + /* The OP_NOT opcode works on one-byte characters only. */ + + if (negate_class) + { + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + zerofirstbyte = firstbyte; + *code++ = OP_NOT; + *code++ = class_lastchar; + break; + } + + /* For a single, positive character, get the value into mcbuffer, and + then we can handle this with the normal one-character code. */ + +#ifdef SUPPORT_UTF8 + if (utf8 && class_lastchar > 127) + mclength = _pcre_ord2utf8(class_lastchar, mcbuffer); + else +#endif + { + mcbuffer[0] = class_lastchar; + mclength = 1; + } + goto ONE_CHAR; + } /* End of 1-char optimization */ + + /* The general case - not the one-char optimization. If this is the first + thing in the branch, there can be no first char setting, whatever the + repeat count. Any reqbyte setting must remain unchanged after any kind of + repeat. */ + + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + zerofirstbyte = firstbyte; + zeroreqbyte = reqbyte; + + /* If there are characters with values > 255, we have to compile an + extended class, with its own opcode. If there are no characters < 256, + we can omit the bitmap in the actual compiled code. */ + +#ifdef SUPPORT_UTF8 + if (class_utf8) + { + *class_utf8data++ = XCL_END; /* Marks the end of extra data */ + *code++ = OP_XCLASS; + code += LINK_SIZE; + *code = negate_class? XCL_NOT : 0; + + /* If the map is required, move up the extra data to make room for it; + otherwise just move the code pointer to the end of the extra data. */ + + if (class_charcount > 0) + { + *code++ |= XCL_MAP; + memmove(code + 32, code, class_utf8data - code); + memcpy(code, classbits, 32); + code = class_utf8data + 32; + } + else code = class_utf8data; + + /* Now fill in the complete length of the item */ + + PUT(previous, 1, code - previous); + break; /* End of class handling */ + } +#endif + + /* If there are no characters > 255, negate the 32-byte map if necessary, + and copy it into the code vector. If this is the first thing in the branch, + there can be no first char setting, whatever the repeat count. Any reqbyte + setting must remain unchanged after any kind of repeat. */ + + if (negate_class) + { + *code++ = OP_NCLASS; + if (lengthptr == NULL) /* Save time in the pre-compile phase */ + for (c = 0; c < 32; c++) code[c] = ~classbits[c]; + } + else + { + *code++ = OP_CLASS; + memcpy(code, classbits, 32); + } + code += 32; + break; + + + /* ===================================================================*/ + /* Various kinds of repeat; '{' is not necessarily a quantifier, but this + has been tested above. */ + + case '{': + if (!is_quantifier) goto NORMAL_CHAR; + ptr = read_repeat_counts(ptr+1, &repeat_min, &repeat_max, errorcodeptr); + if (*errorcodeptr != 0) goto FAILED; + goto REPEAT; + + case '*': + repeat_min = 0; + repeat_max = -1; + goto REPEAT; + + case '+': + repeat_min = 1; + repeat_max = -1; + goto REPEAT; + + case '?': + repeat_min = 0; + repeat_max = 1; + + REPEAT: + if (previous == NULL) + { + *errorcodeptr = ERR9; + goto FAILED; + } + + if (repeat_min == 0) + { + firstbyte = zerofirstbyte; /* Adjust for zero repeat */ + reqbyte = zeroreqbyte; /* Ditto */ + } + + /* Remember whether this is a variable length repeat */ + + reqvary = (repeat_min == repeat_max)? 0 : REQ_VARY; + + op_type = 0; /* Default single-char op codes */ + possessive_quantifier = FALSE; /* Default not possessive quantifier */ + + /* Save start of previous item, in case we have to move it up to make space + for an inserted OP_ONCE for the additional '+' extension. */ + + tempcode = previous; + + /* If the next character is '+', we have a possessive quantifier. This + implies greediness, whatever the setting of the PCRE_UNGREEDY option. + If the next character is '?' this is a minimizing repeat, by default, + but if PCRE_UNGREEDY is set, it works the other way round. We change the + repeat type to the non-default. */ + + if (ptr[1] == '+') + { + repeat_type = 0; /* Force greedy */ + possessive_quantifier = TRUE; + ptr++; + } + else if (ptr[1] == '?') + { + repeat_type = greedy_non_default; + ptr++; + } + else repeat_type = greedy_default; + + /* If previous was a character match, abolish the item and generate a + repeat item instead. If a char item has a minumum of more than one, ensure + that it is set in reqbyte - it might not be if a sequence such as x{3} is + the first thing in a branch because the x will have gone into firstbyte + instead. */ + + if (*previous == OP_CHAR || *previous == OP_CHARNC) + { + /* Deal with UTF-8 characters that take up more than one byte. It's + easier to write this out separately than try to macrify it. Use c to + hold the length of the character in bytes, plus 0x80 to flag that it's a + length rather than a small character. */ + +#ifdef SUPPORT_UTF8 + if (utf8 && (code[-1] & 0x80) != 0) + { + uschar *lastchar = code - 1; + while((*lastchar & 0xc0) == 0x80) lastchar--; + c = code - lastchar; /* Length of UTF-8 character */ + memcpy(utf8_char, lastchar, c); /* Save the char */ + c |= 0x80; /* Flag c as a length */ + } + else +#endif + + /* Handle the case of a single byte - either with no UTF8 support, or + with UTF-8 disabled, or for a UTF-8 character < 128. */ + + { + c = code[-1]; + if (repeat_min > 1) reqbyte = c | req_caseopt | cd->req_varyopt; + } + + /* If the repetition is unlimited, it pays to see if the next thing on + the line is something that cannot possibly match this character. If so, + automatically possessifying this item gains some performance in the case + where the match fails. */ + + if (!possessive_quantifier && + repeat_max < 0 && + check_auto_possessive(*previous, c, utf8, utf8_char, ptr + 1, + options, cd)) + { + repeat_type = 0; /* Force greedy */ + possessive_quantifier = TRUE; + } + + goto OUTPUT_SINGLE_REPEAT; /* Code shared with single character types */ + } + + /* If previous was a single negated character ([^a] or similar), we use + one of the special opcodes, replacing it. The code is shared with single- + character repeats by setting opt_type to add a suitable offset into + repeat_type. We can also test for auto-possessification. OP_NOT is + currently used only for single-byte chars. */ + + else if (*previous == OP_NOT) + { + op_type = OP_NOTSTAR - OP_STAR; /* Use "not" opcodes */ + c = previous[1]; + if (!possessive_quantifier && + repeat_max < 0 && + check_auto_possessive(OP_NOT, c, utf8, NULL, ptr + 1, options, cd)) + { + repeat_type = 0; /* Force greedy */ + possessive_quantifier = TRUE; + } + goto OUTPUT_SINGLE_REPEAT; + } + + /* If previous was a character type match (\d or similar), abolish it and + create a suitable repeat item. The code is shared with single-character + repeats by setting op_type to add a suitable offset into repeat_type. Note + the the Unicode property types will be present only when SUPPORT_UCP is + defined, but we don't wrap the little bits of code here because it just + makes it horribly messy. */ + + else if (*previous < OP_EODN) + { + uschar *oldcode; + int prop_type, prop_value; + op_type = OP_TYPESTAR - OP_STAR; /* Use type opcodes */ + c = *previous; + + if (!possessive_quantifier && + repeat_max < 0 && + check_auto_possessive(c, 0, utf8, NULL, ptr + 1, options, cd)) + { + repeat_type = 0; /* Force greedy */ + possessive_quantifier = TRUE; + } + + OUTPUT_SINGLE_REPEAT: + if (*previous == OP_PROP || *previous == OP_NOTPROP) + { + prop_type = previous[1]; + prop_value = previous[2]; + } + else prop_type = prop_value = -1; + + oldcode = code; + code = previous; /* Usually overwrite previous item */ + + /* If the maximum is zero then the minimum must also be zero; Perl allows + this case, so we do too - by simply omitting the item altogether. */ + + if (repeat_max == 0) goto END_REPEAT; + + /* All real repeats make it impossible to handle partial matching (maybe + one day we will be able to remove this restriction). */ + + if (repeat_max != 1) cd->external_flags |= PCRE_NOPARTIAL; + + /* Combine the op_type with the repeat_type */ + + repeat_type += op_type; + + /* A minimum of zero is handled either as the special case * or ?, or as + an UPTO, with the maximum given. */ + + if (repeat_min == 0) + { + if (repeat_max == -1) *code++ = OP_STAR + repeat_type; + else if (repeat_max == 1) *code++ = OP_QUERY + repeat_type; + else + { + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max); + } + } + + /* A repeat minimum of 1 is optimized into some special cases. If the + maximum is unlimited, we use OP_PLUS. Otherwise, the original item is + left in place and, if the maximum is greater than 1, we use OP_UPTO with + one less than the maximum. */ + + else if (repeat_min == 1) + { + if (repeat_max == -1) + *code++ = OP_PLUS + repeat_type; + else + { + code = oldcode; /* leave previous item in place */ + if (repeat_max == 1) goto END_REPEAT; + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max - 1); + } + } + + /* The case {n,n} is just an EXACT, while the general case {n,m} is + handled as an EXACT followed by an UPTO. */ + + else + { + *code++ = OP_EXACT + op_type; /* NB EXACT doesn't have repeat_type */ + PUT2INC(code, 0, repeat_min); + + /* If the maximum is unlimited, insert an OP_STAR. Before doing so, + we have to insert the character for the previous code. For a repeated + Unicode property match, there are two extra bytes that define the + required property. In UTF-8 mode, long characters have their length in + c, with the 0x80 bit as a flag. */ + + if (repeat_max < 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && c >= 128) + { + memcpy(code, utf8_char, c & 7); + code += c & 7; + } + else +#endif + { + *code++ = c; + if (prop_type >= 0) + { + *code++ = prop_type; + *code++ = prop_value; + } + } + *code++ = OP_STAR + repeat_type; + } + + /* Else insert an UPTO if the max is greater than the min, again + preceded by the character, for the previously inserted code. If the + UPTO is just for 1 instance, we can use QUERY instead. */ + + else if (repeat_max != repeat_min) + { +#ifdef SUPPORT_UTF8 + if (utf8 && c >= 128) + { + memcpy(code, utf8_char, c & 7); + code += c & 7; + } + else +#endif + *code++ = c; + if (prop_type >= 0) + { + *code++ = prop_type; + *code++ = prop_value; + } + repeat_max -= repeat_min; + + if (repeat_max == 1) + { + *code++ = OP_QUERY + repeat_type; + } + else + { + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max); + } + } + } + + /* The character or character type itself comes last in all cases. */ + +#ifdef SUPPORT_UTF8 + if (utf8 && c >= 128) + { + memcpy(code, utf8_char, c & 7); + code += c & 7; + } + else +#endif + *code++ = c; + + /* For a repeated Unicode property match, there are two extra bytes that + define the required property. */ + +#ifdef SUPPORT_UCP + if (prop_type >= 0) + { + *code++ = prop_type; + *code++ = prop_value; + } +#endif + } + + /* If previous was a character class or a back reference, we put the repeat + stuff after it, but just skip the item if the repeat was {0,0}. */ + + else if (*previous == OP_CLASS || + *previous == OP_NCLASS || +#ifdef SUPPORT_UTF8 + *previous == OP_XCLASS || +#endif + *previous == OP_REF) + { + if (repeat_max == 0) + { + code = previous; + goto END_REPEAT; + } + + /* All real repeats make it impossible to handle partial matching (maybe + one day we will be able to remove this restriction). */ + + if (repeat_max != 1) cd->external_flags |= PCRE_NOPARTIAL; + + if (repeat_min == 0 && repeat_max == -1) + *code++ = OP_CRSTAR + repeat_type; + else if (repeat_min == 1 && repeat_max == -1) + *code++ = OP_CRPLUS + repeat_type; + else if (repeat_min == 0 && repeat_max == 1) + *code++ = OP_CRQUERY + repeat_type; + else + { + *code++ = OP_CRRANGE + repeat_type; + PUT2INC(code, 0, repeat_min); + if (repeat_max == -1) repeat_max = 0; /* 2-byte encoding for max */ + PUT2INC(code, 0, repeat_max); + } + } + + /* If previous was a bracket group, we may have to replicate it in certain + cases. */ + + else if (*previous == OP_BRA || *previous == OP_CBRA || + *previous == OP_ONCE || *previous == OP_COND) + { + register int i; + int ketoffset = 0; + int len = code - previous; + uschar *bralink = NULL; + + /* Repeating a DEFINE group is pointless */ + + if (*previous == OP_COND && previous[LINK_SIZE+1] == OP_DEF) + { + *errorcodeptr = ERR55; + goto FAILED; + } + + /* If the maximum repeat count is unlimited, find the end of the bracket + by scanning through from the start, and compute the offset back to it + from the current code pointer. There may be an OP_OPT setting following + the final KET, so we can't find the end just by going back from the code + pointer. */ + + if (repeat_max == -1) + { + register uschar *ket = previous; + do ket += GET(ket, 1); while (*ket != OP_KET); + ketoffset = code - ket; + } + + /* The case of a zero minimum is special because of the need to stick + OP_BRAZERO in front of it, and because the group appears once in the + data, whereas in other cases it appears the minimum number of times. For + this reason, it is simplest to treat this case separately, as otherwise + the code gets far too messy. There are several special subcases when the + minimum is zero. */ + + if (repeat_min == 0) + { + /* If the maximum is also zero, we just omit the group from the output + altogether. */ + + if (repeat_max == 0) + { + code = previous; + goto END_REPEAT; + } + + /* If the maximum is 1 or unlimited, we just have to stick in the + BRAZERO and do no more at this point. However, we do need to adjust + any OP_RECURSE calls inside the group that refer to the group itself or + any internal or forward referenced group, because the offset is from + the start of the whole regex. Temporarily terminate the pattern while + doing this. */ + + if (repeat_max <= 1) + { + *code = OP_END; + adjust_recurse(previous, 1, utf8, cd, save_hwm); + memmove(previous+1, previous, len); + code++; + *previous++ = OP_BRAZERO + repeat_type; + } + + /* If the maximum is greater than 1 and limited, we have to replicate + in a nested fashion, sticking OP_BRAZERO before each set of brackets. + The first one has to be handled carefully because it's the original + copy, which has to be moved up. The remainder can be handled by code + that is common with the non-zero minimum case below. We have to + adjust the value or repeat_max, since one less copy is required. Once + again, we may have to adjust any OP_RECURSE calls inside the group. */ + + else + { + int offset; + *code = OP_END; + adjust_recurse(previous, 2 + LINK_SIZE, utf8, cd, save_hwm); + memmove(previous + 2 + LINK_SIZE, previous, len); + code += 2 + LINK_SIZE; + *previous++ = OP_BRAZERO + repeat_type; + *previous++ = OP_BRA; + + /* We chain together the bracket offset fields that have to be + filled in later when the ends of the brackets are reached. */ + + offset = (bralink == NULL)? 0 : previous - bralink; + bralink = previous; + PUTINC(previous, 0, offset); + } + + repeat_max--; + } + + /* If the minimum is greater than zero, replicate the group as many + times as necessary, and adjust the maximum to the number of subsequent + copies that we need. If we set a first char from the group, and didn't + set a required char, copy the latter from the former. If there are any + forward reference subroutine calls in the group, there will be entries on + the workspace list; replicate these with an appropriate increment. */ + + else + { + if (repeat_min > 1) + { + /* In the pre-compile phase, we don't actually do the replication. We + just adjust the length as if we had. Do some paranoid checks for + potential integer overflow. */ + + if (lengthptr != NULL) + { + int delta = (repeat_min - 1)*length_prevgroup; + if ((double)(repeat_min - 1)*(double)length_prevgroup > + (double)INT_MAX || + OFLOW_MAX - *lengthptr < delta) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += delta; + } + + /* This is compiling for real */ + + else + { + if (groupsetfirstbyte && reqbyte < 0) reqbyte = firstbyte; + for (i = 1; i < repeat_min; i++) + { + uschar *hc; + uschar *this_hwm = cd->hwm; + memcpy(code, previous, len); + for (hc = save_hwm; hc < this_hwm; hc += LINK_SIZE) + { + PUT(cd->hwm, 0, GET(hc, 0) + len); + cd->hwm += LINK_SIZE; + } + save_hwm = this_hwm; + code += len; + } + } + } + + if (repeat_max > 0) repeat_max -= repeat_min; + } + + /* This code is common to both the zero and non-zero minimum cases. If + the maximum is limited, it replicates the group in a nested fashion, + remembering the bracket starts on a stack. In the case of a zero minimum, + the first one was set up above. In all cases the repeat_max now specifies + the number of additional copies needed. Again, we must remember to + replicate entries on the forward reference list. */ + + if (repeat_max >= 0) + { + /* In the pre-compile phase, we don't actually do the replication. We + just adjust the length as if we had. For each repetition we must add 1 + to the length for BRAZERO and for all but the last repetition we must + add 2 + 2*LINKSIZE to allow for the nesting that occurs. Do some + paranoid checks to avoid integer overflow. */ + + if (lengthptr != NULL && repeat_max > 0) + { + int delta = repeat_max * (length_prevgroup + 1 + 2 + 2*LINK_SIZE) - + 2 - 2*LINK_SIZE; /* Last one doesn't nest */ + if ((double)repeat_max * + (double)(length_prevgroup + 1 + 2 + 2*LINK_SIZE) + > (double)INT_MAX || + OFLOW_MAX - *lengthptr < delta) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += delta; + } + + /* This is compiling for real */ + + else for (i = repeat_max - 1; i >= 0; i--) + { + uschar *hc; + uschar *this_hwm = cd->hwm; + + *code++ = OP_BRAZERO + repeat_type; + + /* All but the final copy start a new nesting, maintaining the + chain of brackets outstanding. */ + + if (i != 0) + { + int offset; + *code++ = OP_BRA; + offset = (bralink == NULL)? 0 : code - bralink; + bralink = code; + PUTINC(code, 0, offset); + } + + memcpy(code, previous, len); + for (hc = save_hwm; hc < this_hwm; hc += LINK_SIZE) + { + PUT(cd->hwm, 0, GET(hc, 0) + len + ((i != 0)? 2+LINK_SIZE : 1)); + cd->hwm += LINK_SIZE; + } + save_hwm = this_hwm; + code += len; + } + + /* Now chain through the pending brackets, and fill in their length + fields (which are holding the chain links pro tem). */ + + while (bralink != NULL) + { + int oldlinkoffset; + int offset = code - bralink + 1; + uschar *bra = code - offset; + oldlinkoffset = GET(bra, 1); + bralink = (oldlinkoffset == 0)? NULL : bralink - oldlinkoffset; + *code++ = OP_KET; + PUTINC(code, 0, offset); + PUT(bra, 1, offset); + } + } + + /* If the maximum is unlimited, set a repeater in the final copy. We + can't just offset backwards from the current code point, because we + don't know if there's been an options resetting after the ket. The + correct offset was computed above. + + Then, when we are doing the actual compile phase, check to see whether + this group is a non-atomic one that could match an empty string. If so, + convert the initial operator to the S form (e.g. OP_BRA -> OP_SBRA) so + that runtime checking can be done. [This check is also applied to + atomic groups at runtime, but in a different way.] */ + + else + { + uschar *ketcode = code - ketoffset; + uschar *bracode = ketcode - GET(ketcode, 1); + *ketcode = OP_KETRMAX + repeat_type; + if (lengthptr == NULL && *bracode != OP_ONCE) + { + uschar *scode = bracode; + do + { + if (could_be_empty_branch(scode, ketcode, utf8)) + { + *bracode += OP_SBRA - OP_BRA; + break; + } + scode += GET(scode, 1); + } + while (*scode == OP_ALT); + } + } + } + + /* Else there's some kind of shambles */ + + else + { + *errorcodeptr = ERR11; + goto FAILED; + } + + /* If the character following a repeat is '+', or if certain optimization + tests above succeeded, possessive_quantifier is TRUE. For some of the + simpler opcodes, there is an special alternative opcode for this. For + anything else, we wrap the entire repeated item inside OP_ONCE brackets. + The '+' notation is just syntactic sugar, taken from Sun's Java package, + but the special opcodes can optimize it a bit. The repeated item starts at + tempcode, not at previous, which might be the first part of a string whose + (former) last char we repeated. + + Possessifying an 'exact' quantifier has no effect, so we can ignore it. But + an 'upto' may follow. We skip over an 'exact' item, and then test the + length of what remains before proceeding. */ + + if (possessive_quantifier) + { + int len; + if (*tempcode == OP_EXACT || *tempcode == OP_TYPEEXACT || + *tempcode == OP_NOTEXACT) + tempcode += _pcre_OP_lengths[*tempcode]; + len = code - tempcode; + if (len > 0) switch (*tempcode) + { + case OP_STAR: *tempcode = OP_POSSTAR; break; + case OP_PLUS: *tempcode = OP_POSPLUS; break; + case OP_QUERY: *tempcode = OP_POSQUERY; break; + case OP_UPTO: *tempcode = OP_POSUPTO; break; + + case OP_TYPESTAR: *tempcode = OP_TYPEPOSSTAR; break; + case OP_TYPEPLUS: *tempcode = OP_TYPEPOSPLUS; break; + case OP_TYPEQUERY: *tempcode = OP_TYPEPOSQUERY; break; + case OP_TYPEUPTO: *tempcode = OP_TYPEPOSUPTO; break; + + case OP_NOTSTAR: *tempcode = OP_NOTPOSSTAR; break; + case OP_NOTPLUS: *tempcode = OP_NOTPOSPLUS; break; + case OP_NOTQUERY: *tempcode = OP_NOTPOSQUERY; break; + case OP_NOTUPTO: *tempcode = OP_NOTPOSUPTO; break; + + default: + memmove(tempcode + 1+LINK_SIZE, tempcode, len); + code += 1 + LINK_SIZE; + len += 1 + LINK_SIZE; + tempcode[0] = OP_ONCE; + *code++ = OP_KET; + PUTINC(code, 0, len); + PUT(tempcode, 1, len); + break; + } + } + + /* In all case we no longer have a previous item. We also set the + "follows varying string" flag for subsequently encountered reqbytes if + it isn't already set and we have just passed a varying length item. */ + + END_REPEAT: + previous = NULL; + cd->req_varyopt |= reqvary; + break; + + + /* ===================================================================*/ + /* Start of nested parenthesized sub-expression, or comment or lookahead or + lookbehind or option setting or condition or all the other extended + parenthesis forms. */ + + case '(': + newoptions = options; + skipbytes = 0; + bravalue = OP_CBRA; + save_hwm = cd->hwm; + reset_bracount = FALSE; + + /* First deal with various "verbs" that can be introduced by '*'. */ + + if (*(++ptr) == '*' && (cd->ctypes[ptr[1]] & ctype_letter) != 0) + { + int i, namelen; + const char *vn = verbnames; + const uschar *name = ++ptr; + previous = NULL; + while ((cd->ctypes[*++ptr] & ctype_letter) != 0); + if (*ptr == ':') + { + *errorcodeptr = ERR59; /* Not supported */ + goto FAILED; + } + if (*ptr != ')') + { + *errorcodeptr = ERR60; + goto FAILED; + } + namelen = ptr - name; + for (i = 0; i < verbcount; i++) + { + if (namelen == verbs[i].len && + strncmp((char *)name, vn, namelen) == 0) + { + *code = verbs[i].op; + if (*code++ == OP_ACCEPT) cd->had_accept = TRUE; + break; + } + vn += verbs[i].len + 1; + } + if (i < verbcount) continue; + *errorcodeptr = ERR60; + goto FAILED; + } + + /* Deal with the extended parentheses; all are introduced by '?', and the + appearance of any of them means that this is not a capturing group. */ + + else if (*ptr == '?') + { + int i, set, unset, namelen; + int *optset; + const uschar *name; + uschar *slot; + + switch (*(++ptr)) + { + case '#': /* Comment; skip to ket */ + ptr++; + while (*ptr != 0 && *ptr != ')') ptr++; + if (*ptr == 0) + { + *errorcodeptr = ERR18; + goto FAILED; + } + continue; + + + /* ------------------------------------------------------------ */ + case '|': /* Reset capture count for each branch */ + reset_bracount = TRUE; + /* Fall through */ + + /* ------------------------------------------------------------ */ + case ':': /* Non-capturing bracket */ + bravalue = OP_BRA; + ptr++; + break; + + + /* ------------------------------------------------------------ */ + case '(': + bravalue = OP_COND; /* Conditional group */ + + /* A condition can be an assertion, a number (referring to a numbered + group), a name (referring to a named group), or 'R', referring to + recursion. R<digits> and R&name are also permitted for recursion tests. + + There are several syntaxes for testing a named group: (?(name)) is used + by Python; Perl 5.10 onwards uses (?(<name>) or (?('name')). + + There are two unfortunate ambiguities, caused by history. (a) 'R' can + be the recursive thing or the name 'R' (and similarly for 'R' followed + by digits), and (b) a number could be a name that consists of digits. + In both cases, we look for a name first; if not found, we try the other + cases. */ + + /* For conditions that are assertions, check the syntax, and then exit + the switch. This will take control down to where bracketed groups, + including assertions, are processed. */ + + if (ptr[1] == '?' && (ptr[2] == '=' || ptr[2] == '!' || ptr[2] == '<')) + break; + + /* Most other conditions use OP_CREF (a couple change to OP_RREF + below), and all need to skip 3 bytes at the start of the group. */ + + code[1+LINK_SIZE] = OP_CREF; + skipbytes = 3; + refsign = -1; + + /* Check for a test for recursion in a named group. */ + + if (ptr[1] == 'R' && ptr[2] == '&') + { + terminator = -1; + ptr += 2; + code[1+LINK_SIZE] = OP_RREF; /* Change the type of test */ + } + + /* Check for a test for a named group's having been set, using the Perl + syntax (?(<name>) or (?('name') */ + + else if (ptr[1] == '<') + { + terminator = '>'; + ptr++; + } + else if (ptr[1] == '\'') + { + terminator = '\''; + ptr++; + } + else + { + terminator = 0; + if (ptr[1] == '-' || ptr[1] == '+') refsign = *(++ptr); + } + + /* We now expect to read a name; any thing else is an error */ + + if ((cd->ctypes[ptr[1]] & ctype_word) == 0) + { + ptr += 1; /* To get the right offset */ + *errorcodeptr = ERR28; + goto FAILED; + } + + /* Read the name, but also get it as a number if it's all digits */ + + recno = 0; + name = ++ptr; + while ((cd->ctypes[*ptr] & ctype_word) != 0) + { + if (recno >= 0) + recno = ((digitab[*ptr] & ctype_digit) != 0)? + recno * 10 + *ptr - '0' : -1; + ptr++; + } + namelen = ptr - name; + + if ((terminator > 0 && *ptr++ != terminator) || *ptr++ != ')') + { + ptr--; /* Error offset */ + *errorcodeptr = ERR26; + goto FAILED; + } + + /* Do no further checking in the pre-compile phase. */ + + if (lengthptr != NULL) break; + + /* In the real compile we do the work of looking for the actual + reference. If the string started with "+" or "-" we require the rest to + be digits, in which case recno will be set. */ + + if (refsign > 0) + { + if (recno <= 0) + { + *errorcodeptr = ERR58; + goto FAILED; + } + if (refsign == '-') + { + recno = cd->bracount - recno + 1; + if (recno <= 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + } + else recno += cd->bracount; + PUT2(code, 2+LINK_SIZE, recno); + break; + } + + /* Otherwise (did not start with "+" or "-"), start by looking for the + name. */ + + slot = cd->name_table; + for (i = 0; i < cd->names_found; i++) + { + if (strncmp((char *)name, (char *)slot+2, namelen) == 0) break; + slot += cd->name_entry_size; + } + + /* Found a previous named subpattern */ + + if (i < cd->names_found) + { + recno = GET2(slot, 0); + PUT2(code, 2+LINK_SIZE, recno); + } + + /* Search the pattern for a forward reference */ + + else if ((i = find_parens(ptr, cd->bracount, name, namelen, + (options & PCRE_EXTENDED) != 0)) > 0) + { + PUT2(code, 2+LINK_SIZE, i); + } + + /* If terminator == 0 it means that the name followed directly after + the opening parenthesis [e.g. (?(abc)...] and in this case there are + some further alternatives to try. For the cases where terminator != 0 + [things like (?(<name>... or (?('name')... or (?(R&name)... ] we have + now checked all the possibilities, so give an error. */ + + else if (terminator != 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + + /* Check for (?(R) for recursion. Allow digits after R to specify a + specific group number. */ + + else if (*name == 'R') + { + recno = 0; + for (i = 1; i < namelen; i++) + { + if ((digitab[name[i]] & ctype_digit) == 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + recno = recno * 10 + name[i] - '0'; + } + if (recno == 0) recno = RREF_ANY; + code[1+LINK_SIZE] = OP_RREF; /* Change test type */ + PUT2(code, 2+LINK_SIZE, recno); + } + + /* Similarly, check for the (?(DEFINE) "condition", which is always + false. */ + + else if (namelen == 6 && strncmp((char *)name, "DEFINE", 6) == 0) + { + code[1+LINK_SIZE] = OP_DEF; + skipbytes = 1; + } + + /* Check for the "name" actually being a subpattern number. */ + + else if (recno > 0) + { + PUT2(code, 2+LINK_SIZE, recno); + } + + /* Either an unidentified subpattern, or a reference to (?(0) */ + + else + { + *errorcodeptr = (recno == 0)? ERR35: ERR15; + goto FAILED; + } + break; + + + /* ------------------------------------------------------------ */ + case '=': /* Positive lookahead */ + bravalue = OP_ASSERT; + ptr++; + break; + + + /* ------------------------------------------------------------ */ + case '!': /* Negative lookahead */ + ptr++; + if (*ptr == ')') /* Optimize (?!) */ + { + *code++ = OP_FAIL; + previous = NULL; + continue; + } + bravalue = OP_ASSERT_NOT; + break; + + + /* ------------------------------------------------------------ */ + case '<': /* Lookbehind or named define */ + switch (ptr[1]) + { + case '=': /* Positive lookbehind */ + bravalue = OP_ASSERTBACK; + ptr += 2; + break; + + case '!': /* Negative lookbehind */ + bravalue = OP_ASSERTBACK_NOT; + ptr += 2; + break; + + default: /* Could be name define, else bad */ + if ((cd->ctypes[ptr[1]] & ctype_word) != 0) goto DEFINE_NAME; + ptr++; /* Correct offset for error */ + *errorcodeptr = ERR24; + goto FAILED; + } + break; + + + /* ------------------------------------------------------------ */ + case '>': /* One-time brackets */ + bravalue = OP_ONCE; + ptr++; + break; + + + /* ------------------------------------------------------------ */ + case 'C': /* Callout - may be followed by digits; */ + previous_callout = code; /* Save for later completion */ + after_manual_callout = 1; /* Skip one item before completing */ + *code++ = OP_CALLOUT; + { + int n = 0; + while ((digitab[*(++ptr)] & ctype_digit) != 0) + n = n * 10 + *ptr - '0'; + if (*ptr != ')') + { + *errorcodeptr = ERR39; + goto FAILED; + } + if (n > 255) + { + *errorcodeptr = ERR38; + goto FAILED; + } + *code++ = n; + PUT(code, 0, ptr - cd->start_pattern + 1); /* Pattern offset */ + PUT(code, LINK_SIZE, 0); /* Default length */ + code += 2 * LINK_SIZE; + } + previous = NULL; + continue; + + + /* ------------------------------------------------------------ */ + case 'P': /* Python-style named subpattern handling */ + if (*(++ptr) == '=' || *ptr == '>') /* Reference or recursion */ + { + is_recurse = *ptr == '>'; + terminator = ')'; + goto NAMED_REF_OR_RECURSE; + } + else if (*ptr != '<') /* Test for Python-style definition */ + { + *errorcodeptr = ERR41; + goto FAILED; + } + /* Fall through to handle (?P< as (?< is handled */ + + + /* ------------------------------------------------------------ */ + DEFINE_NAME: /* Come here from (?< handling */ + case '\'': + { + terminator = (*ptr == '<')? '>' : '\''; + name = ++ptr; + + while ((cd->ctypes[*ptr] & ctype_word) != 0) ptr++; + namelen = ptr - name; + + /* In the pre-compile phase, just do a syntax check. */ + + if (lengthptr != NULL) + { + if (*ptr != terminator) + { + *errorcodeptr = ERR42; + goto FAILED; + } + if (cd->names_found >= MAX_NAME_COUNT) + { + *errorcodeptr = ERR49; + goto FAILED; + } + if (namelen + 3 > cd->name_entry_size) + { + cd->name_entry_size = namelen + 3; + if (namelen > MAX_NAME_SIZE) + { + *errorcodeptr = ERR48; + goto FAILED; + } + } + } + + /* In the real compile, create the entry in the table */ + + else + { + slot = cd->name_table; + for (i = 0; i < cd->names_found; i++) + { + int crc = memcmp(name, slot+2, namelen); + if (crc == 0) + { + if (slot[2+namelen] == 0) + { + if ((options & PCRE_DUPNAMES) == 0) + { + *errorcodeptr = ERR43; + goto FAILED; + } + } + else crc = -1; /* Current name is substring */ + } + if (crc < 0) + { + memmove(slot + cd->name_entry_size, slot, + (cd->names_found - i) * cd->name_entry_size); + break; + } + slot += cd->name_entry_size; + } + + PUT2(slot, 0, cd->bracount + 1); + memcpy(slot + 2, name, namelen); + slot[2+namelen] = 0; + } + } + + /* In both cases, count the number of names we've encountered. */ + + ptr++; /* Move past > or ' */ + cd->names_found++; + goto NUMBERED_GROUP; + + + /* ------------------------------------------------------------ */ + case '&': /* Perl recursion/subroutine syntax */ + terminator = ')'; + is_recurse = TRUE; + /* Fall through */ + + /* We come here from the Python syntax above that handles both + references (?P=name) and recursion (?P>name), as well as falling + through from the Perl recursion syntax (?&name). */ + + NAMED_REF_OR_RECURSE: + name = ++ptr; + while ((cd->ctypes[*ptr] & ctype_word) != 0) ptr++; + namelen = ptr - name; + + /* In the pre-compile phase, do a syntax check and set a dummy + reference number. */ + + if (lengthptr != NULL) + { + if (*ptr != terminator) + { + *errorcodeptr = ERR42; + goto FAILED; + } + if (namelen > MAX_NAME_SIZE) + { + *errorcodeptr = ERR48; + goto FAILED; + } + recno = 0; + } + + /* In the real compile, seek the name in the table */ + + else + { + slot = cd->name_table; + for (i = 0; i < cd->names_found; i++) + { + if (strncmp((char *)name, (char *)slot+2, namelen) == 0) break; + slot += cd->name_entry_size; + } + + if (i < cd->names_found) /* Back reference */ + { + recno = GET2(slot, 0); + } + else if ((recno = /* Forward back reference */ + find_parens(ptr, cd->bracount, name, namelen, + (options & PCRE_EXTENDED) != 0)) <= 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + } + + /* In both phases, we can now go to the code than handles numerical + recursion or backreferences. */ + + if (is_recurse) goto HANDLE_RECURSION; + else goto HANDLE_REFERENCE; + + + /* ------------------------------------------------------------ */ + case 'R': /* Recursion */ + ptr++; /* Same as (?0) */ + /* Fall through */ + + + /* ------------------------------------------------------------ */ + case '-': case '+': + case '0': case '1': case '2': case '3': case '4': /* Recursion or */ + case '5': case '6': case '7': case '8': case '9': /* subroutine */ + { + const uschar *called; + + if ((refsign = *ptr) == '+') ptr++; + else if (refsign == '-') + { + if ((digitab[ptr[1]] & ctype_digit) == 0) + goto OTHER_CHAR_AFTER_QUERY; + ptr++; + } + + recno = 0; + while((digitab[*ptr] & ctype_digit) != 0) + recno = recno * 10 + *ptr++ - '0'; + + if (*ptr != ')') + { + *errorcodeptr = ERR29; + goto FAILED; + } + + if (refsign == '-') + { + if (recno == 0) + { + *errorcodeptr = ERR58; + goto FAILED; + } + recno = cd->bracount - recno + 1; + if (recno <= 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + } + else if (refsign == '+') + { + if (recno == 0) + { + *errorcodeptr = ERR58; + goto FAILED; + } + recno += cd->bracount; + } + + /* Come here from code above that handles a named recursion */ + + HANDLE_RECURSION: + + previous = code; + called = cd->start_code; + + /* When we are actually compiling, find the bracket that is being + referenced. Temporarily end the regex in case it doesn't exist before + this point. If we end up with a forward reference, first check that + the bracket does occur later so we can give the error (and position) + now. Then remember this forward reference in the workspace so it can + be filled in at the end. */ + + if (lengthptr == NULL) + { + *code = OP_END; + if (recno != 0) called = find_bracket(cd->start_code, utf8, recno); + + /* Forward reference */ + + if (called == NULL) + { + if (find_parens(ptr, cd->bracount, NULL, recno, + (options & PCRE_EXTENDED) != 0) < 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + called = cd->start_code + recno; + PUTINC(cd->hwm, 0, code + 2 + LINK_SIZE - cd->start_code); + } + + /* If not a forward reference, and the subpattern is still open, + this is a recursive call. We check to see if this is a left + recursion that could loop for ever, and diagnose that case. */ + + else if (GET(called, 1) == 0 && + could_be_empty(called, code, bcptr, utf8)) + { + *errorcodeptr = ERR40; + goto FAILED; + } + } + + /* Insert the recursion/subroutine item, automatically wrapped inside + "once" brackets. Set up a "previous group" length so that a + subsequent quantifier will work. */ + + *code = OP_ONCE; + PUT(code, 1, 2 + 2*LINK_SIZE); + code += 1 + LINK_SIZE; + + *code = OP_RECURSE; + PUT(code, 1, called - cd->start_code); + code += 1 + LINK_SIZE; + + *code = OP_KET; + PUT(code, 1, 2 + 2*LINK_SIZE); + code += 1 + LINK_SIZE; + + length_prevgroup = 3 + 3*LINK_SIZE; + } + + /* Can't determine a first byte now */ + + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + continue; + + + /* ------------------------------------------------------------ */ + default: /* Other characters: check option setting */ + OTHER_CHAR_AFTER_QUERY: + set = unset = 0; + optset = &set; + + while (*ptr != ')' && *ptr != ':') + { + switch (*ptr++) + { + case '-': optset = &unset; break; + + case 'J': /* Record that it changed in the external options */ + *optset |= PCRE_DUPNAMES; + cd->external_flags |= PCRE_JCHANGED; + break; + + case 'i': *optset |= PCRE_CASELESS; break; + case 'm': *optset |= PCRE_MULTILINE; break; + case 's': *optset |= PCRE_DOTALL; break; + case 'x': *optset |= PCRE_EXTENDED; break; + case 'U': *optset |= PCRE_UNGREEDY; break; + case 'X': *optset |= PCRE_EXTRA; break; + + default: *errorcodeptr = ERR12; + ptr--; /* Correct the offset */ + goto FAILED; + } + } + + /* Set up the changed option bits, but don't change anything yet. */ + + newoptions = (options | set) & (~unset); + + /* If the options ended with ')' this is not the start of a nested + group with option changes, so the options change at this level. If this + item is right at the start of the pattern, the options can be + abstracted and made external in the pre-compile phase, and ignored in + the compile phase. This can be helpful when matching -- for instance in + caseless checking of required bytes. + + If the code pointer is not (cd->start_code + 1 + LINK_SIZE), we are + definitely *not* at the start of the pattern because something has been + compiled. In the pre-compile phase, however, the code pointer can have + that value after the start, because it gets reset as code is discarded + during the pre-compile. However, this can happen only at top level - if + we are within parentheses, the starting BRA will still be present. At + any parenthesis level, the length value can be used to test if anything + has been compiled at that level. Thus, a test for both these conditions + is necessary to ensure we correctly detect the start of the pattern in + both phases. + + If we are not at the pattern start, compile code to change the ims + options if this setting actually changes any of them. We also pass the + new setting back so that it can be put at the start of any following + branches, and when this group ends (if we are in a group), a resetting + item can be compiled. */ + + if (*ptr == ')') + { + if (code == cd->start_code + 1 + LINK_SIZE && + (lengthptr == NULL || *lengthptr == 2 + 2*LINK_SIZE)) + { + cd->external_options = newoptions; + options = newoptions; + } + else + { + if ((options & PCRE_IMS) != (newoptions & PCRE_IMS)) + { + *code++ = OP_OPT; + *code++ = newoptions & PCRE_IMS; + } + + /* Change options at this level, and pass them back for use + in subsequent branches. Reset the greedy defaults and the case + value for firstbyte and reqbyte. */ + + *optionsptr = options = newoptions; + greedy_default = ((newoptions & PCRE_UNGREEDY) != 0); + greedy_non_default = greedy_default ^ 1; + req_caseopt = ((options & PCRE_CASELESS) != 0)? REQ_CASELESS : 0; + } + + previous = NULL; /* This item can't be repeated */ + continue; /* It is complete */ + } + + /* If the options ended with ':' we are heading into a nested group + with possible change of options. Such groups are non-capturing and are + not assertions of any kind. All we need to do is skip over the ':'; + the newoptions value is handled below. */ + + bravalue = OP_BRA; + ptr++; + } /* End of switch for character following (? */ + } /* End of (? handling */ + + /* Opening parenthesis not followed by '?'. If PCRE_NO_AUTO_CAPTURE is set, + all unadorned brackets become non-capturing and behave like (?:...) + brackets. */ + + else if ((options & PCRE_NO_AUTO_CAPTURE) != 0) + { + bravalue = OP_BRA; + } + + /* Else we have a capturing group. */ + + else + { + NUMBERED_GROUP: + cd->bracount += 1; + PUT2(code, 1+LINK_SIZE, cd->bracount); + skipbytes = 2; + } + + /* Process nested bracketed regex. Assertions may not be repeated, but + other kinds can be. All their opcodes are >= OP_ONCE. We copy code into a + non-register variable in order to be able to pass its address because some + compilers complain otherwise. Pass in a new setting for the ims options if + they have changed. */ + + previous = (bravalue >= OP_ONCE)? code : NULL; + *code = bravalue; + tempcode = code; + tempreqvary = cd->req_varyopt; /* Save value before bracket */ + length_prevgroup = 0; /* Initialize for pre-compile phase */ + + if (!compile_regex( + newoptions, /* The complete new option state */ + options & PCRE_IMS, /* The previous ims option state */ + &tempcode, /* Where to put code (updated) */ + &ptr, /* Input pointer (updated) */ + errorcodeptr, /* Where to put an error message */ + (bravalue == OP_ASSERTBACK || + bravalue == OP_ASSERTBACK_NOT), /* TRUE if back assert */ + reset_bracount, /* True if (?| group */ + skipbytes, /* Skip over bracket number */ + &subfirstbyte, /* For possible first char */ + &subreqbyte, /* For possible last char */ + bcptr, /* Current branch chain */ + cd, /* Tables block */ + (lengthptr == NULL)? NULL : /* Actual compile phase */ + &length_prevgroup /* Pre-compile phase */ + )) + goto FAILED; + + /* At the end of compiling, code is still pointing to the start of the + group, while tempcode has been updated to point past the end of the group + and any option resetting that may follow it. The pattern pointer (ptr) + is on the bracket. */ + + /* If this is a conditional bracket, check that there are no more than + two branches in the group, or just one if it's a DEFINE group. We do this + in the real compile phase, not in the pre-pass, where the whole group may + not be available. */ + + if (bravalue == OP_COND && lengthptr == NULL) + { + uschar *tc = code; + int condcount = 0; + + do { + condcount++; + tc += GET(tc,1); + } + while (*tc != OP_KET); + + /* A DEFINE group is never obeyed inline (the "condition" is always + false). It must have only one branch. */ + + if (code[LINK_SIZE+1] == OP_DEF) + { + if (condcount > 1) + { + *errorcodeptr = ERR54; + goto FAILED; + } + bravalue = OP_DEF; /* Just a flag to suppress char handling below */ + } + + /* A "normal" conditional group. If there is just one branch, we must not + make use of its firstbyte or reqbyte, because this is equivalent to an + empty second branch. */ + + else + { + if (condcount > 2) + { + *errorcodeptr = ERR27; + goto FAILED; + } + if (condcount == 1) subfirstbyte = subreqbyte = REQ_NONE; + } + } + + /* Error if hit end of pattern */ + + if (*ptr != ')') + { + *errorcodeptr = ERR14; + goto FAILED; + } + + /* In the pre-compile phase, update the length by the length of the group, + less the brackets at either end. Then reduce the compiled code to just a + set of non-capturing brackets so that it doesn't use much memory if it is + duplicated by a quantifier.*/ + + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < length_prevgroup - 2 - 2*LINK_SIZE) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += length_prevgroup - 2 - 2*LINK_SIZE; + *code++ = OP_BRA; + PUTINC(code, 0, 1 + LINK_SIZE); + *code++ = OP_KET; + PUTINC(code, 0, 1 + LINK_SIZE); + break; /* No need to waste time with special character handling */ + } + + /* Otherwise update the main code pointer to the end of the group. */ + + code = tempcode; + + /* For a DEFINE group, required and first character settings are not + relevant. */ + + if (bravalue == OP_DEF) break; + + /* Handle updating of the required and first characters for other types of + group. Update for normal brackets of all kinds, and conditions with two + branches (see code above). If the bracket is followed by a quantifier with + zero repeat, we have to back off. Hence the definition of zeroreqbyte and + zerofirstbyte outside the main loop so that they can be accessed for the + back off. */ + + zeroreqbyte = reqbyte; + zerofirstbyte = firstbyte; + groupsetfirstbyte = FALSE; + + if (bravalue >= OP_ONCE) + { + /* If we have not yet set a firstbyte in this branch, take it from the + subpattern, remembering that it was set here so that a repeat of more + than one can replicate it as reqbyte if necessary. If the subpattern has + no firstbyte, set "none" for the whole branch. In both cases, a zero + repeat forces firstbyte to "none". */ + + if (firstbyte == REQ_UNSET) + { + if (subfirstbyte >= 0) + { + firstbyte = subfirstbyte; + groupsetfirstbyte = TRUE; + } + else firstbyte = REQ_NONE; + zerofirstbyte = REQ_NONE; + } + + /* If firstbyte was previously set, convert the subpattern's firstbyte + into reqbyte if there wasn't one, using the vary flag that was in + existence beforehand. */ + + else if (subfirstbyte >= 0 && subreqbyte < 0) + subreqbyte = subfirstbyte | tempreqvary; + + /* If the subpattern set a required byte (or set a first byte that isn't + really the first byte - see above), set it. */ + + if (subreqbyte >= 0) reqbyte = subreqbyte; + } + + /* For a forward assertion, we take the reqbyte, if set. This can be + helpful if the pattern that follows the assertion doesn't set a different + char. For example, it's useful for /(?=abcde).+/. We can't set firstbyte + for an assertion, however because it leads to incorrect effect for patterns + such as /(?=a)a.+/ when the "real" "a" would then become a reqbyte instead + of a firstbyte. This is overcome by a scan at the end if there's no + firstbyte, looking for an asserted first char. */ + + else if (bravalue == OP_ASSERT && subreqbyte >= 0) reqbyte = subreqbyte; + break; /* End of processing '(' */ + + + /* ===================================================================*/ + /* Handle metasequences introduced by \. For ones like \d, the ESC_ values + are arranged to be the negation of the corresponding OP_values. For the + back references, the values are ESC_REF plus the reference number. Only + back references and those types that consume a character may be repeated. + We can test for values between ESC_b and ESC_Z for the latter; this may + have to change if any new ones are ever created. */ + + case '\\': + tempptr = ptr; + c = check_escape(&ptr, errorcodeptr, cd->bracount, options, FALSE); + if (*errorcodeptr != 0) goto FAILED; + + if (c < 0) + { + if (-c == ESC_Q) /* Handle start of quoted string */ + { + if (ptr[1] == '\\' && ptr[2] == 'E') ptr += 2; /* avoid empty string */ + else inescq = TRUE; + continue; + } + + if (-c == ESC_E) continue; /* Perl ignores an orphan \E */ + + /* For metasequences that actually match a character, we disable the + setting of a first character if it hasn't already been set. */ + + if (firstbyte == REQ_UNSET && -c > ESC_b && -c < ESC_Z) + firstbyte = REQ_NONE; + + /* Set values to reset to if this is followed by a zero repeat. */ + + zerofirstbyte = firstbyte; + zeroreqbyte = reqbyte; + + /* \k<name> or \k'name' is a back reference by name (Perl syntax). + We also support \k{name} (.NET syntax) */ + + if (-c == ESC_k && (ptr[1] == '<' || ptr[1] == '\'' || ptr[1] == '{')) + { + is_recurse = FALSE; + terminator = (*(++ptr) == '<')? '>' : (*ptr == '\'')? '\'' : '}'; + goto NAMED_REF_OR_RECURSE; + } + + /* Back references are handled specially; must disable firstbyte if + not set to cope with cases like (?=(\w+))\1: which would otherwise set + ':' later. */ + + if (-c >= ESC_REF) + { + recno = -c - ESC_REF; + + HANDLE_REFERENCE: /* Come here from named backref handling */ + if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE; + previous = code; + *code++ = OP_REF; + PUT2INC(code, 0, recno); + cd->backref_map |= (recno < 32)? (1 << recno) : 1; + if (recno > cd->top_backref) cd->top_backref = recno; + } + + /* So are Unicode property matches, if supported. */ + +#ifdef SUPPORT_UCP + else if (-c == ESC_P || -c == ESC_p) + { + BOOL negated; + int pdata; + int ptype = get_ucp(&ptr, &negated, &pdata, errorcodeptr); + if (ptype < 0) goto FAILED; + previous = code; + *code++ = ((-c == ESC_p) != negated)? OP_PROP : OP_NOTPROP; + *code++ = ptype; + *code++ = pdata; + } +#else + + /* If Unicode properties are not supported, \X, \P, and \p are not + allowed. */ + + else if (-c == ESC_X || -c == ESC_P || -c == ESC_p) + { + *errorcodeptr = ERR45; + goto FAILED; + } +#endif + + /* For the rest (including \X when Unicode properties are supported), we + can obtain the OP value by negating the escape value. */ + + else + { + previous = (-c > ESC_b && -c < ESC_Z)? code : NULL; + *code++ = -c; + } + continue; + } + + /* We have a data character whose value is in c. In UTF-8 mode it may have + a value > 127. We set its representation in the length/buffer, and then + handle it as a data character. */ + +#ifdef SUPPORT_UTF8 + if (utf8 && c > 127) + mclength = _pcre_ord2utf8(c, mcbuffer); + else +#endif + + { + mcbuffer[0] = c; + mclength = 1; + } + goto ONE_CHAR; + + + /* ===================================================================*/ + /* Handle a literal character. It is guaranteed not to be whitespace or # + when the extended flag is set. If we are in UTF-8 mode, it may be a + multi-byte literal character. */ + + default: + NORMAL_CHAR: + mclength = 1; + mcbuffer[0] = c; + +#ifdef SUPPORT_UTF8 + if (utf8 && c >= 0xc0) + { + while ((ptr[1] & 0xc0) == 0x80) + mcbuffer[mclength++] = *(++ptr); + } +#endif + + /* At this point we have the character's bytes in mcbuffer, and the length + in mclength. When not in UTF-8 mode, the length is always 1. */ + + ONE_CHAR: + previous = code; + *code++ = ((options & PCRE_CASELESS) != 0)? OP_CHARNC : OP_CHAR; + for (c = 0; c < mclength; c++) *code++ = mcbuffer[c]; + + /* Remember if \r or \n were seen */ + + if (mcbuffer[0] == '\r' || mcbuffer[0] == '\n') + cd->external_flags |= PCRE_HASCRORLF; + + /* Set the first and required bytes appropriately. If no previous first + byte, set it from this character, but revert to none on a zero repeat. + Otherwise, leave the firstbyte value alone, and don't change it on a zero + repeat. */ + + if (firstbyte == REQ_UNSET) + { + zerofirstbyte = REQ_NONE; + zeroreqbyte = reqbyte; + + /* If the character is more than one byte long, we can set firstbyte + only if it is not to be matched caselessly. */ + + if (mclength == 1 || req_caseopt == 0) + { + firstbyte = mcbuffer[0] | req_caseopt; + if (mclength != 1) reqbyte = code[-1] | cd->req_varyopt; + } + else firstbyte = reqbyte = REQ_NONE; + } + + /* firstbyte was previously set; we can set reqbyte only the length is + 1 or the matching is caseful. */ + + else + { + zerofirstbyte = firstbyte; + zeroreqbyte = reqbyte; + if (mclength == 1 || req_caseopt == 0) + reqbyte = code[-1] | req_caseopt | cd->req_varyopt; + } + + break; /* End of literal character handling */ + } + } /* end of big loop */ + + +/* Control never reaches here by falling through, only by a goto for all the +error states. Pass back the position in the pattern so that it can be displayed +to the user for diagnosing the error. */ + +FAILED: +*ptrptr = ptr; +return FALSE; +} + + + + +/************************************************* +* Compile sequence of alternatives * +*************************************************/ + +/* On entry, ptr is pointing past the bracket character, but on return it +points to the closing bracket, or vertical bar, or end of string. The code +variable is pointing at the byte into which the BRA operator has been stored. +If the ims options are changed at the start (for a (?ims: group) or during any +branch, we need to insert an OP_OPT item at the start of every following branch +to ensure they get set correctly at run time, and also pass the new options +into every subsequent branch compile. + +This function is used during the pre-compile phase when we are trying to find +out the amount of memory needed, as well as during the real compile phase. The +value of lengthptr distinguishes the two phases. + +Arguments: + options option bits, including any changes for this subpattern + oldims previous settings of ims option bits + codeptr -> the address of the current code pointer + ptrptr -> the address of the current pattern pointer + errorcodeptr -> pointer to error code variable + lookbehind TRUE if this is a lookbehind assertion + reset_bracount TRUE to reset the count for each branch + skipbytes skip this many bytes at start (for brackets and OP_COND) + firstbyteptr place to put the first required character, or a negative number + reqbyteptr place to put the last required character, or a negative number + bcptr pointer to the chain of currently open branches + cd points to the data block with tables pointers etc. + lengthptr NULL during the real compile phase + points to length accumulator during pre-compile phase + +Returns: TRUE on success +*/ + +static BOOL +compile_regex(int options, int oldims, uschar **codeptr, const uschar **ptrptr, + int *errorcodeptr, BOOL lookbehind, BOOL reset_bracount, int skipbytes, + int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr, compile_data *cd, + int *lengthptr) +{ +const uschar *ptr = *ptrptr; +uschar *code = *codeptr; +uschar *last_branch = code; +uschar *start_bracket = code; +uschar *reverse_count = NULL; +int firstbyte, reqbyte; +int branchfirstbyte, branchreqbyte; +int length; +int orig_bracount; +int max_bracount; +branch_chain bc; + +bc.outer = bcptr; +bc.current = code; + +firstbyte = reqbyte = REQ_UNSET; + +/* Accumulate the length for use in the pre-compile phase. Start with the +length of the BRA and KET and any extra bytes that are required at the +beginning. We accumulate in a local variable to save frequent testing of +lenthptr for NULL. We cannot do this by looking at the value of code at the +start and end of each alternative, because compiled items are discarded during +the pre-compile phase so that the work space is not exceeded. */ + +length = 2 + 2*LINK_SIZE + skipbytes; + +/* WARNING: If the above line is changed for any reason, you must also change +the code that abstracts option settings at the start of the pattern and makes +them global. It tests the value of length for (2 + 2*LINK_SIZE) in the +pre-compile phase to find out whether anything has yet been compiled or not. */ + +/* Offset is set zero to mark that this bracket is still open */ + +PUT(code, 1, 0); +code += 1 + LINK_SIZE + skipbytes; + +/* Loop for each alternative branch */ + +orig_bracount = max_bracount = cd->bracount; +for (;;) + { + /* For a (?| group, reset the capturing bracket count so that each branch + uses the same numbers. */ + + if (reset_bracount) cd->bracount = orig_bracount; + + /* Handle a change of ims options at the start of the branch */ + + if ((options & PCRE_IMS) != oldims) + { + *code++ = OP_OPT; + *code++ = options & PCRE_IMS; + length += 2; + } + + /* Set up dummy OP_REVERSE if lookbehind assertion */ + + if (lookbehind) + { + *code++ = OP_REVERSE; + reverse_count = code; + PUTINC(code, 0, 0); + length += 1 + LINK_SIZE; + } + + /* Now compile the branch; in the pre-compile phase its length gets added + into the length. */ + + if (!compile_branch(&options, &code, &ptr, errorcodeptr, &branchfirstbyte, + &branchreqbyte, &bc, cd, (lengthptr == NULL)? NULL : &length)) + { + *ptrptr = ptr; + return FALSE; + } + + /* Keep the highest bracket count in case (?| was used and some branch + has fewer than the rest. */ + + if (cd->bracount > max_bracount) max_bracount = cd->bracount; + + /* In the real compile phase, there is some post-processing to be done. */ + + if (lengthptr == NULL) + { + /* If this is the first branch, the firstbyte and reqbyte values for the + branch become the values for the regex. */ + + if (*last_branch != OP_ALT) + { + firstbyte = branchfirstbyte; + reqbyte = branchreqbyte; + } + + /* If this is not the first branch, the first char and reqbyte have to + match the values from all the previous branches, except that if the + previous value for reqbyte didn't have REQ_VARY set, it can still match, + and we set REQ_VARY for the regex. */ + + else + { + /* If we previously had a firstbyte, but it doesn't match the new branch, + we have to abandon the firstbyte for the regex, but if there was + previously no reqbyte, it takes on the value of the old firstbyte. */ + + if (firstbyte >= 0 && firstbyte != branchfirstbyte) + { + if (reqbyte < 0) reqbyte = firstbyte; + firstbyte = REQ_NONE; + } + + /* If we (now or from before) have no firstbyte, a firstbyte from the + branch becomes a reqbyte if there isn't a branch reqbyte. */ + + if (firstbyte < 0 && branchfirstbyte >= 0 && branchreqbyte < 0) + branchreqbyte = branchfirstbyte; + + /* Now ensure that the reqbytes match */ + + if ((reqbyte & ~REQ_VARY) != (branchreqbyte & ~REQ_VARY)) + reqbyte = REQ_NONE; + else reqbyte |= branchreqbyte; /* To "or" REQ_VARY */ + } + + /* If lookbehind, check that this branch matches a fixed-length string, and + put the length into the OP_REVERSE item. Temporarily mark the end of the + branch with OP_END. */ + + if (lookbehind) + { + int fixed_length; + *code = OP_END; + fixed_length = find_fixedlength(last_branch, options); + DPRINTF(("fixed length = %d\n", fixed_length)); + if (fixed_length < 0) + { + *errorcodeptr = (fixed_length == -2)? ERR36 : ERR25; + *ptrptr = ptr; + return FALSE; + } + PUT(reverse_count, 0, fixed_length); + } + } + + /* Reached end of expression, either ')' or end of pattern. In the real + compile phase, go back through the alternative branches and reverse the chain + of offsets, with the field in the BRA item now becoming an offset to the + first alternative. If there are no alternatives, it points to the end of the + group. The length in the terminating ket is always the length of the whole + bracketed item. If any of the ims options were changed inside the group, + compile a resetting op-code following, except at the very end of the pattern. + Return leaving the pointer at the terminating char. */ + + if (*ptr != '|') + { + if (lengthptr == NULL) + { + int branch_length = code - last_branch; + do + { + int prev_length = GET(last_branch, 1); + PUT(last_branch, 1, branch_length); + branch_length = prev_length; + last_branch -= branch_length; + } + while (branch_length > 0); + } + + /* Fill in the ket */ + + *code = OP_KET; + PUT(code, 1, code - start_bracket); + code += 1 + LINK_SIZE; + + /* Resetting option if needed */ + + if ((options & PCRE_IMS) != oldims && *ptr == ')') + { + *code++ = OP_OPT; + *code++ = oldims; + length += 2; + } + + /* Retain the highest bracket number, in case resetting was used. */ + + cd->bracount = max_bracount; + + /* Set values to pass back */ + + *codeptr = code; + *ptrptr = ptr; + *firstbyteptr = firstbyte; + *reqbyteptr = reqbyte; + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < length) + { + *errorcodeptr = ERR20; + return FALSE; + } + *lengthptr += length; + } + return TRUE; + } + + /* Another branch follows. In the pre-compile phase, we can move the code + pointer back to where it was for the start of the first branch. (That is, + pretend that each branch is the only one.) + + In the real compile phase, insert an ALT node. Its length field points back + to the previous branch while the bracket remains open. At the end the chain + is reversed. It's done like this so that the start of the bracket has a + zero offset until it is closed, making it possible to detect recursion. */ + + if (lengthptr != NULL) + { + code = *codeptr + 1 + LINK_SIZE + skipbytes; + length += 1 + LINK_SIZE; + } + else + { + *code = OP_ALT; + PUT(code, 1, code - last_branch); + bc.current = last_branch = code; + code += 1 + LINK_SIZE; + } + + ptr++; + } +/* Control never reaches here */ +} + + + + +/************************************************* +* Check for anchored expression * +*************************************************/ + +/* Try to find out if this is an anchored regular expression. Consider each +alternative branch. If they all start with OP_SOD or OP_CIRC, or with a bracket +all of whose alternatives start with OP_SOD or OP_CIRC (recurse ad lib), then +it's anchored. However, if this is a multiline pattern, then only OP_SOD +counts, since OP_CIRC can match in the middle. + +We can also consider a regex to be anchored if OP_SOM starts all its branches. +This is the code for \G, which means "match at start of match position, taking +into account the match offset". + +A branch is also implicitly anchored if it starts with .* and DOTALL is set, +because that will try the rest of the pattern at all possible matching points, +so there is no point trying again.... er .... + +.... except when the .* appears inside capturing parentheses, and there is a +subsequent back reference to those parentheses. We haven't enough information +to catch that case precisely. + +At first, the best we could do was to detect when .* was in capturing brackets +and the highest back reference was greater than or equal to that level. +However, by keeping a bitmap of the first 31 back references, we can catch some +of the more common cases more precisely. + +Arguments: + code points to start of expression (the bracket) + options points to the options setting + bracket_map a bitmap of which brackets we are inside while testing; this + handles up to substring 31; after that we just have to take + the less precise approach + backref_map the back reference bitmap + +Returns: TRUE or FALSE +*/ + +static BOOL +is_anchored(register const uschar *code, int *options, unsigned int bracket_map, + unsigned int backref_map) +{ +do { + const uschar *scode = first_significant_code(code + _pcre_OP_lengths[*code], + options, PCRE_MULTILINE, FALSE); + register int op = *scode; + + /* Non-capturing brackets */ + + if (op == OP_BRA) + { + if (!is_anchored(scode, options, bracket_map, backref_map)) return FALSE; + } + + /* Capturing brackets */ + + else if (op == OP_CBRA) + { + int n = GET2(scode, 1+LINK_SIZE); + int new_map = bracket_map | ((n < 32)? (1 << n) : 1); + if (!is_anchored(scode, options, new_map, backref_map)) return FALSE; + } + + /* Other brackets */ + + else if (op == OP_ASSERT || op == OP_ONCE || op == OP_COND) + { + if (!is_anchored(scode, options, bracket_map, backref_map)) return FALSE; + } + + /* .* is not anchored unless DOTALL is set and it isn't in brackets that + are or may be referenced. */ + + else if ((op == OP_TYPESTAR || op == OP_TYPEMINSTAR || + op == OP_TYPEPOSSTAR) && + (*options & PCRE_DOTALL) != 0) + { + if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0) return FALSE; + } + + /* Check for explicit anchoring */ + + else if (op != OP_SOD && op != OP_SOM && + ((*options & PCRE_MULTILINE) != 0 || op != OP_CIRC)) + return FALSE; + code += GET(code, 1); + } +while (*code == OP_ALT); /* Loop for each alternative */ +return TRUE; +} + + + +/************************************************* +* Check for starting with ^ or .* * +*************************************************/ + +/* This is called to find out if every branch starts with ^ or .* so that +"first char" processing can be done to speed things up in multiline +matching and for non-DOTALL patterns that start with .* (which must start at +the beginning or after \n). As in the case of is_anchored() (see above), we +have to take account of back references to capturing brackets that contain .* +because in that case we can't make the assumption. + +Arguments: + code points to start of expression (the bracket) + bracket_map a bitmap of which brackets we are inside while testing; this + handles up to substring 31; after that we just have to take + the less precise approach + backref_map the back reference bitmap + +Returns: TRUE or FALSE +*/ + +static BOOL +is_startline(const uschar *code, unsigned int bracket_map, + unsigned int backref_map) +{ +do { + const uschar *scode = first_significant_code(code + _pcre_OP_lengths[*code], + NULL, 0, FALSE); + register int op = *scode; + + /* Non-capturing brackets */ + + if (op == OP_BRA) + { + if (!is_startline(scode, bracket_map, backref_map)) return FALSE; + } + + /* Capturing brackets */ + + else if (op == OP_CBRA) + { + int n = GET2(scode, 1+LINK_SIZE); + int new_map = bracket_map | ((n < 32)? (1 << n) : 1); + if (!is_startline(scode, new_map, backref_map)) return FALSE; + } + + /* Other brackets */ + + else if (op == OP_ASSERT || op == OP_ONCE || op == OP_COND) + { if (!is_startline(scode, bracket_map, backref_map)) return FALSE; } + + /* .* means "start at start or after \n" if it isn't in brackets that + may be referenced. */ + + else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR || op == OP_TYPEPOSSTAR) + { + if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0) return FALSE; + } + + /* Check for explicit circumflex */ + + else if (op != OP_CIRC) return FALSE; + + /* Move on to the next alternative */ + + code += GET(code, 1); + } +while (*code == OP_ALT); /* Loop for each alternative */ +return TRUE; +} + + + +/************************************************* +* Check for asserted fixed first char * +*************************************************/ + +/* During compilation, the "first char" settings from forward assertions are +discarded, because they can cause conflicts with actual literals that follow. +However, if we end up without a first char setting for an unanchored pattern, +it is worth scanning the regex to see if there is an initial asserted first +char. If all branches start with the same asserted char, or with a bracket all +of whose alternatives start with the same asserted char (recurse ad lib), then +we return that char, otherwise -1. + +Arguments: + code points to start of expression (the bracket) + options pointer to the options (used to check casing changes) + inassert TRUE if in an assertion + +Returns: -1 or the fixed first char +*/ + +static int +find_firstassertedchar(const uschar *code, int *options, BOOL inassert) +{ +register int c = -1; +do { + int d; + const uschar *scode = + first_significant_code(code + 1+LINK_SIZE, options, PCRE_CASELESS, TRUE); + register int op = *scode; + + switch(op) + { + default: + return -1; + + case OP_BRA: + case OP_CBRA: + case OP_ASSERT: + case OP_ONCE: + case OP_COND: + if ((d = find_firstassertedchar(scode, options, op == OP_ASSERT)) < 0) + return -1; + if (c < 0) c = d; else if (c != d) return -1; + break; + + case OP_EXACT: /* Fall through */ + scode += 2; + + case OP_CHAR: + case OP_CHARNC: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + if (!inassert) return -1; + if (c < 0) + { + c = scode[1]; + if ((*options & PCRE_CASELESS) != 0) c |= REQ_CASELESS; + } + else if (c != scode[1]) return -1; + break; + } + + code += GET(code, 1); + } +while (*code == OP_ALT); +return c; +} + + + +/************************************************* +* Compile a Regular Expression * +*************************************************/ + +/* This function takes a string and returns a pointer to a block of store +holding a compiled version of the expression. The original API for this +function had no error code return variable; it is retained for backwards +compatibility. The new function is given a new name. + +Arguments: + pattern the regular expression + options various option bits + errorcodeptr pointer to error code variable (pcre_compile2() only) + can be NULL if you don't want a code value + errorptr pointer to pointer to error text + erroroffset ptr offset in pattern where error was detected + tables pointer to character tables or NULL + +Returns: pointer to compiled data block, or NULL on error, + with errorptr and erroroffset set +*/ + +PCRE_EXP_DEFN pcre * +pcre_compile(const char *pattern, int options, const char **errorptr, + int *erroroffset, const unsigned char *tables) +{ +return pcre_compile2(pattern, options, NULL, errorptr, erroroffset, tables); +} + + +PCRE_EXP_DEFN pcre * +pcre_compile2(const char *pattern, int options, int *errorcodeptr, + const char **errorptr, int *erroroffset, const unsigned char *tables) +{ +real_pcre *re; +int length = 1; /* For final END opcode */ +int firstbyte, reqbyte, newline; +int errorcode = 0; +int skipatstart = 0; +#ifdef SUPPORT_UTF8 +BOOL utf8; +#endif +size_t size; +uschar *code; +const uschar *codestart; +const uschar *ptr; +compile_data compile_block; +compile_data *cd = &compile_block; + +/* This space is used for "compiling" into during the first phase, when we are +computing the amount of memory that is needed. Compiled items are thrown away +as soon as possible, so that a fairly large buffer should be sufficient for +this purpose. The same space is used in the second phase for remembering where +to fill in forward references to subpatterns. */ + +uschar cworkspace[COMPILE_WORK_SIZE]; + + +/* Set this early so that early errors get offset 0. */ + +ptr = (const uschar *)pattern; + +/* We can't pass back an error message if errorptr is NULL; I guess the best we +can do is just return NULL, but we can set a code value if there is a code +pointer. */ + +if (errorptr == NULL) + { + if (errorcodeptr != NULL) *errorcodeptr = 99; + return NULL; + } + +*errorptr = NULL; +if (errorcodeptr != NULL) *errorcodeptr = ERR0; + +/* However, we can give a message for this error */ + +if (erroroffset == NULL) + { + errorcode = ERR16; + goto PCRE_EARLY_ERROR_RETURN2; + } + +*erroroffset = 0; + +/* Can't support UTF8 unless PCRE has been compiled to include the code. */ + +#ifdef SUPPORT_UTF8 +utf8 = (options & PCRE_UTF8) != 0; +if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0 && + (*erroroffset = _pcre_valid_utf8((uschar *)pattern, -1)) >= 0) + { + errorcode = ERR44; + goto PCRE_EARLY_ERROR_RETURN2; + } +#else +if ((options & PCRE_UTF8) != 0) + { + errorcode = ERR32; + goto PCRE_EARLY_ERROR_RETURN; + } +#endif + +if ((options & ~PUBLIC_OPTIONS) != 0) + { + errorcode = ERR17; + goto PCRE_EARLY_ERROR_RETURN; + } + +/* Set up pointers to the individual character tables */ + +if (tables == NULL) tables = _pcre_default_tables; +cd->lcc = tables + lcc_offset; +cd->fcc = tables + fcc_offset; +cd->cbits = tables + cbits_offset; +cd->ctypes = tables + ctypes_offset; + +/* Check for global one-time settings at the start of the pattern, and remember +the offset for later. */ + +while (ptr[skipatstart] == '(' && ptr[skipatstart+1] == '*') + { + int newnl = 0; + int newbsr = 0; + + if (strncmp((char *)(ptr+skipatstart+2), "CR)", 3) == 0) + { skipatstart += 5; newnl = PCRE_NEWLINE_CR; } + else if (strncmp((char *)(ptr+skipatstart+2), "LF)", 3) == 0) + { skipatstart += 5; newnl = PCRE_NEWLINE_LF; } + else if (strncmp((char *)(ptr+skipatstart+2), "CRLF)", 5) == 0) + { skipatstart += 7; newnl = PCRE_NEWLINE_CR + PCRE_NEWLINE_LF; } + else if (strncmp((char *)(ptr+skipatstart+2), "ANY)", 4) == 0) + { skipatstart += 6; newnl = PCRE_NEWLINE_ANY; } + else if (strncmp((char *)(ptr+skipatstart+2), "ANYCRLF)", 8) == 0) + { skipatstart += 10; newnl = PCRE_NEWLINE_ANYCRLF; } + + else if (strncmp((char *)(ptr+skipatstart+2), "BSR_ANYCRLF)", 12) == 0) + { skipatstart += 14; newbsr = PCRE_BSR_ANYCRLF; } + else if (strncmp((char *)(ptr+skipatstart+2), "BSR_UNICODE)", 12) == 0) + { skipatstart += 14; newbsr = PCRE_BSR_UNICODE; } + + if (newnl != 0) + options = (options & ~PCRE_NEWLINE_BITS) | newnl; + else if (newbsr != 0) + options = (options & ~(PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) | newbsr; + else break; + } + +/* Check validity of \R options. */ + +switch (options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) + { + case 0: + case PCRE_BSR_ANYCRLF: + case PCRE_BSR_UNICODE: + break; + default: errorcode = ERR56; goto PCRE_EARLY_ERROR_RETURN; + } + +/* Handle different types of newline. The three bits give seven cases. The +current code allows for fixed one- or two-byte sequences, plus "any" and +"anycrlf". */ + +switch (options & PCRE_NEWLINE_BITS) + { + case 0: newline = NEWLINE; break; /* Build-time default */ + case PCRE_NEWLINE_CR: newline = '\r'; break; + case PCRE_NEWLINE_LF: newline = '\n'; break; + case PCRE_NEWLINE_CR+ + PCRE_NEWLINE_LF: newline = ('\r' << 8) | '\n'; break; + case PCRE_NEWLINE_ANY: newline = -1; break; + case PCRE_NEWLINE_ANYCRLF: newline = -2; break; + default: errorcode = ERR56; goto PCRE_EARLY_ERROR_RETURN; + } + +if (newline == -2) + { + cd->nltype = NLTYPE_ANYCRLF; + } +else if (newline < 0) + { + cd->nltype = NLTYPE_ANY; + } +else + { + cd->nltype = NLTYPE_FIXED; + if (newline > 255) + { + cd->nllen = 2; + cd->nl[0] = (newline >> 8) & 255; + cd->nl[1] = newline & 255; + } + else + { + cd->nllen = 1; + cd->nl[0] = newline; + } + } + +/* Maximum back reference and backref bitmap. The bitmap records up to 31 back +references to help in deciding whether (.*) can be treated as anchored or not. +*/ + +cd->top_backref = 0; +cd->backref_map = 0; + +/* Reflect pattern for debugging output */ + +DPRINTF(("------------------------------------------------------------------\n")); +DPRINTF(("%s\n", pattern)); + +/* Pretend to compile the pattern while actually just accumulating the length +of memory required. This behaviour is triggered by passing a non-NULL final +argument to compile_regex(). We pass a block of workspace (cworkspace) for it +to compile parts of the pattern into; the compiled code is discarded when it is +no longer needed, so hopefully this workspace will never overflow, though there +is a test for its doing so. */ + +cd->bracount = 0; +cd->names_found = 0; +cd->name_entry_size = 0; +cd->name_table = NULL; +cd->start_workspace = cworkspace; +cd->start_code = cworkspace; +cd->hwm = cworkspace; +cd->start_pattern = (const uschar *)pattern; +cd->end_pattern = (const uschar *)(pattern + strlen(pattern)); +cd->req_varyopt = 0; +cd->external_options = options; +cd->external_flags = 0; + +/* Now do the pre-compile. On error, errorcode will be set non-zero, so we +don't need to look at the result of the function here. The initial options have +been put into the cd block so that they can be changed if an option setting is +found within the regex right at the beginning. Bringing initial option settings +outside can help speed up starting point checks. */ + +ptr += skipatstart; +code = cworkspace; +*code = OP_BRA; +(void)compile_regex(cd->external_options, cd->external_options & PCRE_IMS, + &code, &ptr, &errorcode, FALSE, FALSE, 0, &firstbyte, &reqbyte, NULL, cd, + &length); +if (errorcode != 0) goto PCRE_EARLY_ERROR_RETURN; + +DPRINTF(("end pre-compile: length=%d workspace=%d\n", length, + cd->hwm - cworkspace)); + +if (length > MAX_PATTERN_SIZE) + { + errorcode = ERR20; + goto PCRE_EARLY_ERROR_RETURN; + } + +/* Compute the size of data block needed and get it, either from malloc or +externally provided function. Integer overflow should no longer be possible +because nowadays we limit the maximum value of cd->names_found and +cd->name_entry_size. */ + +size = length + sizeof(real_pcre) + cd->names_found * (cd->name_entry_size + 3); +re = (real_pcre *)(pcre_malloc)(size); + +if (re == NULL) + { + errorcode = ERR21; + goto PCRE_EARLY_ERROR_RETURN; + } + +/* Put in the magic number, and save the sizes, initial options, internal +flags, and character table pointer. NULL is used for the default character +tables. The nullpad field is at the end; it's there to help in the case when a +regex compiled on a system with 4-byte pointers is run on another with 8-byte +pointers. */ + +re->magic_number = MAGIC_NUMBER; +re->size = size; +re->options = cd->external_options; +re->flags = cd->external_flags; +re->dummy1 = 0; +re->first_byte = 0; +re->req_byte = 0; +re->name_table_offset = sizeof(real_pcre); +re->name_entry_size = cd->name_entry_size; +re->name_count = cd->names_found; +re->ref_count = 0; +re->tables = (tables == _pcre_default_tables)? NULL : tables; +re->nullpad = NULL; + +/* The starting points of the name/number translation table and of the code are +passed around in the compile data block. The start/end pattern and initial +options are already set from the pre-compile phase, as is the name_entry_size +field. Reset the bracket count and the names_found field. Also reset the hwm +field; this time it's used for remembering forward references to subpatterns. +*/ + +cd->bracount = 0; +cd->names_found = 0; +cd->name_table = (uschar *)re + re->name_table_offset; +codestart = cd->name_table + re->name_entry_size * re->name_count; +cd->start_code = codestart; +cd->hwm = cworkspace; +cd->req_varyopt = 0; +cd->had_accept = FALSE; + +/* Set up a starting, non-extracting bracket, then compile the expression. On +error, errorcode will be set non-zero, so we don't need to look at the result +of the function here. */ + +ptr = (const uschar *)pattern + skipatstart; +code = (uschar *)codestart; +*code = OP_BRA; +(void)compile_regex(re->options, re->options & PCRE_IMS, &code, &ptr, + &errorcode, FALSE, FALSE, 0, &firstbyte, &reqbyte, NULL, cd, NULL); +re->top_bracket = cd->bracount; +re->top_backref = cd->top_backref; +re->flags = cd->external_flags; + +if (cd->had_accept) reqbyte = -1; /* Must disable after (*ACCEPT) */ + +/* If not reached end of pattern on success, there's an excess bracket. */ + +if (errorcode == 0 && *ptr != 0) errorcode = ERR22; + +/* Fill in the terminating state and check for disastrous overflow, but +if debugging, leave the test till after things are printed out. */ + +*code++ = OP_END; + +#ifndef DEBUG +if (code - codestart > length) errorcode = ERR23; +#endif + +/* Fill in any forward references that are required. */ + +while (errorcode == 0 && cd->hwm > cworkspace) + { + int offset, recno; + const uschar *groupptr; + cd->hwm -= LINK_SIZE; + offset = GET(cd->hwm, 0); + recno = GET(codestart, offset); + groupptr = find_bracket(codestart, (re->options & PCRE_UTF8) != 0, recno); + if (groupptr == NULL) errorcode = ERR53; + else PUT(((uschar *)codestart), offset, groupptr - codestart); + } + +/* Give an error if there's back reference to a non-existent capturing +subpattern. */ + +if (errorcode == 0 && re->top_backref > re->top_bracket) errorcode = ERR15; + +/* Failed to compile, or error while post-processing */ + +if (errorcode != 0) + { + (pcre_free)(re); + PCRE_EARLY_ERROR_RETURN: + *erroroffset = ptr - (const uschar *)pattern; + PCRE_EARLY_ERROR_RETURN2: + *errorptr = find_error_text(errorcode); + if (errorcodeptr != NULL) *errorcodeptr = errorcode; + return NULL; + } + +/* If the anchored option was not passed, set the flag if we can determine that +the pattern is anchored by virtue of ^ characters or \A or anything else (such +as starting with .* when DOTALL is set). + +Otherwise, if we know what the first byte has to be, save it, because that +speeds up unanchored matches no end. If not, see if we can set the +PCRE_STARTLINE flag. This is helpful for multiline matches when all branches +start with ^. and also when all branches start with .* for non-DOTALL matches. +*/ + +if ((re->options & PCRE_ANCHORED) == 0) + { + int temp_options = re->options; /* May get changed during these scans */ + if (is_anchored(codestart, &temp_options, 0, cd->backref_map)) + re->options |= PCRE_ANCHORED; + else + { + if (firstbyte < 0) + firstbyte = find_firstassertedchar(codestart, &temp_options, FALSE); + if (firstbyte >= 0) /* Remove caseless flag for non-caseable chars */ + { + int ch = firstbyte & 255; + re->first_byte = ((firstbyte & REQ_CASELESS) != 0 && + cd->fcc[ch] == ch)? ch : firstbyte; + re->flags |= PCRE_FIRSTSET; + } + else if (is_startline(codestart, 0, cd->backref_map)) + re->flags |= PCRE_STARTLINE; + } + } + +/* For an anchored pattern, we use the "required byte" only if it follows a +variable length item in the regex. Remove the caseless flag for non-caseable +bytes. */ + +if (reqbyte >= 0 && + ((re->options & PCRE_ANCHORED) == 0 || (reqbyte & REQ_VARY) != 0)) + { + int ch = reqbyte & 255; + re->req_byte = ((reqbyte & REQ_CASELESS) != 0 && + cd->fcc[ch] == ch)? (reqbyte & ~REQ_CASELESS) : reqbyte; + re->flags |= PCRE_REQCHSET; + } + +/* Print out the compiled data if debugging is enabled. This is never the +case when building a production library. */ + +#ifdef DEBUG + +printf("Length = %d top_bracket = %d top_backref = %d\n", + length, re->top_bracket, re->top_backref); + +printf("Options=%08x\n", re->options); + +if ((re->flags & PCRE_FIRSTSET) != 0) + { + int ch = re->first_byte & 255; + const char *caseless = ((re->first_byte & REQ_CASELESS) == 0)? + "" : " (caseless)"; + if (isprint(ch)) printf("First char = %c%s\n", ch, caseless); + else printf("First char = \\x%02x%s\n", ch, caseless); + } + +if ((re->flags & PCRE_REQCHSET) != 0) + { + int ch = re->req_byte & 255; + const char *caseless = ((re->req_byte & REQ_CASELESS) == 0)? + "" : " (caseless)"; + if (isprint(ch)) printf("Req char = %c%s\n", ch, caseless); + else printf("Req char = \\x%02x%s\n", ch, caseless); + } + +pcre_printint(re, stdout, TRUE); + +/* This check is done here in the debugging case so that the code that +was compiled can be seen. */ + +if (code - codestart > length) + { + (pcre_free)(re); + *errorptr = find_error_text(ERR23); + *erroroffset = ptr - (uschar *)pattern; + if (errorcodeptr != NULL) *errorcodeptr = ERR23; + return NULL; + } +#endif /* DEBUG */ + +return (pcre *)re; +} + +/* End of pcre_compile.c */ diff --git a/pcre-7.4/pcre_config.c b/pcre-7.4/pcre_config.c new file mode 100644 index 0000000..220ef93 --- /dev/null +++ b/pcre-7.4/pcre_config.c @@ -0,0 +1,128 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_config(). */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Return info about what features are configured * +*************************************************/ + +/* This function has an extensible interface so that additional items can be +added compatibly. + +Arguments: + what what information is required + where where to put the information + +Returns: 0 if data returned, negative on error +*/ + +PCRE_EXP_DEFN int +pcre_config(int what, void *where) +{ +switch (what) + { + case PCRE_CONFIG_UTF8: +#ifdef SUPPORT_UTF8 + *((int *)where) = 1; +#else + *((int *)where) = 0; +#endif + break; + + case PCRE_CONFIG_UNICODE_PROPERTIES: +#ifdef SUPPORT_UCP + *((int *)where) = 1; +#else + *((int *)where) = 0; +#endif + break; + + case PCRE_CONFIG_NEWLINE: + *((int *)where) = NEWLINE; + break; + + case PCRE_CONFIG_BSR: +#ifdef BSR_ANYCRLF + *((int *)where) = 1; +#else + *((int *)where) = 0; +#endif + break; + + case PCRE_CONFIG_LINK_SIZE: + *((int *)where) = LINK_SIZE; + break; + + case PCRE_CONFIG_POSIX_MALLOC_THRESHOLD: + *((int *)where) = POSIX_MALLOC_THRESHOLD; + break; + + case PCRE_CONFIG_MATCH_LIMIT: + *((unsigned int *)where) = MATCH_LIMIT; + break; + + case PCRE_CONFIG_MATCH_LIMIT_RECURSION: + *((unsigned int *)where) = MATCH_LIMIT_RECURSION; + break; + + case PCRE_CONFIG_STACKRECURSE: +#ifdef NO_RECURSE + *((int *)where) = 0; +#else + *((int *)where) = 1; +#endif + break; + + default: return PCRE_ERROR_BADOPTION; + } + +return 0; +} + +/* End of pcre_config.c */ diff --git a/pcre-7.4/pcre_dfa_exec.c b/pcre-7.4/pcre_dfa_exec.c new file mode 100644 index 0000000..e590fbb --- /dev/null +++ b/pcre-7.4/pcre_dfa_exec.c @@ -0,0 +1,2896 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_dfa_exec(), which is an +alternative matching function that uses a sort of DFA algorithm (not a true +FSM). This is NOT Perl- compatible, but it has advantages in certain +applications. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK md /* Block containing newline information */ +#define PSSTART start_subject /* Field containing processed string start */ +#define PSEND end_subject /* Field containing processed string end */ + +#include "pcre_internal.h" + + +/* For use to indent debugging output */ + +#define SP " " + + + +/************************************************* +* Code parameters and static tables * +*************************************************/ + +/* These are offsets that are used to turn the OP_TYPESTAR and friends opcodes +into others, under special conditions. A gap of 20 between the blocks should be +enough. The resulting opcodes don't have to be less than 256 because they are +never stored, so we push them well clear of the normal opcodes. */ + +#define OP_PROP_EXTRA 300 +#define OP_EXTUNI_EXTRA 320 +#define OP_ANYNL_EXTRA 340 +#define OP_HSPACE_EXTRA 360 +#define OP_VSPACE_EXTRA 380 + + +/* This table identifies those opcodes that are followed immediately by a +character that is to be tested in some way. This makes is possible to +centralize the loading of these characters. In the case of Type * etc, the +"character" is the opcode for \D, \d, \S, \s, \W, or \w, which will always be a +small value. ***NOTE*** If the start of this table is modified, the two tables +that follow must also be modified. */ + +static uschar coptable[] = { + 0, /* End */ + 0, 0, 0, 0, 0, /* \A, \G, \K, \B, \b */ + 0, 0, 0, 0, 0, 0, /* \D, \d, \S, \s, \W, \w */ + 0, 0, /* Any, Anybyte */ + 0, 0, 0, /* NOTPROP, PROP, EXTUNI */ + 0, 0, 0, 0, 0, /* \R, \H, \h, \V, \v */ + 0, 0, 0, 0, 0, /* \Z, \z, Opt, ^, $ */ + 1, /* Char */ + 1, /* Charnc */ + 1, /* not */ + /* Positive single-char repeats */ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ + 3, 3, 3, /* upto, minupto, exact */ + 1, 1, 1, 3, /* *+, ++, ?+, upto+ */ + /* Negative single-char repeats - only for chars < 256 */ + 1, 1, 1, 1, 1, 1, /* NOT *, *?, +, +?, ?, ?? */ + 3, 3, 3, /* NOT upto, minupto, exact */ + 1, 1, 1, 3, /* NOT *+, ++, ?+, updo+ */ + /* Positive type repeats */ + 1, 1, 1, 1, 1, 1, /* Type *, *?, +, +?, ?, ?? */ + 3, 3, 3, /* Type upto, minupto, exact */ + 1, 1, 1, 3, /* Type *+, ++, ?+, upto+ */ + /* Character class & ref repeats */ + 0, 0, 0, 0, 0, 0, /* *, *?, +, +?, ?, ?? */ + 0, 0, /* CRRANGE, CRMINRANGE */ + 0, /* CLASS */ + 0, /* NCLASS */ + 0, /* XCLASS - variable length */ + 0, /* REF */ + 0, /* RECURSE */ + 0, /* CALLOUT */ + 0, /* Alt */ + 0, /* Ket */ + 0, /* KetRmax */ + 0, /* KetRmin */ + 0, /* Assert */ + 0, /* Assert not */ + 0, /* Assert behind */ + 0, /* Assert behind not */ + 0, /* Reverse */ + 0, 0, 0, 0, /* ONCE, BRA, CBRA, COND */ + 0, 0, 0, /* SBRA, SCBRA, SCOND */ + 0, /* CREF */ + 0, /* RREF */ + 0, /* DEF */ + 0, 0, /* BRAZERO, BRAMINZERO */ + 0, 0, 0, 0, /* PRUNE, SKIP, THEN, COMMIT */ + 0, 0 /* FAIL, ACCEPT */ +}; + +/* These 2 tables allow for compact code for testing for \D, \d, \S, \s, \W, +and \w */ + +static uschar toptable1[] = { + 0, 0, 0, 0, 0, 0, + ctype_digit, ctype_digit, + ctype_space, ctype_space, + ctype_word, ctype_word, + 0 /* OP_ANY */ +}; + +static uschar toptable2[] = { + 0, 0, 0, 0, 0, 0, + ctype_digit, 0, + ctype_space, 0, + ctype_word, 0, + 1 /* OP_ANY */ +}; + + +/* Structure for holding data about a particular state, which is in effect the +current data for an active path through the match tree. It must consist +entirely of ints because the working vector we are passed, and which we put +these structures in, is a vector of ints. */ + +typedef struct stateblock { + int offset; /* Offset to opcode */ + int count; /* Count for repeats */ + int ims; /* ims flag bits */ + int data; /* Some use extra data */ +} stateblock; + +#define INTS_PER_STATEBLOCK (sizeof(stateblock)/sizeof(int)) + + +#ifdef DEBUG +/************************************************* +* Print character string * +*************************************************/ + +/* Character string printing function for debugging. + +Arguments: + p points to string + length number of bytes + f where to print + +Returns: nothing +*/ + +static void +pchars(unsigned char *p, int length, FILE *f) +{ +int c; +while (length-- > 0) + { + if (isprint(c = *(p++))) + fprintf(f, "%c", c); + else + fprintf(f, "\\x%02x", c); + } +} +#endif + + + +/************************************************* +* Execute a Regular Expression - DFA engine * +*************************************************/ + +/* This internal function applies a compiled pattern to a subject string, +starting at a given point, using a DFA engine. This function is called from the +external one, possibly multiple times if the pattern is not anchored. The +function calls itself recursively for some kinds of subpattern. + +Arguments: + md the match_data block with fixed information + this_start_code the opening bracket of this subexpression's code + current_subject where we currently are in the subject string + start_offset start offset in the subject string + offsets vector to contain the matching string offsets + offsetcount size of same + workspace vector of workspace + wscount size of same + ims the current ims flags + rlevel function call recursion level + recursing regex recursive call level + +Returns: > 0 => + = 0 => + -1 => failed to match + < -1 => some kind of unexpected problem + +The following macros are used for adding states to the two state vectors (one +for the current character, one for the following character). */ + +#define ADD_ACTIVE(x,y) \ + if (active_count++ < wscount) \ + { \ + next_active_state->offset = (x); \ + next_active_state->count = (y); \ + next_active_state->ims = ims; \ + next_active_state++; \ + DPRINTF(("%.*sADD_ACTIVE(%d,%d)\n", rlevel*2-2, SP, (x), (y))); \ + } \ + else return PCRE_ERROR_DFA_WSSIZE + +#define ADD_ACTIVE_DATA(x,y,z) \ + if (active_count++ < wscount) \ + { \ + next_active_state->offset = (x); \ + next_active_state->count = (y); \ + next_active_state->ims = ims; \ + next_active_state->data = (z); \ + next_active_state++; \ + DPRINTF(("%.*sADD_ACTIVE_DATA(%d,%d,%d)\n", rlevel*2-2, SP, (x), (y), (z))); \ + } \ + else return PCRE_ERROR_DFA_WSSIZE + +#define ADD_NEW(x,y) \ + if (new_count++ < wscount) \ + { \ + next_new_state->offset = (x); \ + next_new_state->count = (y); \ + next_new_state->ims = ims; \ + next_new_state++; \ + DPRINTF(("%.*sADD_NEW(%d,%d)\n", rlevel*2-2, SP, (x), (y))); \ + } \ + else return PCRE_ERROR_DFA_WSSIZE + +#define ADD_NEW_DATA(x,y,z) \ + if (new_count++ < wscount) \ + { \ + next_new_state->offset = (x); \ + next_new_state->count = (y); \ + next_new_state->ims = ims; \ + next_new_state->data = (z); \ + next_new_state++; \ + DPRINTF(("%.*sADD_NEW_DATA(%d,%d,%d)\n", rlevel*2-2, SP, (x), (y), (z))); \ + } \ + else return PCRE_ERROR_DFA_WSSIZE + +/* And now, here is the code */ + +static int +internal_dfa_exec( + dfa_match_data *md, + const uschar *this_start_code, + const uschar *current_subject, + int start_offset, + int *offsets, + int offsetcount, + int *workspace, + int wscount, + int ims, + int rlevel, + int recursing) +{ +stateblock *active_states, *new_states, *temp_states; +stateblock *next_active_state, *next_new_state; + +const uschar *ctypes, *lcc, *fcc; +const uschar *ptr; +const uschar *end_code, *first_op; + +int active_count, new_count, match_count; + +/* Some fields in the md block are frequently referenced, so we load them into +independent variables in the hope that this will perform better. */ + +const uschar *start_subject = md->start_subject; +const uschar *end_subject = md->end_subject; +const uschar *start_code = md->start_code; + +#ifdef SUPPORT_UTF8 +BOOL utf8 = (md->poptions & PCRE_UTF8) != 0; +#else +BOOL utf8 = FALSE; +#endif + +rlevel++; +offsetcount &= (-2); + +wscount -= 2; +wscount = (wscount - (wscount % (INTS_PER_STATEBLOCK * 2))) / + (2 * INTS_PER_STATEBLOCK); + +DPRINTF(("\n%.*s---------------------\n" + "%.*sCall to internal_dfa_exec f=%d r=%d\n", + rlevel*2-2, SP, rlevel*2-2, SP, rlevel, recursing)); + +ctypes = md->tables + ctypes_offset; +lcc = md->tables + lcc_offset; +fcc = md->tables + fcc_offset; + +match_count = PCRE_ERROR_NOMATCH; /* A negative number */ + +active_states = (stateblock *)(workspace + 2); +next_new_state = new_states = active_states + wscount; +new_count = 0; + +first_op = this_start_code + 1 + LINK_SIZE + + ((*this_start_code == OP_CBRA || *this_start_code == OP_SCBRA)? 2:0); + +/* The first thing in any (sub) pattern is a bracket of some sort. Push all +the alternative states onto the list, and find out where the end is. This +makes is possible to use this function recursively, when we want to stop at a +matching internal ket rather than at the end. + +If the first opcode in the first alternative is OP_REVERSE, we are dealing with +a backward assertion. In that case, we have to find out the maximum amount to +move back, and set up each alternative appropriately. */ + +if (*first_op == OP_REVERSE) + { + int max_back = 0; + int gone_back; + + end_code = this_start_code; + do + { + int back = GET(end_code, 2+LINK_SIZE); + if (back > max_back) max_back = back; + end_code += GET(end_code, 1); + } + while (*end_code == OP_ALT); + + /* If we can't go back the amount required for the longest lookbehind + pattern, go back as far as we can; some alternatives may still be viable. */ + +#ifdef SUPPORT_UTF8 + /* In character mode we have to step back character by character */ + + if (utf8) + { + for (gone_back = 0; gone_back < max_back; gone_back++) + { + if (current_subject <= start_subject) break; + current_subject--; + while (current_subject > start_subject && + (*current_subject & 0xc0) == 0x80) + current_subject--; + } + } + else +#endif + + /* In byte-mode we can do this quickly. */ + + { + gone_back = (current_subject - max_back < start_subject)? + current_subject - start_subject : max_back; + current_subject -= gone_back; + } + + /* Now we can process the individual branches. */ + + end_code = this_start_code; + do + { + int back = GET(end_code, 2+LINK_SIZE); + if (back <= gone_back) + { + int bstate = end_code - start_code + 2 + 2*LINK_SIZE; + ADD_NEW_DATA(-bstate, 0, gone_back - back); + } + end_code += GET(end_code, 1); + } + while (*end_code == OP_ALT); + } + +/* This is the code for a "normal" subpattern (not a backward assertion). The +start of a whole pattern is always one of these. If we are at the top level, +we may be asked to restart matching from the same point that we reached for a +previous partial match. We still have to scan through the top-level branches to +find the end state. */ + +else + { + end_code = this_start_code; + + /* Restarting */ + + if (rlevel == 1 && (md->moptions & PCRE_DFA_RESTART) != 0) + { + do { end_code += GET(end_code, 1); } while (*end_code == OP_ALT); + new_count = workspace[1]; + if (!workspace[0]) + memcpy(new_states, active_states, new_count * sizeof(stateblock)); + } + + /* Not restarting */ + + else + { + int length = 1 + LINK_SIZE + + ((*this_start_code == OP_CBRA || *this_start_code == OP_SCBRA)? 2:0); + do + { + ADD_NEW(end_code - start_code + length, 0); + end_code += GET(end_code, 1); + length = 1 + LINK_SIZE; + } + while (*end_code == OP_ALT); + } + } + +workspace[0] = 0; /* Bit indicating which vector is current */ + +DPRINTF(("%.*sEnd state = %d\n", rlevel*2-2, SP, end_code - start_code)); + +/* Loop for scanning the subject */ + +ptr = current_subject; +for (;;) + { + int i, j; + int clen, dlen; + unsigned int c, d; + + /* Make the new state list into the active state list and empty the + new state list. */ + + temp_states = active_states; + active_states = new_states; + new_states = temp_states; + active_count = new_count; + new_count = 0; + + workspace[0] ^= 1; /* Remember for the restarting feature */ + workspace[1] = active_count; + +#ifdef DEBUG + printf("%.*sNext character: rest of subject = \"", rlevel*2-2, SP); + pchars((uschar *)ptr, strlen((char *)ptr), stdout); + printf("\"\n"); + + printf("%.*sActive states: ", rlevel*2-2, SP); + for (i = 0; i < active_count; i++) + printf("%d/%d ", active_states[i].offset, active_states[i].count); + printf("\n"); +#endif + + /* Set the pointers for adding new states */ + + next_active_state = active_states + active_count; + next_new_state = new_states; + + /* Load the current character from the subject outside the loop, as many + different states may want to look at it, and we assume that at least one + will. */ + + if (ptr < end_subject) + { + clen = 1; /* Number of bytes in the character */ +#ifdef SUPPORT_UTF8 + if (utf8) { GETCHARLEN(c, ptr, clen); } else +#endif /* SUPPORT_UTF8 */ + c = *ptr; + } + else + { + clen = 0; /* This indicates the end of the subject */ + c = NOTACHAR; /* This value should never actually be used */ + } + + /* Scan up the active states and act on each one. The result of an action + may be to add more states to the currently active list (e.g. on hitting a + parenthesis) or it may be to put states on the new list, for considering + when we move the character pointer on. */ + + for (i = 0; i < active_count; i++) + { + stateblock *current_state = active_states + i; + const uschar *code; + int state_offset = current_state->offset; + int count, codevalue; +#ifdef SUPPORT_UCP + int chartype, script; +#endif + +#ifdef DEBUG + printf ("%.*sProcessing state %d c=", rlevel*2-2, SP, state_offset); + if (clen == 0) printf("EOL\n"); + else if (c > 32 && c < 127) printf("'%c'\n", c); + else printf("0x%02x\n", c); +#endif + + /* This variable is referred to implicity in the ADD_xxx macros. */ + + ims = current_state->ims; + + /* A negative offset is a special case meaning "hold off going to this + (negated) state until the number of characters in the data field have + been skipped". */ + + if (state_offset < 0) + { + if (current_state->data > 0) + { + DPRINTF(("%.*sSkipping this character\n", rlevel*2-2, SP)); + ADD_NEW_DATA(state_offset, current_state->count, + current_state->data - 1); + continue; + } + else + { + current_state->offset = state_offset = -state_offset; + } + } + + /* Check for a duplicate state with the same count, and skip if found. */ + + for (j = 0; j < i; j++) + { + if (active_states[j].offset == state_offset && + active_states[j].count == current_state->count) + { + DPRINTF(("%.*sDuplicate state: skipped\n", rlevel*2-2, SP)); + goto NEXT_ACTIVE_STATE; + } + } + + /* The state offset is the offset to the opcode */ + + code = start_code + state_offset; + codevalue = *code; + + /* If this opcode is followed by an inline character, load it. It is + tempting to test for the presence of a subject character here, but that + is wrong, because sometimes zero repetitions of the subject are + permitted. + + We also use this mechanism for opcodes such as OP_TYPEPLUS that take an + argument that is not a data character - but is always one byte long. We + have to take special action to deal with \P, \p, \H, \h, \V, \v and \X in + this case. To keep the other cases fast, convert these ones to new opcodes. + */ + + if (coptable[codevalue] > 0) + { + dlen = 1; +#ifdef SUPPORT_UTF8 + if (utf8) { GETCHARLEN(d, (code + coptable[codevalue]), dlen); } else +#endif /* SUPPORT_UTF8 */ + d = code[coptable[codevalue]]; + if (codevalue >= OP_TYPESTAR) + { + switch(d) + { + case OP_ANYBYTE: return PCRE_ERROR_DFA_UITEM; + case OP_NOTPROP: + case OP_PROP: codevalue += OP_PROP_EXTRA; break; + case OP_ANYNL: codevalue += OP_ANYNL_EXTRA; break; + case OP_EXTUNI: codevalue += OP_EXTUNI_EXTRA; break; + case OP_NOT_HSPACE: + case OP_HSPACE: codevalue += OP_HSPACE_EXTRA; break; + case OP_NOT_VSPACE: + case OP_VSPACE: codevalue += OP_VSPACE_EXTRA; break; + default: break; + } + } + } + else + { + dlen = 0; /* Not strictly necessary, but compilers moan */ + d = NOTACHAR; /* if these variables are not set. */ + } + + + /* Now process the individual opcodes */ + + switch (codevalue) + { + +/* ========================================================================== */ + /* Reached a closing bracket. If not at the end of the pattern, carry + on with the next opcode. Otherwise, unless we have an empty string and + PCRE_NOTEMPTY is set, save the match data, shifting up all previous + matches so we always have the longest first. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + if (code != end_code) + { + ADD_ACTIVE(state_offset + 1 + LINK_SIZE, 0); + if (codevalue != OP_KET) + { + ADD_ACTIVE(state_offset - GET(code, 1), 0); + } + } + else if (ptr > current_subject || (md->moptions & PCRE_NOTEMPTY) == 0) + { + if (match_count < 0) match_count = (offsetcount >= 2)? 1 : 0; + else if (match_count > 0 && ++match_count * 2 >= offsetcount) + match_count = 0; + count = ((match_count == 0)? offsetcount : match_count * 2) - 2; + if (count > 0) memmove(offsets + 2, offsets, count * sizeof(int)); + if (offsetcount >= 2) + { + offsets[0] = current_subject - start_subject; + offsets[1] = ptr - start_subject; + DPRINTF(("%.*sSet matched string = \"%.*s\"\n", rlevel*2-2, SP, + offsets[1] - offsets[0], current_subject)); + } + if ((md->moptions & PCRE_DFA_SHORTEST) != 0) + { + DPRINTF(("%.*sEnd of internal_dfa_exec %d: returning %d\n" + "%.*s---------------------\n\n", rlevel*2-2, SP, rlevel, + match_count, rlevel*2-2, SP)); + return match_count; + } + } + break; + +/* ========================================================================== */ + /* These opcodes add to the current list of states without looking + at the current character. */ + + /*-----------------------------------------------------------------*/ + case OP_ALT: + do { code += GET(code, 1); } while (*code == OP_ALT); + ADD_ACTIVE(code - start_code, 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_BRA: + case OP_SBRA: + do + { + ADD_ACTIVE(code - start_code + 1 + LINK_SIZE, 0); + code += GET(code, 1); + } + while (*code == OP_ALT); + break; + + /*-----------------------------------------------------------------*/ + case OP_CBRA: + case OP_SCBRA: + ADD_ACTIVE(code - start_code + 3 + LINK_SIZE, 0); + code += GET(code, 1); + while (*code == OP_ALT) + { + ADD_ACTIVE(code - start_code + 1 + LINK_SIZE, 0); + code += GET(code, 1); + } + break; + + /*-----------------------------------------------------------------*/ + case OP_BRAZERO: + case OP_BRAMINZERO: + ADD_ACTIVE(state_offset + 1, 0); + code += 1 + GET(code, 2); + while (*code == OP_ALT) code += GET(code, 1); + ADD_ACTIVE(code - start_code + 1 + LINK_SIZE, 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_CIRC: + if ((ptr == start_subject && (md->moptions & PCRE_NOTBOL) == 0) || + ((ims & PCRE_MULTILINE) != 0 && + ptr != end_subject && + WAS_NEWLINE(ptr))) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_EOD: + if (ptr >= end_subject) { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_OPT: + ims = code[1]; + ADD_ACTIVE(state_offset + 2, 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_SOD: + if (ptr == start_subject) { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_SOM: + if (ptr == start_subject + start_offset) { ADD_ACTIVE(state_offset + 1, 0); } + break; + + +/* ========================================================================== */ + /* These opcodes inspect the next subject character, and sometimes + the previous one as well, but do not have an argument. The variable + clen contains the length of the current character and is zero if we are + at the end of the subject. */ + + /*-----------------------------------------------------------------*/ + case OP_ANY: + if (clen > 0 && ((ims & PCRE_DOTALL) != 0 || !IS_NEWLINE(ptr))) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_EODN: + if (clen == 0 || (IS_NEWLINE(ptr) && ptr == end_subject - md->nllen)) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_DOLL: + if ((md->moptions & PCRE_NOTEOL) == 0) + { + if (clen == 0 || + (IS_NEWLINE(ptr) && + ((ims & PCRE_MULTILINE) != 0 || ptr == end_subject - md->nllen) + )) + { ADD_ACTIVE(state_offset + 1, 0); } + } + else if ((ims & PCRE_MULTILINE) != 0 && IS_NEWLINE(ptr)) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + + case OP_DIGIT: + case OP_WHITESPACE: + case OP_WORDCHAR: + if (clen > 0 && c < 256 && + ((ctypes[c] & toptable1[codevalue]) ^ toptable2[codevalue]) != 0) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_DIGIT: + case OP_NOT_WHITESPACE: + case OP_NOT_WORDCHAR: + if (clen > 0 && (c >= 256 || + ((ctypes[c] & toptable1[codevalue]) ^ toptable2[codevalue]) != 0)) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_WORD_BOUNDARY: + case OP_NOT_WORD_BOUNDARY: + { + int left_word, right_word; + + if (ptr > start_subject) + { + const uschar *temp = ptr - 1; +#ifdef SUPPORT_UTF8 + if (utf8) BACKCHAR(temp); +#endif + GETCHARTEST(d, temp); + left_word = d < 256 && (ctypes[d] & ctype_word) != 0; + } + else left_word = 0; + + if (clen > 0) right_word = c < 256 && (ctypes[c] & ctype_word) != 0; + else right_word = 0; + + if ((left_word == right_word) == (codevalue == OP_NOT_WORD_BOUNDARY)) + { ADD_ACTIVE(state_offset + 1, 0); } + } + break; + + + /*-----------------------------------------------------------------*/ + /* Check the next character by Unicode property. We will get here only + if the support is in the binary; otherwise a compile-time error occurs. + */ + +#ifdef SUPPORT_UCP + case OP_PROP: + case OP_NOTPROP: + if (clen > 0) + { + BOOL OK; + int category = _pcre_ucp_findprop(c, &chartype, &script); + switch(code[1]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = chartype == ucp_Lu || chartype == ucp_Ll || chartype == ucp_Lt; + break; + + case PT_GC: + OK = category == code[2]; + break; + + case PT_PC: + OK = chartype == code[2]; + break; + + case PT_SC: + OK = script == code[2]; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (codevalue == OP_PROP)) { ADD_NEW(state_offset + 3, 0); } + } + break; +#endif + + + +/* ========================================================================== */ + /* These opcodes likewise inspect the subject character, but have an + argument that is not a data character. It is one of these opcodes: + OP_ANY, OP_DIGIT, OP_NOT_DIGIT, OP_WHITESPACE, OP_NOT_SPACE, OP_WORDCHAR, + OP_NOT_WORDCHAR. The value is loaded into d. */ + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || + (ims & PCRE_DOTALL) != 0 || + !IS_NEWLINE(ptr) + ) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (count > 0 && codevalue == OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || + (ims & PCRE_DOTALL) != 0 || + !IS_NEWLINE(ptr) + ) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + 2, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || + (ims & PCRE_DOTALL) != 0 || + !IS_NEWLINE(ptr) + ) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSSTAR) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEEXACT: + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || + (ims & PCRE_DOTALL) != 0 || + !IS_NEWLINE(ptr) + ) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (++count >= GET2(code, 1)) + { ADD_NEW(state_offset + 4, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + ADD_ACTIVE(state_offset + 4, 0); + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || + (ims & PCRE_DOTALL) != 0 || + !IS_NEWLINE(ptr) + ) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW(state_offset + 4, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + +/* ========================================================================== */ + /* These are virtual opcodes that are used when something like + OP_TYPEPLUS has OP_PROP, OP_NOTPROP, OP_ANYNL, or OP_EXTUNI as its + argument. It keeps the code above fast for the other cases. The argument + is in the d variable. */ + +#ifdef SUPPORT_UCP + case OP_PROP_EXTRA + OP_TYPEPLUS: + case OP_PROP_EXTRA + OP_TYPEMINPLUS: + case OP_PROP_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 4, 0); } + if (clen > 0) + { + BOOL OK; + int category = _pcre_ucp_findprop(c, &chartype, &script); + switch(code[2]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = chartype == ucp_Lu || chartype == ucp_Ll || chartype == ucp_Lt; + break; + + case PT_GC: + OK = category == code[3]; + break; + + case PT_PC: + OK = chartype == code[3]; + break; + + case PT_SC: + OK = script == code[3]; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (count > 0 && codevalue == OP_PROP_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEPLUS: + case OP_EXTUNI_EXTRA + OP_TYPEMINPLUS: + case OP_EXTUNI_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0 && _pcre_ucp_findprop(c, &chartype, &script) != ucp_M) + { + const uschar *nptr = ptr + clen; + int ncount = 0; + if (count > 0 && codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + while (nptr < end_subject) + { + int nd; + int ndlen = 1; + GETCHARLEN(nd, nptr, ndlen); + if (_pcre_ucp_findprop(nd, &chartype, &script) != ucp_M) break; + ncount++; + nptr += ndlen; + } + count++; + ADD_NEW_DATA(-state_offset, count, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEPLUS: + case OP_ANYNL_EXTRA + OP_TYPEMINPLUS: + case OP_ANYNL_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if ((md->moptions & PCRE_BSR_ANYCRLF) != 0) break; + goto ANYNL01; + + case 0x000d: + if (ptr + 1 < end_subject && ptr[1] == 0x0a) ncount = 1; + /* Fall through */ + + ANYNL01: + case 0x000a: + if (count > 0 && codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, ncount); + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEPLUS: + case OP_VSPACE_EXTRA + OP_TYPEMINPLUS: + case OP_VSPACE_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x000a: + case 0x000b: + case 0x000c: + case 0x000d: + case 0x0085: + case 0x2028: + case 0x2029: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_VSPACE)) + { + if (count > 0 && codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEPLUS: + case OP_HSPACE_EXTRA + OP_TYPEMINPLUS: + case OP_HSPACE_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (count > 0 && codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ +#ifdef SUPPORT_UCP + case OP_PROP_EXTRA + OP_TYPEQUERY: + case OP_PROP_EXTRA + OP_TYPEMINQUERY: + case OP_PROP_EXTRA + OP_TYPEPOSQUERY: + count = 4; + goto QS1; + + case OP_PROP_EXTRA + OP_TYPESTAR: + case OP_PROP_EXTRA + OP_TYPEMINSTAR: + case OP_PROP_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS1: + + ADD_ACTIVE(state_offset + 4, 0); + if (clen > 0) + { + BOOL OK; + int category = _pcre_ucp_findprop(c, &chartype, &script); + switch(code[2]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = chartype == ucp_Lu || chartype == ucp_Ll || chartype == ucp_Lt; + break; + + case PT_GC: + OK = category == code[3]; + break; + + case PT_PC: + OK = chartype == code[3]; + break; + + case PT_SC: + OK = script == code[3]; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (codevalue == OP_PROP_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_PROP_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEQUERY: + case OP_EXTUNI_EXTRA + OP_TYPEMINQUERY: + case OP_EXTUNI_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS2; + + case OP_EXTUNI_EXTRA + OP_TYPESTAR: + case OP_EXTUNI_EXTRA + OP_TYPEMINSTAR: + case OP_EXTUNI_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS2: + + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0 && _pcre_ucp_findprop(c, &chartype, &script) != ucp_M) + { + const uschar *nptr = ptr + clen; + int ncount = 0; + if (codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + while (nptr < end_subject) + { + int nd; + int ndlen = 1; + GETCHARLEN(nd, nptr, ndlen); + if (_pcre_ucp_findprop(nd, &chartype, &script) != ucp_M) break; + ncount++; + nptr += ndlen; + } + ADD_NEW_DATA(-(state_offset + count), 0, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEQUERY: + case OP_ANYNL_EXTRA + OP_TYPEMINQUERY: + case OP_ANYNL_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS3; + + case OP_ANYNL_EXTRA + OP_TYPESTAR: + case OP_ANYNL_EXTRA + OP_TYPEMINSTAR: + case OP_ANYNL_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS3: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if ((md->moptions & PCRE_BSR_ANYCRLF) != 0) break; + goto ANYNL02; + + case 0x000d: + if (ptr + 1 < end_subject && ptr[1] == 0x0a) ncount = 1; + /* Fall through */ + + ANYNL02: + case 0x000a: + if (codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + count), 0, ncount); + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEQUERY: + case OP_VSPACE_EXTRA + OP_TYPEMINQUERY: + case OP_VSPACE_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS4; + + case OP_VSPACE_EXTRA + OP_TYPESTAR: + case OP_VSPACE_EXTRA + OP_TYPEMINSTAR: + case OP_VSPACE_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS4: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x000a: + case 0x000b: + case 0x000c: + case 0x000d: + case 0x0085: + case 0x2028: + case 0x2029: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + if (OK == (d == OP_VSPACE)) + { + if (codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + count), 0, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEQUERY: + case OP_HSPACE_EXTRA + OP_TYPEMINQUERY: + case OP_HSPACE_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS5; + + case OP_HSPACE_EXTRA + OP_TYPESTAR: + case OP_HSPACE_EXTRA + OP_TYPEMINSTAR: + case OP_HSPACE_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS5: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + count), 0, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ +#ifdef SUPPORT_UCP + case OP_PROP_EXTRA + OP_TYPEEXACT: + case OP_PROP_EXTRA + OP_TYPEUPTO: + case OP_PROP_EXTRA + OP_TYPEMINUPTO: + case OP_PROP_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_PROP_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 6, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + int category = _pcre_ucp_findprop(c, &chartype, &script); + switch(code[4]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = chartype == ucp_Lu || chartype == ucp_Ll || chartype == ucp_Lt; + break; + + case PT_GC: + OK = category == code[5]; + break; + + case PT_PC: + OK = chartype == code[5]; + break; + + case PT_SC: + OK = script == code[5]; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (codevalue == OP_PROP_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW(state_offset + 6, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEEXACT: + case OP_EXTUNI_EXTRA + OP_TYPEUPTO: + case OP_EXTUNI_EXTRA + OP_TYPEMINUPTO: + case OP_EXTUNI_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_EXTUNI_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 4, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0 && _pcre_ucp_findprop(c, &chartype, &script) != ucp_M) + { + const uschar *nptr = ptr + clen; + int ncount = 0; + if (codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + while (nptr < end_subject) + { + int nd; + int ndlen = 1; + GETCHARLEN(nd, nptr, ndlen); + if (_pcre_ucp_findprop(nd, &chartype, &script) != ucp_M) break; + ncount++; + nptr += ndlen; + } + if (++count >= GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 4), 0, ncount); } + else + { ADD_NEW_DATA(-state_offset, count, ncount); } + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEEXACT: + case OP_ANYNL_EXTRA + OP_TYPEUPTO: + case OP_ANYNL_EXTRA + OP_TYPEMINUPTO: + case OP_ANYNL_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_ANYNL_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 4, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if ((md->moptions & PCRE_BSR_ANYCRLF) != 0) break; + goto ANYNL03; + + case 0x000d: + if (ptr + 1 < end_subject && ptr[1] == 0x0a) ncount = 1; + /* Fall through */ + + ANYNL03: + case 0x000a: + if (codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 4), 0, ncount); } + else + { ADD_NEW_DATA(-state_offset, count, ncount); } + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEEXACT: + case OP_VSPACE_EXTRA + OP_TYPEUPTO: + case OP_VSPACE_EXTRA + OP_TYPEMINUPTO: + case OP_VSPACE_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_VSPACE_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 4, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x000a: + case 0x000b: + case 0x000c: + case 0x000d: + case 0x0085: + case 0x2028: + case 0x2029: + OK = TRUE; + break; + + default: + OK = FALSE; + } + + if (OK == (d == OP_VSPACE)) + { + if (codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 4), 0, 0); } + else + { ADD_NEW_DATA(-state_offset, count, 0); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEEXACT: + case OP_HSPACE_EXTRA + OP_TYPEUPTO: + case OP_HSPACE_EXTRA + OP_TYPEMINUPTO: + case OP_HSPACE_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_HSPACE_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 4, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + switch (c) + { + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 4), 0, 0); } + else + { ADD_NEW_DATA(-state_offset, count, 0); } + } + } + break; + +/* ========================================================================== */ + /* These opcodes are followed by a character that is usually compared + to the current subject character; it is loaded into d. We still get + here even if there is no subject character, because in some cases zero + repetitions are permitted. */ + + /*-----------------------------------------------------------------*/ + case OP_CHAR: + if (clen > 0 && c == d) { ADD_NEW(state_offset + dlen + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_CHARNC: + if (clen == 0) break; + +#ifdef SUPPORT_UTF8 + if (utf8) + { + if (c == d) { ADD_NEW(state_offset + dlen + 1, 0); } else + { + unsigned int othercase; + if (c < 128) othercase = fcc[c]; else + + /* If we have Unicode property support, we can use it to test the + other case of the character. */ + +#ifdef SUPPORT_UCP + othercase = _pcre_ucp_othercase(c); +#else + othercase = NOTACHAR; +#endif + + if (d == othercase) { ADD_NEW(state_offset + dlen + 1, 0); } + } + } + else +#endif /* SUPPORT_UTF8 */ + + /* Non-UTF-8 mode */ + { + if (lcc[c] == lcc[d]) { ADD_NEW(state_offset + 2, 0); } + } + break; + + +#ifdef SUPPORT_UCP + /*-----------------------------------------------------------------*/ + /* This is a tricky one because it can match more than one character. + Find out how many characters to skip, and then set up a negative state + to wait for them to pass before continuing. */ + + case OP_EXTUNI: + if (clen > 0 && _pcre_ucp_findprop(c, &chartype, &script) != ucp_M) + { + const uschar *nptr = ptr + clen; + int ncount = 0; + while (nptr < end_subject) + { + int nclen = 1; + GETCHARLEN(c, nptr, nclen); + if (_pcre_ucp_findprop(c, &chartype, &script) != ucp_M) break; + ncount++; + nptr += nclen; + } + ADD_NEW_DATA(-(state_offset + 1), 0, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + /* This is a tricky like EXTUNI because it too can match more than one + character (when CR is followed by LF). In this case, set up a negative + state to wait for one character to pass before continuing. */ + + case OP_ANYNL: + if (clen > 0) switch(c) + { + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if ((md->moptions & PCRE_BSR_ANYCRLF) != 0) break; + + case 0x000a: + ADD_NEW(state_offset + 1, 0); + break; + + case 0x000d: + if (ptr + 1 < end_subject && ptr[1] == 0x0a) + { + ADD_NEW_DATA(-(state_offset + 1), 0, 1); + } + else + { + ADD_NEW(state_offset + 1, 0); + } + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_VSPACE: + if (clen > 0) switch(c) + { + case 0x000a: + case 0x000b: + case 0x000c: + case 0x000d: + case 0x0085: + case 0x2028: + case 0x2029: + break; + + default: + ADD_NEW(state_offset + 1, 0); + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE: + if (clen > 0) switch(c) + { + case 0x000a: + case 0x000b: + case 0x000c: + case 0x000d: + case 0x0085: + case 0x2028: + case 0x2029: + ADD_NEW(state_offset + 1, 0); + break; + + default: break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_HSPACE: + if (clen > 0) switch(c) + { + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + break; + + default: + ADD_NEW(state_offset + 1, 0); + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE: + if (clen > 0) switch(c) + { + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + ADD_NEW(state_offset + 1, 0); + break; + } + break; + + /*-----------------------------------------------------------------*/ + /* Match a negated single character. This is only used for one-byte + characters, that is, we know that d < 256. The character we are + checking (c) can be multibyte. */ + + case OP_NOT: + if (clen > 0) + { + unsigned int otherd = ((ims & PCRE_CASELESS) != 0)? fcc[d] : d; + if (c != d && c != otherd) { ADD_NEW(state_offset + dlen + 1, 0); } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + dlen + 1, 0); } + if (clen > 0) + { + unsigned int otherd = NOTACHAR; + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && d >= 128) + { +#ifdef SUPPORT_UCP + otherd = _pcre_ucp_othercase(d); +#endif /* SUPPORT_UCP */ + } + else +#endif /* SUPPORT_UTF8 */ + otherd = fcc[d]; + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (count > 0 && + (codevalue == OP_POSPLUS || codevalue == OP_NOTPOSPLUS)) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTPOSQUERY: + ADD_ACTIVE(state_offset + dlen + 1, 0); + if (clen > 0) + { + unsigned int otherd = NOTACHAR; + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && d >= 128) + { +#ifdef SUPPORT_UCP + otherd = _pcre_ucp_othercase(d); +#endif /* SUPPORT_UCP */ + } + else +#endif /* SUPPORT_UTF8 */ + otherd = fcc[d]; + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSQUERY || codevalue == OP_NOTPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + dlen + 1, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPOSSTAR: + ADD_ACTIVE(state_offset + dlen + 1, 0); + if (clen > 0) + { + unsigned int otherd = NOTACHAR; + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && d >= 128) + { +#ifdef SUPPORT_UCP + otherd = _pcre_ucp_othercase(d); +#endif /* SUPPORT_UCP */ + } + else +#endif /* SUPPORT_UTF8 */ + otherd = fcc[d]; + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSSTAR || codevalue == OP_NOTPOSSTAR) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXACT: + case OP_NOTEXACT: + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + unsigned int otherd = NOTACHAR; + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && d >= 128) + { +#ifdef SUPPORT_UCP + otherd = _pcre_ucp_othercase(d); +#endif /* SUPPORT_UCP */ + } + else +#endif /* SUPPORT_UTF8 */ + otherd = fcc[d]; + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (++count >= GET2(code, 1)) + { ADD_NEW(state_offset + dlen + 3, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTPOSUPTO: + ADD_ACTIVE(state_offset + dlen + 3, 0); + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + unsigned int otherd = NOTACHAR; + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (utf8 && d >= 128) + { +#ifdef SUPPORT_UCP + otherd = _pcre_ucp_othercase(d); +#endif /* SUPPORT_UCP */ + } + else +#endif /* SUPPORT_UTF8 */ + otherd = fcc[d]; + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSUPTO || codevalue == OP_NOTPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= GET2(code, 1)) + { ADD_NEW(state_offset + dlen + 3, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + +/* ========================================================================== */ + /* These are the class-handling opcodes */ + + case OP_CLASS: + case OP_NCLASS: + case OP_XCLASS: + { + BOOL isinclass = FALSE; + int next_state_offset; + const uschar *ecode; + + /* For a simple class, there is always just a 32-byte table, and we + can set isinclass from it. */ + + if (codevalue != OP_XCLASS) + { + ecode = code + 33; + if (clen > 0) + { + isinclass = (c > 255)? (codevalue == OP_NCLASS) : + ((code[1 + c/8] & (1 << (c&7))) != 0); + } + } + + /* An extended class may have a table or a list of single characters, + ranges, or both, and it may be positive or negative. There's a + function that sorts all this out. */ + + else + { + ecode = code + GET(code, 1); + if (clen > 0) isinclass = _pcre_xclass(c, code + 1 + LINK_SIZE); + } + + /* At this point, isinclass is set for all kinds of class, and ecode + points to the byte after the end of the class. If there is a + quantifier, this is where it will be. */ + + next_state_offset = ecode - start_code; + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + ADD_ACTIVE(next_state_offset + 1, 0); + if (isinclass) { ADD_NEW(state_offset, 0); } + break; + + case OP_CRPLUS: + case OP_CRMINPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(next_state_offset + 1, 0); } + if (isinclass) { count++; ADD_NEW(state_offset, count); } + break; + + case OP_CRQUERY: + case OP_CRMINQUERY: + ADD_ACTIVE(next_state_offset + 1, 0); + if (isinclass) { ADD_NEW(next_state_offset + 1, 0); } + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + count = current_state->count; /* Already matched */ + if (count >= GET2(ecode, 1)) + { ADD_ACTIVE(next_state_offset + 5, 0); } + if (isinclass) + { + int max = GET2(ecode, 3); + if (++count >= max && max != 0) /* Max 0 => no limit */ + { ADD_NEW(next_state_offset + 5, 0); } + else + { ADD_NEW(state_offset, count); } + } + break; + + default: + if (isinclass) { ADD_NEW(next_state_offset, 0); } + break; + } + } + break; + +/* ========================================================================== */ + /* These are the opcodes for fancy brackets of various kinds. We have + to use recursion in order to handle them. */ + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + { + int rc; + int local_offsets[2]; + int local_workspace[1000]; + const uschar *endasscode = code + GET(code, 1); + + while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1); + + rc = internal_dfa_exec( + md, /* static match data */ + code, /* this subexpression's code */ + ptr, /* where we currently are */ + ptr - start_subject, /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(int), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + ims, /* the current ims flags */ + rlevel, /* function recursion level */ + recursing); /* pass on regex recursion */ + + if ((rc >= 0) == (codevalue == OP_ASSERT || codevalue == OP_ASSERTBACK)) + { ADD_ACTIVE(endasscode + LINK_SIZE + 1 - start_code, 0); } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_COND: + case OP_SCOND: + { + int local_offsets[1000]; + int local_workspace[1000]; + int condcode = code[LINK_SIZE+1]; + + /* Back reference conditions are not supported */ + + if (condcode == OP_CREF) return PCRE_ERROR_DFA_UCOND; + + /* The DEFINE condition is always false */ + + if (condcode == OP_DEF) + { + ADD_ACTIVE(state_offset + GET(code, 1) + LINK_SIZE + 1, 0); + } + + /* The only supported version of OP_RREF is for the value RREF_ANY, + which means "test if in any recursion". We can't test for specifically + recursed groups. */ + + else if (condcode == OP_RREF) + { + int value = GET2(code, LINK_SIZE+2); + if (value != RREF_ANY) return PCRE_ERROR_DFA_UCOND; + if (recursing > 0) { ADD_ACTIVE(state_offset + LINK_SIZE + 4, 0); } + else { ADD_ACTIVE(state_offset + GET(code, 1) + LINK_SIZE + 1, 0); } + } + + /* Otherwise, the condition is an assertion */ + + else + { + int rc; + const uschar *asscode = code + LINK_SIZE + 1; + const uschar *endasscode = asscode + GET(asscode, 1); + + while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1); + + rc = internal_dfa_exec( + md, /* fixed match data */ + asscode, /* this subexpression's code */ + ptr, /* where we currently are */ + ptr - start_subject, /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(int), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + ims, /* the current ims flags */ + rlevel, /* function recursion level */ + recursing); /* pass on regex recursion */ + + if ((rc >= 0) == + (condcode == OP_ASSERT || condcode == OP_ASSERTBACK)) + { ADD_ACTIVE(endasscode + LINK_SIZE + 1 - start_code, 0); } + else + { ADD_ACTIVE(state_offset + GET(code, 1) + LINK_SIZE + 1, 0); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_RECURSE: + { + int local_offsets[1000]; + int local_workspace[1000]; + int rc; + + DPRINTF(("%.*sStarting regex recursion %d\n", rlevel*2-2, SP, + recursing + 1)); + + rc = internal_dfa_exec( + md, /* fixed match data */ + start_code + GET(code, 1), /* this subexpression's code */ + ptr, /* where we currently are */ + ptr - start_subject, /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(int), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + ims, /* the current ims flags */ + rlevel, /* function recursion level */ + recursing + 1); /* regex recurse level */ + + DPRINTF(("%.*sReturn from regex recursion %d: rc=%d\n", rlevel*2-2, SP, + recursing + 1, rc)); + + /* Ran out of internal offsets */ + + if (rc == 0) return PCRE_ERROR_DFA_RECURSE; + + /* For each successful matched substring, set up the next state with a + count of characters to skip before trying it. Note that the count is in + characters, not bytes. */ + + if (rc > 0) + { + for (rc = rc*2 - 2; rc >= 0; rc -= 2) + { + const uschar *p = start_subject + local_offsets[rc]; + const uschar *pp = start_subject + local_offsets[rc+1]; + int charcount = local_offsets[rc+1] - local_offsets[rc]; + while (p < pp) if ((*p++ & 0xc0) == 0x80) charcount--; + if (charcount > 0) + { + ADD_NEW_DATA(-(state_offset + LINK_SIZE + 1), 0, (charcount - 1)); + } + else + { + ADD_ACTIVE(state_offset + LINK_SIZE + 1, 0); + } + } + } + else if (rc != PCRE_ERROR_NOMATCH) return rc; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_ONCE: + { + int local_offsets[2]; + int local_workspace[1000]; + + int rc = internal_dfa_exec( + md, /* fixed match data */ + code, /* this subexpression's code */ + ptr, /* where we currently are */ + ptr - start_subject, /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(int), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + ims, /* the current ims flags */ + rlevel, /* function recursion level */ + recursing); /* pass on regex recursion */ + + if (rc >= 0) + { + const uschar *end_subpattern = code; + int charcount = local_offsets[1] - local_offsets[0]; + int next_state_offset, repeat_state_offset; + + do { end_subpattern += GET(end_subpattern, 1); } + while (*end_subpattern == OP_ALT); + next_state_offset = end_subpattern - start_code + LINK_SIZE + 1; + + /* If the end of this subpattern is KETRMAX or KETRMIN, we must + arrange for the repeat state also to be added to the relevant list. + Calculate the offset, or set -1 for no repeat. */ + + repeat_state_offset = (*end_subpattern == OP_KETRMAX || + *end_subpattern == OP_KETRMIN)? + end_subpattern - start_code - GET(end_subpattern, 1) : -1; + + /* If we have matched an empty string, add the next state at the + current character pointer. This is important so that the duplicate + checking kicks in, which is what breaks infinite loops that match an + empty string. */ + + if (charcount == 0) + { + ADD_ACTIVE(next_state_offset, 0); + } + + /* Optimization: if there are no more active states, and there + are no new states yet set up, then skip over the subject string + right here, to save looping. Otherwise, set up the new state to swing + into action when the end of the substring is reached. */ + + else if (i + 1 >= active_count && new_count == 0) + { + ptr += charcount; + clen = 0; + ADD_NEW(next_state_offset, 0); + + /* If we are adding a repeat state at the new character position, + we must fudge things so that it is the only current state. + Otherwise, it might be a duplicate of one we processed before, and + that would cause it to be skipped. */ + + if (repeat_state_offset >= 0) + { + next_active_state = active_states; + active_count = 0; + i = -1; + ADD_ACTIVE(repeat_state_offset, 0); + } + } + else + { + const uschar *p = start_subject + local_offsets[0]; + const uschar *pp = start_subject + local_offsets[1]; + while (p < pp) if ((*p++ & 0xc0) == 0x80) charcount--; + ADD_NEW_DATA(-next_state_offset, 0, (charcount - 1)); + if (repeat_state_offset >= 0) + { ADD_NEW_DATA(-repeat_state_offset, 0, (charcount - 1)); } + } + + } + else if (rc != PCRE_ERROR_NOMATCH) return rc; + } + break; + + +/* ========================================================================== */ + /* Handle callouts */ + + case OP_CALLOUT: + if (pcre_callout != NULL) + { + int rrc; + pcre_callout_block cb; + cb.version = 1; /* Version 1 of the callout block */ + cb.callout_number = code[1]; + cb.offset_vector = offsets; + cb.subject = (PCRE_SPTR)start_subject; + cb.subject_length = end_subject - start_subject; + cb.start_match = current_subject - start_subject; + cb.current_position = ptr - start_subject; + cb.pattern_position = GET(code, 2); + cb.next_item_length = GET(code, 2 + LINK_SIZE); + cb.capture_top = 1; + cb.capture_last = -1; + cb.callout_data = md->callout_data; + if ((rrc = (*pcre_callout)(&cb)) < 0) return rrc; /* Abandon */ + if (rrc == 0) { ADD_ACTIVE(state_offset + 2 + 2*LINK_SIZE, 0); } + } + break; + + +/* ========================================================================== */ + default: /* Unsupported opcode */ + return PCRE_ERROR_DFA_UITEM; + } + + NEXT_ACTIVE_STATE: continue; + + } /* End of loop scanning active states */ + + /* We have finished the processing at the current subject character. If no + new states have been set for the next character, we have found all the + matches that we are going to find. If we are at the top level and partial + matching has been requested, check for appropriate conditions. */ + + if (new_count <= 0) + { + if (match_count < 0 && /* No matches found */ + rlevel == 1 && /* Top level match function */ + (md->moptions & PCRE_PARTIAL) != 0 && /* Want partial matching */ + ptr >= end_subject && /* Reached end of subject */ + ptr > current_subject) /* Matched non-empty string */ + { + if (offsetcount >= 2) + { + offsets[0] = current_subject - start_subject; + offsets[1] = end_subject - start_subject; + } + match_count = PCRE_ERROR_PARTIAL; + } + + DPRINTF(("%.*sEnd of internal_dfa_exec %d: returning %d\n" + "%.*s---------------------\n\n", rlevel*2-2, SP, rlevel, match_count, + rlevel*2-2, SP)); + break; /* In effect, "return", but see the comment below */ + } + + /* One or more states are active for the next character. */ + + ptr += clen; /* Advance to next subject character */ + } /* Loop to move along the subject string */ + +/* Control gets here from "break" a few lines above. We do it this way because +if we use "return" above, we have compiler trouble. Some compilers warn if +there's nothing here because they think the function doesn't return a value. On +the other hand, if we put a dummy statement here, some more clever compilers +complain that it can't be reached. Sigh. */ + +return match_count; +} + + + + +/************************************************* +* Execute a Regular Expression - DFA engine * +*************************************************/ + +/* This external function applies a compiled re to a subject string using a DFA +engine. This function calls the internal function multiple times if the pattern +is not anchored. + +Arguments: + argument_re points to the compiled expression + extra_data points to extra data or is NULL + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + offsets vector of match offsets + offsetcount size of same + workspace workspace vector + wscount size of same + +Returns: > 0 => number of match offset pairs placed in offsets + = 0 => offsets overflowed; longest matches are present + -1 => failed to match + < -1 => some kind of unexpected problem +*/ + +PCRE_EXP_DEFN int +pcre_dfa_exec(const pcre *argument_re, const pcre_extra *extra_data, + const char *subject, int length, int start_offset, int options, int *offsets, + int offsetcount, int *workspace, int wscount) +{ +real_pcre *re = (real_pcre *)argument_re; +dfa_match_data match_block; +dfa_match_data *md = &match_block; +BOOL utf8, anchored, startline, firstline; +const uschar *current_subject, *end_subject, *lcc; + +pcre_study_data internal_study; +const pcre_study_data *study = NULL; +real_pcre internal_re; + +const uschar *req_byte_ptr; +const uschar *start_bits = NULL; +BOOL first_byte_caseless = FALSE; +BOOL req_byte_caseless = FALSE; +int first_byte = -1; +int req_byte = -1; +int req_byte2 = -1; +int newline; + +/* Plausibility checks */ + +if ((options & ~PUBLIC_DFA_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION; +if (re == NULL || subject == NULL || workspace == NULL || + (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL; +if (offsetcount < 0) return PCRE_ERROR_BADCOUNT; +if (wscount < 20) return PCRE_ERROR_DFA_WSSIZE; + +/* We need to find the pointer to any study data before we test for byte +flipping, so we scan the extra_data block first. This may set two fields in the +match block, so we must initialize them beforehand. However, the other fields +in the match block must not be set until after the byte flipping. */ + +md->tables = re->tables; +md->callout_data = NULL; + +if (extra_data != NULL) + { + unsigned int flags = extra_data->flags; + if ((flags & PCRE_EXTRA_STUDY_DATA) != 0) + study = (const pcre_study_data *)extra_data->study_data; + if ((flags & PCRE_EXTRA_MATCH_LIMIT) != 0) return PCRE_ERROR_DFA_UMLIMIT; + if ((flags & PCRE_EXTRA_MATCH_LIMIT_RECURSION) != 0) + return PCRE_ERROR_DFA_UMLIMIT; + if ((flags & PCRE_EXTRA_CALLOUT_DATA) != 0) + md->callout_data = extra_data->callout_data; + if ((flags & PCRE_EXTRA_TABLES) != 0) + md->tables = extra_data->tables; + } + +/* Check that the first field in the block is the magic number. If it is not, +test for a regex that was compiled on a host of opposite endianness. If this is +the case, flipped values are put in internal_re and internal_study if there was +study data too. */ + +if (re->magic_number != MAGIC_NUMBER) + { + re = _pcre_try_flipped(re, &internal_re, study, &internal_study); + if (re == NULL) return PCRE_ERROR_BADMAGIC; + if (study != NULL) study = &internal_study; + } + +/* Set some local values */ + +current_subject = (const unsigned char *)subject + start_offset; +end_subject = (const unsigned char *)subject + length; +req_byte_ptr = current_subject - 1; + +#ifdef SUPPORT_UTF8 +utf8 = (re->options & PCRE_UTF8) != 0; +#else +utf8 = FALSE; +#endif + +anchored = (options & (PCRE_ANCHORED|PCRE_DFA_RESTART)) != 0 || + (re->options & PCRE_ANCHORED) != 0; + +/* The remaining fixed data for passing around. */ + +md->start_code = (const uschar *)argument_re + + re->name_table_offset + re->name_count * re->name_entry_size; +md->start_subject = (const unsigned char *)subject; +md->end_subject = end_subject; +md->moptions = options; +md->poptions = re->options; + +/* If the BSR option is not set at match time, copy what was set +at compile time. */ + +if ((md->moptions & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) == 0) + { + if ((re->options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) != 0) + md->moptions |= re->options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE); +#ifdef BSR_ANYCRLF + else md->moptions |= PCRE_BSR_ANYCRLF; +#endif + } + +/* Handle different types of newline. The three bits give eight cases. If +nothing is set at run time, whatever was used at compile time applies. */ + +switch ((((options & PCRE_NEWLINE_BITS) == 0)? re->options : (pcre_uint32)options) & + PCRE_NEWLINE_BITS) + { + case 0: newline = NEWLINE; break; /* Compile-time default */ + case PCRE_NEWLINE_CR: newline = '\r'; break; + case PCRE_NEWLINE_LF: newline = '\n'; break; + case PCRE_NEWLINE_CR+ + PCRE_NEWLINE_LF: newline = ('\r' << 8) | '\n'; break; + case PCRE_NEWLINE_ANY: newline = -1; break; + case PCRE_NEWLINE_ANYCRLF: newline = -2; break; + default: return PCRE_ERROR_BADNEWLINE; + } + +if (newline == -2) + { + md->nltype = NLTYPE_ANYCRLF; + } +else if (newline < 0) + { + md->nltype = NLTYPE_ANY; + } +else + { + md->nltype = NLTYPE_FIXED; + if (newline > 255) + { + md->nllen = 2; + md->nl[0] = (newline >> 8) & 255; + md->nl[1] = newline & 255; + } + else + { + md->nllen = 1; + md->nl[0] = newline; + } + } + +/* Check a UTF-8 string if required. Unfortunately there's no way of passing +back the character offset. */ + +#ifdef SUPPORT_UTF8 +if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0) + { + if (_pcre_valid_utf8((uschar *)subject, length) >= 0) + return PCRE_ERROR_BADUTF8; + if (start_offset > 0 && start_offset < length) + { + int tb = ((uschar *)subject)[start_offset]; + if (tb > 127) + { + tb &= 0xc0; + if (tb != 0 && tb != 0xc0) return PCRE_ERROR_BADUTF8_OFFSET; + } + } + } +#endif + +/* If the exec call supplied NULL for tables, use the inbuilt ones. This +is a feature that makes it possible to save compiled regex and re-use them +in other programs later. */ + +if (md->tables == NULL) md->tables = _pcre_default_tables; + +/* The lower casing table and the "must be at the start of a line" flag are +used in a loop when finding where to start. */ + +lcc = md->tables + lcc_offset; +startline = (re->flags & PCRE_STARTLINE) != 0; +firstline = (re->options & PCRE_FIRSTLINE) != 0; + +/* Set up the first character to match, if available. The first_byte value is +never set for an anchored regular expression, but the anchoring may be forced +at run time, so we have to test for anchoring. The first char may be unset for +an unanchored pattern, of course. If there's no first char and the pattern was +studied, there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->flags & PCRE_FIRSTSET) != 0) + { + first_byte = re->first_byte & 255; + if ((first_byte_caseless = ((re->first_byte & REQ_CASELESS) != 0)) == TRUE) + first_byte = lcc[first_byte]; + } + else + { + if (startline && study != NULL && + (study->options & PCRE_STUDY_MAPPED) != 0) + start_bits = study->start_bits; + } + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. */ + +if ((re->flags & PCRE_REQCHSET) != 0) + { + req_byte = re->req_byte & 255; + req_byte_caseless = (re->req_byte & REQ_CASELESS) != 0; + req_byte2 = (md->tables + fcc_offset)[req_byte]; /* case flipped */ + } + +/* Call the main matching function, looping for a non-anchored regex after a +failed match. Unless restarting, optimize by moving to the first match +character if possible, when not anchored. Then unless wanting a partial match, +check for a required later character. */ + +for (;;) + { + int rc; + + if ((options & PCRE_DFA_RESTART) == 0) + { + const uschar *save_end_subject = end_subject; + + /* Advance to a unique first char if possible. If firstline is TRUE, the + start of the match is constrained to the first line of a multiline string. + Implement this by temporarily adjusting end_subject so that we stop + scanning at a newline. If the match fails at the newline, later code breaks + this loop. */ + + if (firstline) + { + const uschar *t = current_subject; + while (t < md->end_subject && !IS_NEWLINE(t)) t++; + end_subject = t; + } + + if (first_byte >= 0) + { + if (first_byte_caseless) + while (current_subject < end_subject && + lcc[*current_subject] != first_byte) + current_subject++; + else + while (current_subject < end_subject && *current_subject != first_byte) + current_subject++; + } + + /* Or to just after a linebreak for a multiline match if possible */ + + else if (startline) + { + if (current_subject > md->start_subject + start_offset) + { + while (current_subject <= end_subject && !WAS_NEWLINE(current_subject)) + current_subject++; + + /* If we have just passed a CR and the newline option is ANY or + ANYCRLF, and we are now at a LF, advance the match position by one more + character. */ + + if (current_subject[-1] == '\r' && + (md->nltype == NLTYPE_ANY || md->nltype == NLTYPE_ANYCRLF) && + current_subject < end_subject && + *current_subject == '\n') + current_subject++; + } + } + + /* Or to a non-unique first char after study */ + + else if (start_bits != NULL) + { + while (current_subject < end_subject) + { + register unsigned int c = *current_subject; + if ((start_bits[c/8] & (1 << (c&7))) == 0) current_subject++; + else break; + } + } + + /* Restore fudged end_subject */ + + end_subject = save_end_subject; + } + + /* If req_byte is set, we know that that character must appear in the subject + for the match to succeed. If the first character is set, req_byte must be + later in the subject; otherwise the test starts at the match point. This + optimization can save a huge amount of work in patterns with nested unlimited + repeats that aren't going to match. Writing separate code for cased/caseless + versions makes it go faster, as does using an autoincrement and backing off + on a match. + + HOWEVER: when the subject string is very, very long, searching to its end can + take a long time, and give bad performance on quite ordinary patterns. This + showed up when somebody was matching /^C/ on a 32-megabyte string... so we + don't do this when the string is sufficiently long. + + ALSO: this processing is disabled when partial matching is requested. + */ + + if (req_byte >= 0 && + end_subject - current_subject < REQ_BYTE_MAX && + (options & PCRE_PARTIAL) == 0) + { + register const uschar *p = current_subject + ((first_byte >= 0)? 1 : 0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_byte_ptr) + { + if (req_byte_caseless) + { + while (p < end_subject) + { + register int pp = *p++; + if (pp == req_byte || pp == req_byte2) { p--; break; } + } + } + else + { + while (p < end_subject) + { + if (*p++ == req_byte) { p--; break; } + } + } + + /* If we can't find the required character, break the matching loop, + which will cause a return or PCRE_ERROR_NOMATCH. */ + + if (p >= end_subject) break; + + /* If we have found the required character, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this character yet. */ + + req_byte_ptr = p; + } + } + + /* OK, now we can do the business */ + + rc = internal_dfa_exec( + md, /* fixed match data */ + md->start_code, /* this subexpression's code */ + current_subject, /* where we currently are */ + start_offset, /* start offset in subject */ + offsets, /* offset vector */ + offsetcount, /* size of same */ + workspace, /* workspace vector */ + wscount, /* size of same */ + re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL), /* ims flags */ + 0, /* function recurse level */ + 0); /* regex recurse level */ + + /* Anything other than "no match" means we are done, always; otherwise, carry + on only if not anchored. */ + + if (rc != PCRE_ERROR_NOMATCH || anchored) return rc; + + /* Advance to the next subject character unless we are at the end of a line + and firstline is set. */ + + if (firstline && IS_NEWLINE(current_subject)) break; + current_subject++; + if (utf8) + { + while (current_subject < end_subject && (*current_subject & 0xc0) == 0x80) + current_subject++; + } + if (current_subject > end_subject) break; + + /* If we have just passed a CR and we are now at a LF, and the pattern does + not contain any explicit matches for \r or \n, and the newline option is CRLF + or ANY or ANYCRLF, advance the match position by one more character. */ + + if (current_subject[-1] == '\r' && + current_subject < end_subject && + *current_subject == '\n' && + (re->flags & PCRE_HASCRORLF) == 0 && + (md->nltype == NLTYPE_ANY || + md->nltype == NLTYPE_ANYCRLF || + md->nllen == 2)) + current_subject++; + + } /* "Bumpalong" loop */ + +return PCRE_ERROR_NOMATCH; +} + +/* End of pcre_dfa_exec.c */ diff --git a/pcre-7.4/pcre_exec.c b/pcre-7.4/pcre_exec.c new file mode 100644 index 0000000..6db7c35 --- /dev/null +++ b/pcre-7.4/pcre_exec.c @@ -0,0 +1,4938 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains pcre_exec(), the externally visible function that does +pattern matching using an NFA algorithm, trying to mimic Perl as closely as +possible. There are also some static supporting functions. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK md /* Block containing newline information */ +#define PSSTART start_subject /* Field containing processed string start */ +#define PSEND end_subject /* Field containing processed string end */ + +#include "pcre_internal.h" + +/* Undefine some potentially clashing cpp symbols */ + +#undef min +#undef max + +/* Flag bits for the match() function */ + +#define match_condassert 0x01 /* Called to check a condition assertion */ +#define match_cbegroup 0x02 /* Could-be-empty unlimited repeat group */ + +/* Non-error returns from the match() function. Error returns are externally +defined PCRE_ERROR_xxx codes, which are all negative. */ + +#define MATCH_MATCH 1 +#define MATCH_NOMATCH 0 + +/* Special internal returns from the match() function. Make them sufficiently +negative to avoid the external error codes. */ + +#define MATCH_COMMIT (-999) +#define MATCH_PRUNE (-998) +#define MATCH_SKIP (-997) +#define MATCH_THEN (-996) + +/* Maximum number of ints of offset to save on the stack for recursive calls. +If the offset vector is bigger, malloc is used. This should be a multiple of 3, +because the offset vector is always a multiple of 3 long. */ + +#define REC_STACK_SAVE_MAX 30 + +/* Min and max values for the common repeats; for the maxima, 0 => infinity */ + +static const char rep_min[] = { 0, 0, 1, 1, 0, 0 }; +static const char rep_max[] = { 0, 0, 0, 0, 1, 1 }; + + + +#ifdef DEBUG +/************************************************* +* Debugging function to print chars * +*************************************************/ + +/* Print a sequence of chars in printable format, stopping at the end of the +subject if the requested. + +Arguments: + p points to characters + length number to print + is_subject TRUE if printing from within md->start_subject + md pointer to matching data block, if is_subject is TRUE + +Returns: nothing +*/ + +static void +pchars(const uschar *p, int length, BOOL is_subject, match_data *md) +{ +unsigned int c; +if (is_subject && length > md->end_subject - p) length = md->end_subject - p; +while (length-- > 0) + if (isprint(c = *(p++))) printf("%c", c); else printf("\\x%02x", c); +} +#endif + + + +/************************************************* +* Match a back-reference * +*************************************************/ + +/* If a back reference hasn't been set, the length that is passed is greater +than the number of characters left in the string, so the match fails. + +Arguments: + offset index into the offset vector + eptr points into the subject + length length to be matched + md points to match data block + ims the ims flags + +Returns: TRUE if matched +*/ + +static BOOL +match_ref(int offset, register USPTR eptr, int length, match_data *md, + unsigned long int ims) +{ +USPTR p = md->start_subject + md->offset_vector[offset]; + +#ifdef DEBUG +if (eptr >= md->end_subject) + printf("matching subject <null>"); +else + { + printf("matching subject "); + pchars(eptr, length, TRUE, md); + } +printf(" against backref "); +pchars(p, length, FALSE, md); +printf("\n"); +#endif + +/* Always fail if not enough characters left */ + +if (length > md->end_subject - eptr) return FALSE; + +/* Separate the caselesss case for speed */ + +if ((ims & PCRE_CASELESS) != 0) + { + while (length-- > 0) + if (md->lcc[*p++] != md->lcc[*eptr++]) return FALSE; + } +else + { while (length-- > 0) if (*p++ != *eptr++) return FALSE; } + +return TRUE; +} + + + +/*************************************************************************** +**************************************************************************** + RECURSION IN THE match() FUNCTION + +The match() function is highly recursive, though not every recursive call +increases the recursive depth. Nevertheless, some regular expressions can cause +it to recurse to a great depth. I was writing for Unix, so I just let it call +itself recursively. This uses the stack for saving everything that has to be +saved for a recursive call. On Unix, the stack can be large, and this works +fine. + +It turns out that on some non-Unix-like systems there are problems with +programs that use a lot of stack. (This despite the fact that every last chip +has oodles of memory these days, and techniques for extending the stack have +been known for decades.) So.... + +There is a fudge, triggered by defining NO_RECURSE, which avoids recursive +calls by keeping local variables that need to be preserved in blocks of memory +obtained from malloc() instead instead of on the stack. Macros are used to +achieve this so that the actual code doesn't look very different to what it +always used to. + +The original heap-recursive code used longjmp(). However, it seems that this +can be very slow on some operating systems. Following a suggestion from Stan +Switzer, the use of longjmp() has been abolished, at the cost of having to +provide a unique number for each call to RMATCH. There is no way of generating +a sequence of numbers at compile time in C. I have given them names, to make +them stand out more clearly. + +Crude tests on x86 Linux show a small speedup of around 5-8%. However, on +FreeBSD, avoiding longjmp() more than halves the time taken to run the standard +tests. Furthermore, not using longjmp() means that local dynamic variables +don't have indeterminate values; this has meant that the frame size can be +reduced because the result can be "passed back" by straight setting of the +variable instead of being passed in the frame. +**************************************************************************** +***************************************************************************/ + +/* Numbers for RMATCH calls. When this list is changed, the code at HEAP_RETURN +below must be updated in sync. */ + +enum { RM1=1, RM2, RM3, RM4, RM5, RM6, RM7, RM8, RM9, RM10, + RM11, RM12, RM13, RM14, RM15, RM16, RM17, RM18, RM19, RM20, + RM21, RM22, RM23, RM24, RM25, RM26, RM27, RM28, RM29, RM30, + RM31, RM32, RM33, RM34, RM35, RM36, RM37, RM38, RM39, RM40, + RM41, RM42, RM43, RM44, RM45, RM46, RM47, RM48, RM49, RM50, + RM51, RM52, RM53, RM54 }; + +/* These versions of the macros use the stack, as normal. There are debugging +versions and production versions. Note that the "rw" argument of RMATCH isn't +actuall used in this definition. */ + +#ifndef NO_RECURSE +#define REGISTER register + +#ifdef DEBUG +#define RMATCH(ra,rb,rc,rd,re,rf,rg,rw) \ + { \ + printf("match() called in line %d\n", __LINE__); \ + rrc = match(ra,rb,mstart,rc,rd,re,rf,rg,rdepth+1); \ + printf("to line %d\n", __LINE__); \ + } +#define RRETURN(ra) \ + { \ + printf("match() returned %d from line %d ", ra, __LINE__); \ + return ra; \ + } +#else +#define RMATCH(ra,rb,rc,rd,re,rf,rg,rw) \ + rrc = match(ra,rb,mstart,rc,rd,re,rf,rg,rdepth+1) +#define RRETURN(ra) return ra +#endif + +#else + + +/* These versions of the macros manage a private stack on the heap. Note that +the "rd" argument of RMATCH isn't actually used in this definition. It's the md +argument of match(), which never changes. */ + +#define REGISTER + +#define RMATCH(ra,rb,rc,rd,re,rf,rg,rw)\ + {\ + heapframe *newframe = (pcre_stack_malloc)(sizeof(heapframe));\ + frame->Xwhere = rw; \ + newframe->Xeptr = ra;\ + newframe->Xecode = rb;\ + newframe->Xmstart = mstart;\ + newframe->Xoffset_top = rc;\ + newframe->Xims = re;\ + newframe->Xeptrb = rf;\ + newframe->Xflags = rg;\ + newframe->Xrdepth = frame->Xrdepth + 1;\ + newframe->Xprevframe = frame;\ + frame = newframe;\ + DPRINTF(("restarting from line %d\n", __LINE__));\ + goto HEAP_RECURSE;\ + L_##rw:\ + DPRINTF(("jumped back to line %d\n", __LINE__));\ + } + +#define RRETURN(ra)\ + {\ + heapframe *newframe = frame;\ + frame = newframe->Xprevframe;\ + (pcre_stack_free)(newframe);\ + if (frame != NULL)\ + {\ + rrc = ra;\ + goto HEAP_RETURN;\ + }\ + return ra;\ + } + + +/* Structure for remembering the local variables in a private frame */ + +typedef struct heapframe { + struct heapframe *Xprevframe; + + /* Function arguments that may change */ + + const uschar *Xeptr; + const uschar *Xecode; + const uschar *Xmstart; + int Xoffset_top; + long int Xims; + eptrblock *Xeptrb; + int Xflags; + unsigned int Xrdepth; + + /* Function local variables */ + + const uschar *Xcallpat; + const uschar *Xcharptr; + const uschar *Xdata; + const uschar *Xnext; + const uschar *Xpp; + const uschar *Xprev; + const uschar *Xsaved_eptr; + + recursion_info Xnew_recursive; + + BOOL Xcur_is_word; + BOOL Xcondition; + BOOL Xprev_is_word; + + unsigned long int Xoriginal_ims; + +#ifdef SUPPORT_UCP + int Xprop_type; + int Xprop_value; + int Xprop_fail_result; + int Xprop_category; + int Xprop_chartype; + int Xprop_script; + int Xoclength; + uschar Xocchars[8]; +#endif + + int Xctype; + unsigned int Xfc; + int Xfi; + int Xlength; + int Xmax; + int Xmin; + int Xnumber; + int Xoffset; + int Xop; + int Xsave_capture_last; + int Xsave_offset1, Xsave_offset2, Xsave_offset3; + int Xstacksave[REC_STACK_SAVE_MAX]; + + eptrblock Xnewptrb; + + /* Where to jump back to */ + + int Xwhere; + +} heapframe; + +#endif + + +/*************************************************************************** +***************************************************************************/ + + + +/************************************************* +* Match from current position * +*************************************************/ + +/* This function is called recursively in many circumstances. Whenever it +returns a negative (error) response, the outer incarnation must also return the +same response. + +Performance note: It might be tempting to extract commonly used fields from the +md structure (e.g. utf8, end_subject) into individual variables to improve +performance. Tests using gcc on a SPARC disproved this; in the first case, it +made performance worse. + +Arguments: + eptr pointer to current character in subject + ecode pointer to current position in compiled code + mstart pointer to the current match start position (can be modified + by encountering \K) + offset_top current top pointer + md pointer to "static" info for the match + ims current /i, /m, and /s options + eptrb pointer to chain of blocks containing eptr at start of + brackets - for testing for empty matches + flags can contain + match_condassert - this is an assertion condition + match_cbegroup - this is the start of an unlimited repeat + group that can match an empty string + rdepth the recursion depth + +Returns: MATCH_MATCH if matched ) these values are >= 0 + MATCH_NOMATCH if failed to match ) + a negative PCRE_ERROR_xxx value if aborted by an error condition + (e.g. stopped by repeated call or recursion limit) +*/ + +static int +match(REGISTER USPTR eptr, REGISTER const uschar *ecode, const uschar *mstart, + int offset_top, match_data *md, unsigned long int ims, eptrblock *eptrb, + int flags, unsigned int rdepth) +{ +/* These variables do not need to be preserved over recursion in this function, +so they can be ordinary variables in all cases. Mark some of them with +"register" because they are used a lot in loops. */ + +register int rrc; /* Returns from recursive calls */ +register int i; /* Used for loops not involving calls to RMATCH() */ +register unsigned int c; /* Character values not kept over RMATCH() calls */ +register BOOL utf8; /* Local copy of UTF-8 flag for speed */ + +BOOL minimize, possessive; /* Quantifier options */ + +/* When recursion is not being used, all "local" variables that have to be +preserved over calls to RMATCH() are part of a "frame" which is obtained from +heap storage. Set up the top-level frame here; others are obtained from the +heap whenever RMATCH() does a "recursion". See the macro definitions above. */ + +#ifdef NO_RECURSE +heapframe *frame = (pcre_stack_malloc)(sizeof(heapframe)); +frame->Xprevframe = NULL; /* Marks the top level */ + +/* Copy in the original argument variables */ + +frame->Xeptr = eptr; +frame->Xecode = ecode; +frame->Xmstart = mstart; +frame->Xoffset_top = offset_top; +frame->Xims = ims; +frame->Xeptrb = eptrb; +frame->Xflags = flags; +frame->Xrdepth = rdepth; + +/* This is where control jumps back to to effect "recursion" */ + +HEAP_RECURSE: + +/* Macros make the argument variables come from the current frame */ + +#define eptr frame->Xeptr +#define ecode frame->Xecode +#define mstart frame->Xmstart +#define offset_top frame->Xoffset_top +#define ims frame->Xims +#define eptrb frame->Xeptrb +#define flags frame->Xflags +#define rdepth frame->Xrdepth + +/* Ditto for the local variables */ + +#ifdef SUPPORT_UTF8 +#define charptr frame->Xcharptr +#endif +#define callpat frame->Xcallpat +#define data frame->Xdata +#define next frame->Xnext +#define pp frame->Xpp +#define prev frame->Xprev +#define saved_eptr frame->Xsaved_eptr + +#define new_recursive frame->Xnew_recursive + +#define cur_is_word frame->Xcur_is_word +#define condition frame->Xcondition +#define prev_is_word frame->Xprev_is_word + +#define original_ims frame->Xoriginal_ims + +#ifdef SUPPORT_UCP +#define prop_type frame->Xprop_type +#define prop_value frame->Xprop_value +#define prop_fail_result frame->Xprop_fail_result +#define prop_category frame->Xprop_category +#define prop_chartype frame->Xprop_chartype +#define prop_script frame->Xprop_script +#define oclength frame->Xoclength +#define occhars frame->Xocchars +#endif + +#define ctype frame->Xctype +#define fc frame->Xfc +#define fi frame->Xfi +#define length frame->Xlength +#define max frame->Xmax +#define min frame->Xmin +#define number frame->Xnumber +#define offset frame->Xoffset +#define op frame->Xop +#define save_capture_last frame->Xsave_capture_last +#define save_offset1 frame->Xsave_offset1 +#define save_offset2 frame->Xsave_offset2 +#define save_offset3 frame->Xsave_offset3 +#define stacksave frame->Xstacksave + +#define newptrb frame->Xnewptrb + +/* When recursion is being used, local variables are allocated on the stack and +get preserved during recursion in the normal way. In this environment, fi and +i, and fc and c, can be the same variables. */ + +#else /* NO_RECURSE not defined */ +#define fi i +#define fc c + + +#ifdef SUPPORT_UTF8 /* Many of these variables are used only */ +const uschar *charptr; /* in small blocks of the code. My normal */ +#endif /* style of coding would have declared */ +const uschar *callpat; /* them within each of those blocks. */ +const uschar *data; /* However, in order to accommodate the */ +const uschar *next; /* version of this code that uses an */ +USPTR pp; /* external "stack" implemented on the */ +const uschar *prev; /* heap, it is easier to declare them all */ +USPTR saved_eptr; /* here, so the declarations can be cut */ + /* out in a block. The only declarations */ +recursion_info new_recursive; /* within blocks below are for variables */ + /* that do not have to be preserved over */ +BOOL cur_is_word; /* a recursive call to RMATCH(). */ +BOOL condition; +BOOL prev_is_word; + +unsigned long int original_ims; + +#ifdef SUPPORT_UCP +int prop_type; +int prop_value; +int prop_fail_result; +int prop_category; +int prop_chartype; +int prop_script; +int oclength; +uschar occhars[8]; +#endif + +int ctype; +int length; +int max; +int min; +int number; +int offset; +int op; +int save_capture_last; +int save_offset1, save_offset2, save_offset3; +int stacksave[REC_STACK_SAVE_MAX]; + +eptrblock newptrb; +#endif /* NO_RECURSE */ + +/* These statements are here to stop the compiler complaining about unitialized +variables. */ + +#ifdef SUPPORT_UCP +prop_value = 0; +prop_fail_result = 0; +#endif + + +/* This label is used for tail recursion, which is used in a few cases even +when NO_RECURSE is not defined, in order to reduce the amount of stack that is +used. Thanks to Ian Taylor for noticing this possibility and sending the +original patch. */ + +TAIL_RECURSE: + +/* OK, now we can get on with the real code of the function. Recursive calls +are specified by the macro RMATCH and RRETURN is used to return. When +NO_RECURSE is *not* defined, these just turn into a recursive call to match() +and a "return", respectively (possibly with some debugging if DEBUG is +defined). However, RMATCH isn't like a function call because it's quite a +complicated macro. It has to be used in one particular way. This shouldn't, +however, impact performance when true recursion is being used. */ + +#ifdef SUPPORT_UTF8 +utf8 = md->utf8; /* Local copy of the flag */ +#else +utf8 = FALSE; +#endif + +/* First check that we haven't called match() too many times, or that we +haven't exceeded the recursive call limit. */ + +if (md->match_call_count++ >= md->match_limit) RRETURN(PCRE_ERROR_MATCHLIMIT); +if (rdepth >= md->match_limit_recursion) RRETURN(PCRE_ERROR_RECURSIONLIMIT); + +original_ims = ims; /* Save for resetting on ')' */ + +/* At the start of a group with an unlimited repeat that may match an empty +string, the match_cbegroup flag is set. When this is the case, add the current +subject pointer to the chain of such remembered pointers, to be checked when we +hit the closing ket, in order to break infinite loops that match no characters. +When match() is called in other circumstances, don't add to the chain. The +match_cbegroup flag must NOT be used with tail recursion, because the memory +block that is used is on the stack, so a new one may be required for each +match(). */ + +if ((flags & match_cbegroup) != 0) + { + newptrb.epb_saved_eptr = eptr; + newptrb.epb_prev = eptrb; + eptrb = &newptrb; + } + +/* Now start processing the opcodes. */ + +for (;;) + { + minimize = possessive = FALSE; + op = *ecode; + + /* For partial matching, remember if we ever hit the end of the subject after + matching at least one subject character. */ + + if (md->partial && + eptr >= md->end_subject && + eptr > mstart) + md->hitend = TRUE; + + switch(op) + { + case OP_FAIL: + RRETURN(MATCH_NOMATCH); + + case OP_PRUNE: + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, + ims, eptrb, flags, RM51); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_PRUNE); + + case OP_COMMIT: + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, + ims, eptrb, flags, RM52); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_COMMIT); + + case OP_SKIP: + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, + ims, eptrb, flags, RM53); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + md->start_match_ptr = eptr; /* Pass back current position */ + RRETURN(MATCH_SKIP); + + case OP_THEN: + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, + ims, eptrb, flags, RM54); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_THEN); + + /* Handle a capturing bracket. If there is space in the offset vector, save + the current subject position in the working slot at the top of the vector. + We mustn't change the current values of the data slot, because they may be + set from a previous iteration of this group, and be referred to by a + reference inside the group. + + If the bracket fails to match, we need to restore this value and also the + values of the final offsets, in case they were set by a previous iteration + of the same bracket. + + If there isn't enough space in the offset vector, treat this as if it were + a non-capturing bracket. Don't worry about setting the flag for the error + case here; that is handled in the code for KET. */ + + case OP_CBRA: + case OP_SCBRA: + number = GET2(ecode, 1+LINK_SIZE); + offset = number << 1; + +#ifdef DEBUG + printf("start bracket %d\n", number); + printf("subject="); + pchars(eptr, 16, TRUE, md); + printf("\n"); +#endif + + if (offset < md->offset_max) + { + save_offset1 = md->offset_vector[offset]; + save_offset2 = md->offset_vector[offset+1]; + save_offset3 = md->offset_vector[md->offset_end - number]; + save_capture_last = md->capture_last; + + DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3)); + md->offset_vector[md->offset_end - number] = eptr - md->start_subject; + + flags = (op == OP_SCBRA)? match_cbegroup : 0; + do + { + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, + ims, eptrb, flags, RM1); + if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc); + md->capture_last = save_capture_last; + ecode += GET(ecode, 1); + } + while (*ecode == OP_ALT); + + DPRINTF(("bracket %d failed\n", number)); + + md->offset_vector[offset] = save_offset1; + md->offset_vector[offset+1] = save_offset2; + md->offset_vector[md->offset_end - number] = save_offset3; + + RRETURN(MATCH_NOMATCH); + } + + /* FALL THROUGH ... Insufficient room for saving captured contents. Treat + as a non-capturing bracket. */ + + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + + DPRINTF(("insufficient capture room: treat as non-capturing\n")); + + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + + /* Non-capturing bracket. Loop for all the alternatives. When we get to the + final alternative within the brackets, we would return the result of a + recursive call to match() whatever happened. We can reduce stack usage by + turning this into a tail recursion, except in the case when match_cbegroup + is set.*/ + + case OP_BRA: + case OP_SBRA: + DPRINTF(("start non-capturing bracket\n")); + flags = (op >= OP_SBRA)? match_cbegroup : 0; + for (;;) + { + if (ecode[GET(ecode, 1)] != OP_ALT) /* Final alternative */ + { + if (flags == 0) /* Not a possibly empty group */ + { + ecode += _pcre_OP_lengths[*ecode]; + DPRINTF(("bracket 0 tail recursion\n")); + goto TAIL_RECURSE; + } + + /* Possibly empty group; can't use tail recursion. */ + + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, ims, + eptrb, flags, RM48); + RRETURN(rrc); + } + + /* For non-final alternatives, continue the loop for a NOMATCH result; + otherwise return. */ + + RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, ims, + eptrb, flags, RM2); + if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc); + ecode += GET(ecode, 1); + } + /* Control never reaches here. */ + + /* Conditional group: compilation checked that there are no more than + two branches. If the condition is false, skipping the first branch takes us + past the end if there is only one branch, but that's OK because that is + exactly what going to the ket would do. As there is only one branch to be + obeyed, we can use tail recursion to avoid using another stack frame. */ + + case OP_COND: + case OP_SCOND: + if (ecode[LINK_SIZE+1] == OP_RREF) /* Recursion test */ + { + offset = GET2(ecode, LINK_SIZE + 2); /* Recursion group number*/ + condition = md->recursive != NULL && + (offset == RREF_ANY || offset == md->recursive->group_num); + ecode += condition? 3 : GET(ecode, 1); + } + + else if (ecode[LINK_SIZE+1] == OP_CREF) /* Group used test */ + { + offset = GET2(ecode, LINK_SIZE+2) << 1; /* Doubled ref number */ + condition = offset < offset_top && md->offset_vector[offset] >= 0; + ecode += condition? 3 : GET(ecode, 1); + } + + else if (ecode[LINK_SIZE+1] == OP_DEF) /* DEFINE - always false */ + { + condition = FALSE; + ecode += GET(ecode, 1); + } + + /* The condition is an assertion. Call match() to evaluate it - setting + the final argument match_condassert causes it to stop at the end of an + assertion. */ + + else + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, + match_condassert, RM3); + if (rrc == MATCH_MATCH) + { + condition = TRUE; + ecode += 1 + LINK_SIZE + GET(ecode, LINK_SIZE + 2); + while (*ecode == OP_ALT) ecode += GET(ecode, 1); + } + else if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) + { + RRETURN(rrc); /* Need braces because of following else */ + } + else + { + condition = FALSE; + ecode += GET(ecode, 1); + } + } + + /* We are now at the branch that is to be obeyed. As there is only one, + we can use tail recursion to avoid using another stack frame, except when + match_cbegroup is required for an unlimited repeat of a possibly empty + group. If the second alternative doesn't exist, we can just plough on. */ + + if (condition || *ecode == OP_ALT) + { + ecode += 1 + LINK_SIZE; + if (op == OP_SCOND) /* Possibly empty group */ + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, match_cbegroup, RM49); + RRETURN(rrc); + } + else /* Group must match something */ + { + flags = 0; + goto TAIL_RECURSE; + } + } + else /* Condition false & no 2nd alternative */ + { + ecode += 1 + LINK_SIZE; + } + break; + + + /* End of the pattern, either real or forced. If we are in a top-level + recursion, we should restore the offsets appropriately and continue from + after the call. */ + + case OP_ACCEPT: + case OP_END: + if (md->recursive != NULL && md->recursive->group_num == 0) + { + recursion_info *rec = md->recursive; + DPRINTF(("End of pattern in a (?0) recursion\n")); + md->recursive = rec->prevrec; + memmove(md->offset_vector, rec->offset_save, + rec->saved_max * sizeof(int)); + mstart = rec->save_start; + ims = original_ims; + ecode = rec->after_call; + break; + } + + /* Otherwise, if PCRE_NOTEMPTY is set, fail if we have matched an empty + string - backtracking will then try other alternatives, if any. */ + + if (md->notempty && eptr == mstart) RRETURN(MATCH_NOMATCH); + md->end_match_ptr = eptr; /* Record where we ended */ + md->end_offset_top = offset_top; /* and how many extracts were taken */ + md->start_match_ptr = mstart; /* and the start (\K can modify) */ + RRETURN(MATCH_MATCH); + + /* Change option settings */ + + case OP_OPT: + ims = ecode[1]; + ecode += 2; + DPRINTF(("ims set to %02lx\n", ims)); + break; + + /* Assertion brackets. Check the alternative branches in turn - the + matching won't pass the KET for an assertion. If any one branch matches, + the assertion is true. Lookbehind assertions have an OP_REVERSE item at the + start of each branch to move the current point backwards, so the code at + this level is identical to the lookahead case. */ + + case OP_ASSERT: + case OP_ASSERTBACK: + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, 0, + RM4); + if (rrc == MATCH_MATCH) break; + if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc); + ecode += GET(ecode, 1); + } + while (*ecode == OP_ALT); + if (*ecode == OP_KET) RRETURN(MATCH_NOMATCH); + + /* If checking an assertion for a condition, return MATCH_MATCH. */ + + if ((flags & match_condassert) != 0) RRETURN(MATCH_MATCH); + + /* Continue from after the assertion, updating the offsets high water + mark, since extracts may have been taken during the assertion. */ + + do ecode += GET(ecode,1); while (*ecode == OP_ALT); + ecode += 1 + LINK_SIZE; + offset_top = md->end_offset_top; + continue; + + /* Negative assertion: all branches must fail to match */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK_NOT: + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL, 0, + RM5); + if (rrc == MATCH_MATCH) RRETURN(MATCH_NOMATCH); + if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc); + ecode += GET(ecode,1); + } + while (*ecode == OP_ALT); + + if ((flags & match_condassert) != 0) RRETURN(MATCH_MATCH); + + ecode += 1 + LINK_SIZE; + continue; + + /* Move the subject pointer back. This occurs only at the start of + each branch of a lookbehind assertion. If we are too close to the start to + move back, this match function fails. When working with UTF-8 we move + back a number of characters, not bytes. */ + + case OP_REVERSE: +#ifdef SUPPORT_UTF8 + if (utf8) + { + i = GET(ecode, 1); + while (i-- > 0) + { + eptr--; + if (eptr < md->start_subject) RRETURN(MATCH_NOMATCH); + BACKCHAR(eptr); + } + } + else +#endif + + /* No UTF-8 support, or not in UTF-8 mode: count is byte count */ + + { + eptr -= GET(ecode, 1); + if (eptr < md->start_subject) RRETURN(MATCH_NOMATCH); + } + + /* Skip to next op code */ + + ecode += 1 + LINK_SIZE; + break; + + /* The callout item calls an external function, if one is provided, passing + details of the match so far. This is mainly for debugging, though the + function is able to force a failure. */ + + case OP_CALLOUT: + if (pcre_callout != NULL) + { + pcre_callout_block cb; + cb.version = 1; /* Version 1 of the callout block */ + cb.callout_number = ecode[1]; + cb.offset_vector = md->offset_vector; + cb.subject = (PCRE_SPTR)md->start_subject; + cb.subject_length = md->end_subject - md->start_subject; + cb.start_match = mstart - md->start_subject; + cb.current_position = eptr - md->start_subject; + cb.pattern_position = GET(ecode, 2); + cb.next_item_length = GET(ecode, 2 + LINK_SIZE); + cb.capture_top = offset_top/2; + cb.capture_last = md->capture_last; + cb.callout_data = md->callout_data; + if ((rrc = (*pcre_callout)(&cb)) > 0) RRETURN(MATCH_NOMATCH); + if (rrc < 0) RRETURN(rrc); + } + ecode += 2 + 2*LINK_SIZE; + break; + + /* Recursion either matches the current regex, or some subexpression. The + offset data is the offset to the starting bracket from the start of the + whole pattern. (This is so that it works from duplicated subpatterns.) + + If there are any capturing brackets started but not finished, we have to + save their starting points and reinstate them after the recursion. However, + we don't know how many such there are (offset_top records the completed + total) so we just have to save all the potential data. There may be up to + 65535 such values, which is too large to put on the stack, but using malloc + for small numbers seems expensive. As a compromise, the stack is used when + there are no more than REC_STACK_SAVE_MAX values to store; otherwise malloc + is used. A problem is what to do if the malloc fails ... there is no way of + returning to the top level with an error. Save the top REC_STACK_SAVE_MAX + values on the stack, and accept that the rest may be wrong. + + There are also other values that have to be saved. We use a chained + sequence of blocks that actually live on the stack. Thanks to Robin Houston + for the original version of this logic. */ + + case OP_RECURSE: + { + callpat = md->start_code + GET(ecode, 1); + new_recursive.group_num = (callpat == md->start_code)? 0 : + GET2(callpat, 1 + LINK_SIZE); + + /* Add to "recursing stack" */ + + new_recursive.prevrec = md->recursive; + md->recursive = &new_recursive; + + /* Find where to continue from afterwards */ + + ecode += 1 + LINK_SIZE; + new_recursive.after_call = ecode; + + /* Now save the offset data. */ + + new_recursive.saved_max = md->offset_end; + if (new_recursive.saved_max <= REC_STACK_SAVE_MAX) + new_recursive.offset_save = stacksave; + else + { + new_recursive.offset_save = + (int *)(pcre_malloc)(new_recursive.saved_max * sizeof(int)); + if (new_recursive.offset_save == NULL) RRETURN(PCRE_ERROR_NOMEMORY); + } + + memcpy(new_recursive.offset_save, md->offset_vector, + new_recursive.saved_max * sizeof(int)); + new_recursive.save_start = mstart; + mstart = eptr; + + /* OK, now we can do the recursion. For each top-level alternative we + restore the offset and recursion data. */ + + DPRINTF(("Recursing into group %d\n", new_recursive.group_num)); + flags = (*callpat >= OP_SBRA)? match_cbegroup : 0; + do + { + RMATCH(eptr, callpat + _pcre_OP_lengths[*callpat], offset_top, + md, ims, eptrb, flags, RM6); + if (rrc == MATCH_MATCH) + { + DPRINTF(("Recursion matched\n")); + md->recursive = new_recursive.prevrec; + if (new_recursive.offset_save != stacksave) + (pcre_free)(new_recursive.offset_save); + RRETURN(MATCH_MATCH); + } + else if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) + { + DPRINTF(("Recursion gave error %d\n", rrc)); + RRETURN(rrc); + } + + md->recursive = &new_recursive; + memcpy(md->offset_vector, new_recursive.offset_save, + new_recursive.saved_max * sizeof(int)); + callpat += GET(callpat, 1); + } + while (*callpat == OP_ALT); + + DPRINTF(("Recursion didn't match\n")); + md->recursive = new_recursive.prevrec; + if (new_recursive.offset_save != stacksave) + (pcre_free)(new_recursive.offset_save); + RRETURN(MATCH_NOMATCH); + } + /* Control never reaches here */ + + /* "Once" brackets are like assertion brackets except that after a match, + the point in the subject string is not moved back. Thus there can never be + a move back into the brackets. Friedl calls these "atomic" subpatterns. + Check the alternative branches in turn - the matching won't pass the KET + for this kind of subpattern. If any one branch matches, we carry on as at + the end of a normal bracket, leaving the subject pointer. */ + + case OP_ONCE: + prev = ecode; + saved_eptr = eptr; + + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, 0, RM7); + if (rrc == MATCH_MATCH) break; + if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc); + ecode += GET(ecode,1); + } + while (*ecode == OP_ALT); + + /* If hit the end of the group (which could be repeated), fail */ + + if (*ecode != OP_ONCE && *ecode != OP_ALT) RRETURN(MATCH_NOMATCH); + + /* Continue as from after the assertion, updating the offsets high water + mark, since extracts may have been taken. */ + + do ecode += GET(ecode, 1); while (*ecode == OP_ALT); + + offset_top = md->end_offset_top; + eptr = md->end_match_ptr; + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 1+LINK_SIZE; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. The second "call" of match() + uses tail recursion, to avoid using another stack frame. We need to reset + any options that changed within the bracket before re-running it, so + check the next opcode. */ + + if (ecode[1+LINK_SIZE] == OP_OPT) + { + ims = (ims & ~PCRE_IMS) | ecode[4]; + DPRINTF(("ims set to %02lx at group repeat\n", ims)); + } + + if (*ecode == OP_KETRMIN) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, 0, RM8); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode = prev; + flags = 0; + goto TAIL_RECURSE; + } + else /* OP_KETRMAX */ + { + RMATCH(eptr, prev, offset_top, md, ims, eptrb, match_cbegroup, RM9); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += 1 + LINK_SIZE; + flags = 0; + goto TAIL_RECURSE; + } + /* Control never gets here */ + + /* An alternation is the end of a branch; scan along to find the end of the + bracketed group and go to there. */ + + case OP_ALT: + do ecode += GET(ecode,1); while (*ecode == OP_ALT); + break; + + /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating + that it may occur zero times. It may repeat infinitely, or not at all - + i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper + repeat limits are compiled as a number of copies, with the optional ones + preceded by BRAZERO or BRAMINZERO. */ + + case OP_BRAZERO: + { + next = ecode+1; + RMATCH(eptr, next, offset_top, md, ims, eptrb, 0, RM10); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + do next += GET(next,1); while (*next == OP_ALT); + ecode = next + 1 + LINK_SIZE; + } + break; + + case OP_BRAMINZERO: + { + next = ecode+1; + do next += GET(next, 1); while (*next == OP_ALT); + RMATCH(eptr, next + 1+LINK_SIZE, offset_top, md, ims, eptrb, 0, RM11); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode++; + } + break; + + /* End of a group, repeated or non-repeating. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + prev = ecode - GET(ecode, 1); + + /* If this was a group that remembered the subject start, in order to break + infinite repeats of empty string matches, retrieve the subject start from + the chain. Otherwise, set it NULL. */ + + if (*prev >= OP_SBRA) + { + saved_eptr = eptrb->epb_saved_eptr; /* Value at start of group */ + eptrb = eptrb->epb_prev; /* Backup to previous group */ + } + else saved_eptr = NULL; + + /* If we are at the end of an assertion group, stop matching and return + MATCH_MATCH, but record the current high water mark for use by positive + assertions. Do this also for the "once" (atomic) groups. */ + + if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT || + *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT || + *prev == OP_ONCE) + { + md->end_match_ptr = eptr; /* For ONCE */ + md->end_offset_top = offset_top; + RRETURN(MATCH_MATCH); + } + + /* For capturing groups we have to check the group number back at the start + and if necessary complete handling an extraction by setting the offsets and + bumping the high water mark. Note that whole-pattern recursion is coded as + a recurse into group 0, so it won't be picked up here. Instead, we catch it + when the OP_END is reached. Other recursion is handled here. */ + + if (*prev == OP_CBRA || *prev == OP_SCBRA) + { + number = GET2(prev, 1+LINK_SIZE); + offset = number << 1; + +#ifdef DEBUG + printf("end bracket %d", number); + printf("\n"); +#endif + + md->capture_last = number; + if (offset >= md->offset_max) md->offset_overflow = TRUE; else + { + md->offset_vector[offset] = + md->offset_vector[md->offset_end - number]; + md->offset_vector[offset+1] = eptr - md->start_subject; + if (offset_top <= offset) offset_top = offset + 2; + } + + /* Handle a recursively called group. Restore the offsets + appropriately and continue from after the call. */ + + if (md->recursive != NULL && md->recursive->group_num == number) + { + recursion_info *rec = md->recursive; + DPRINTF(("Recursion (%d) succeeded - continuing\n", number)); + md->recursive = rec->prevrec; + mstart = rec->save_start; + memcpy(md->offset_vector, rec->offset_save, + rec->saved_max * sizeof(int)); + ecode = rec->after_call; + ims = original_ims; + break; + } + } + + /* For both capturing and non-capturing groups, reset the value of the ims + flags, in case they got changed during the group. */ + + ims = original_ims; + DPRINTF(("ims reset to %02lx\n", ims)); + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 1 + LINK_SIZE; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. In the second case, we can use + tail recursion to avoid using another stack frame, unless we have an + unlimited repeat of a group that can match an empty string. */ + + flags = (*prev >= OP_SBRA)? match_cbegroup : 0; + + if (*ecode == OP_KETRMIN) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb, 0, RM12); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (flags != 0) /* Could match an empty string */ + { + RMATCH(eptr, prev, offset_top, md, ims, eptrb, flags, RM50); + RRETURN(rrc); + } + ecode = prev; + goto TAIL_RECURSE; + } + else /* OP_KETRMAX */ + { + RMATCH(eptr, prev, offset_top, md, ims, eptrb, flags, RM13); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += 1 + LINK_SIZE; + flags = 0; + goto TAIL_RECURSE; + } + /* Control never gets here */ + + /* Start of subject unless notbol, or after internal newline if multiline */ + + case OP_CIRC: + if (md->notbol && eptr == md->start_subject) RRETURN(MATCH_NOMATCH); + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr != md->start_subject && + (eptr == md->end_subject || !WAS_NEWLINE(eptr))) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + } + /* ... else fall through */ + + /* Start of subject assertion */ + + case OP_SOD: + if (eptr != md->start_subject) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Start of match assertion */ + + case OP_SOM: + if (eptr != md->start_subject + md->start_offset) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Reset the start of match point */ + + case OP_SET_SOM: + mstart = eptr; + ecode++; + break; + + /* Assert before internal newline if multiline, or before a terminating + newline unless endonly is set, else end of subject unless noteol is set. */ + + case OP_DOLL: + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr < md->end_subject) + { if (!IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); } + else + { if (md->noteol) RRETURN(MATCH_NOMATCH); } + ecode++; + break; + } + else + { + if (md->noteol) RRETURN(MATCH_NOMATCH); + if (!md->endonly) + { + if (eptr != md->end_subject && + (!IS_NEWLINE(eptr) || eptr != md->end_subject - md->nllen)) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + } + } + /* ... else fall through for endonly */ + + /* End of subject assertion (\z) */ + + case OP_EOD: + if (eptr < md->end_subject) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* End of subject or ending \n assertion (\Z) */ + + case OP_EODN: + if (eptr != md->end_subject && + (!IS_NEWLINE(eptr) || eptr != md->end_subject - md->nllen)) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Word boundary assertions */ + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + { + + /* Find out if the previous and current characters are "word" characters. + It takes a bit more work in UTF-8 mode. Characters > 255 are assumed to + be "non-word" characters. */ + +#ifdef SUPPORT_UTF8 + if (utf8) + { + if (eptr == md->start_subject) prev_is_word = FALSE; else + { + const uschar *lastptr = eptr - 1; + while((*lastptr & 0xc0) == 0x80) lastptr--; + GETCHAR(c, lastptr); + prev_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0; + } + if (eptr >= md->end_subject) cur_is_word = FALSE; else + { + GETCHAR(c, eptr); + cur_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0; + } + } + else +#endif + + /* More streamlined when not in UTF-8 mode */ + + { + prev_is_word = (eptr != md->start_subject) && + ((md->ctypes[eptr[-1]] & ctype_word) != 0); + cur_is_word = (eptr < md->end_subject) && + ((md->ctypes[*eptr] & ctype_word) != 0); + } + + /* Now see if the situation is what we want */ + + if ((*ecode++ == OP_WORD_BOUNDARY)? + cur_is_word == prev_is_word : cur_is_word != prev_is_word) + RRETURN(MATCH_NOMATCH); + } + break; + + /* Match a single character type; inline for speed */ + + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0) + { + if (IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); + } + if (eptr++ >= md->end_subject) RRETURN(MATCH_NOMATCH); + if (utf8) + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + ecode++; + break; + + /* Match a single byte, even in UTF-8 mode. This opcode really does match + any byte, even newline, independent of the setting of PCRE_DOTALL. */ + + case OP_ANYBYTE: + if (eptr++ >= md->end_subject) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_NOT_DIGIT: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c < 256 && +#endif + (md->ctypes[c] & ctype_digit) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_DIGIT: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c >= 256 || +#endif + (md->ctypes[c] & ctype_digit) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_NOT_WHITESPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c < 256 && +#endif + (md->ctypes[c] & ctype_space) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_WHITESPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c >= 256 || +#endif + (md->ctypes[c] & ctype_space) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_NOT_WORDCHAR: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c < 256 && +#endif + (md->ctypes[c] & ctype_word) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_WORDCHAR: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_UTF8 + c >= 256 || +#endif + (md->ctypes[c] & ctype_word) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_ANYNL: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x000d: + if (eptr < md->end_subject && *eptr == 0x0a) eptr++; + break; + + case 0x000a: + break; + + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if (md->bsr_anycrlf) RRETURN(MATCH_NOMATCH); + break; + } + ecode++; + break; + + case OP_NOT_HSPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + switch(c) + { + default: break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + RRETURN(MATCH_NOMATCH); + } + ecode++; + break; + + case OP_HSPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + break; + } + ecode++; + break; + + case OP_NOT_VSPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + switch(c) + { + default: break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + RRETURN(MATCH_NOMATCH); + } + ecode++; + break; + + case OP_VSPACE: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + break; + } + ecode++; + break; + +#ifdef SUPPORT_UCP + /* Check the next character by Unicode property. We will get here only + if the support is in the binary; otherwise a compile-time error occurs. */ + + case OP_PROP: + case OP_NOTPROP: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + { + int chartype, script; + int category = _pcre_ucp_findprop(c, &chartype, &script); + + switch(ecode[1]) + { + case PT_ANY: + if (op == OP_NOTPROP) RRETURN(MATCH_NOMATCH); + break; + + case PT_LAMP: + if ((chartype == ucp_Lu || + chartype == ucp_Ll || + chartype == ucp_Lt) == (op == OP_NOTPROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_GC: + if ((ecode[2] != category) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_PC: + if ((ecode[2] != chartype) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_SC: + if ((ecode[2] != script) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + + ecode += 3; + } + break; + + /* Match an extended Unicode sequence. We will get here only if the support + is in the binary; otherwise a compile-time error occurs. */ + + case OP_EXTUNI: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + { + int chartype, script; + int category = _pcre_ucp_findprop(c, &chartype, &script); + if (category == ucp_M) RRETURN(MATCH_NOMATCH); + while (eptr < md->end_subject) + { + int len = 1; + if (!utf8) c = *eptr; else + { + GETCHARLEN(c, eptr, len); + } + category = _pcre_ucp_findprop(c, &chartype, &script); + if (category != ucp_M) break; + eptr += len; + } + } + ecode++; + break; +#endif + + + /* Match a back reference, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. The code is similar + to that for character classes, but repeated for efficiency. Then obey + similar code to character type repeats - written out again for speed. + However, if the referenced string is the empty string, always treat + it as matched, any number of times (otherwise there could be infinite + loops). */ + + case OP_REF: + { + offset = GET2(ecode, 1) << 1; /* Doubled ref number */ + ecode += 3; /* Advance past item */ + + /* If the reference is unset, set the length to be longer than the amount + of subject left; this ensures that every attempt at a match fails. We + can't just fail here, because of the possibility of quantifiers with zero + minima. */ + + length = (offset >= offset_top || md->offset_vector[offset] < 0)? + md->end_subject - eptr + 1 : + md->offset_vector[offset+1] - md->offset_vector[offset]; + + /* Set up for repetition, or handle the non-repeated case */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 3); + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + if (!match_ref(offset, eptr, length, md, ims)) RRETURN(MATCH_NOMATCH); + eptr += length; + continue; /* With the main loop */ + } + + /* If the length of the reference is zero, just continue with the + main loop. */ + + if (length == 0) continue; + + /* First, ensure the minimum number of matches are present. We get back + the length of the reference string explicitly rather than passing the + address of eptr, so that eptr can be a register variable. */ + + for (i = 1; i <= min; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) RRETURN(MATCH_NOMATCH); + eptr += length; + } + + /* If min = max, continue at the same level without recursion. + They are not both allowed to be zero. */ + + if (min == max) continue; + + /* If minimizing, keep trying and advancing the pointer */ + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM14); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || !match_ref(offset, eptr, length, md, ims)) + RRETURN(MATCH_NOMATCH); + eptr += length; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest string and work backwards */ + + else + { + pp = eptr; + for (i = min; i < max; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) break; + eptr += length; + } + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM15); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr -= length; + } + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + + + + /* Match a bit-mapped character class, possibly repeatedly. This op code is + used when all the characters in the class have values in the range 0-255, + and either the matching is caseful, or the characters are in the range + 0-127 when UTF-8 processing is enabled. The only difference between + OP_CLASS and OP_NCLASS occurs when a data character outside the range is + encountered. + + First, look past the end of the item to see if there is repeat information + following. Then obey similar code to character type repeats - written out + again for speed. */ + + case OP_NCLASS: + case OP_CLASS: + { + data = ecode + 1; /* Save for matching */ + ecode += 33; /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 3); + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else + { + if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + c = *eptr++; + if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM16); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else + { + if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM17); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + c = *eptr++; + if ((data[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c > 255) + { + if (op == OP_CLASS) break; + } + else + { + if ((data[c/8] & (1 << (c&7))) == 0) break; + } + eptr += len; + } + for (;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM18); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + BACKCHAR(eptr); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if ((data[c/8] & (1 << (c&7))) == 0) break; + eptr++; + } + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM19); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + + + /* Match an extended character class. This opcode is encountered only + in UTF-8 mode, because that's the only time it is compiled. */ + +#ifdef SUPPORT_UTF8 + case OP_XCLASS: + { + data = ecode + 1 + LINK_SIZE; /* Save for matching */ + ecode += GET(ecode, 1); /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 3); + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (!_pcre_xclass(c, data)) RRETURN(MATCH_NOMATCH); + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM20); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (!_pcre_xclass(c, data)) RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + pp = eptr; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (!_pcre_xclass(c, data)) break; + eptr += len; + } + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM21); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + if (utf8) BACKCHAR(eptr); + } + RRETURN(MATCH_NOMATCH); + } + + /* Control never gets here */ + } +#endif /* End of XCLASS */ + + /* Match a single character, casefully */ + + case OP_CHAR: +#ifdef SUPPORT_UTF8 + if (utf8) + { + length = 1; + ecode++; + GETCHARLEN(fc, ecode, length); + if (length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + while (length-- > 0) if (*ecode++ != *eptr++) RRETURN(MATCH_NOMATCH); + } + else +#endif + + /* Non-UTF-8 mode */ + { + if (md->end_subject - eptr < 1) RRETURN(MATCH_NOMATCH); + if (ecode[1] != *eptr++) RRETURN(MATCH_NOMATCH); + ecode += 2; + } + break; + + /* Match a single character, caselessly */ + + case OP_CHARNC: +#ifdef SUPPORT_UTF8 + if (utf8) + { + length = 1; + ecode++; + GETCHARLEN(fc, ecode, length); + + if (length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + + /* If the pattern character's value is < 128, we have only one byte, and + can use the fast lookup table. */ + + if (fc < 128) + { + if (md->lcc[*ecode++] != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); + } + + /* Otherwise we must pick up the subject character */ + + else + { + unsigned int dc; + GETCHARINC(dc, eptr); + ecode += length; + + /* If we have Unicode property support, we can use it to test the other + case of the character, if there is one. */ + + if (fc != dc) + { +#ifdef SUPPORT_UCP + if (dc != _pcre_ucp_othercase(fc)) +#endif + RRETURN(MATCH_NOMATCH); + } + } + } + else +#endif /* SUPPORT_UTF8 */ + + /* Non-UTF-8 mode */ + { + if (md->end_subject - eptr < 1) RRETURN(MATCH_NOMATCH); + if (md->lcc[ecode[1]] != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); + ecode += 2; + } + break; + + /* Match a single character repeatedly. */ + + case OP_EXACT: + min = max = GET2(ecode, 1); + ecode += 3; + goto REPEATCHAR; + + case OP_POSUPTO: + possessive = TRUE; + /* Fall through */ + + case OP_UPTO: + case OP_MINUPTO: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_MINUPTO; + ecode += 3; + goto REPEATCHAR; + + case OP_POSSTAR: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATCHAR; + + case OP_POSPLUS: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATCHAR; + + case OP_POSQUERY: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATCHAR; + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + c = *ecode++ - OP_STAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We can give + up quickly if there are fewer than the minimum number of characters left in + the subject. */ + + REPEATCHAR: +#ifdef SUPPORT_UTF8 + if (utf8) + { + length = 1; + charptr = ecode; + GETCHARLEN(fc, ecode, length); + if (min * length > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + ecode += length; + + /* Handle multibyte character matching specially here. There is + support for caseless matching if UCP support is present. */ + + if (length > 1) + { +#ifdef SUPPORT_UCP + unsigned int othercase; + if ((ims & PCRE_CASELESS) != 0 && + (othercase = _pcre_ucp_othercase(fc)) != NOTACHAR) + oclength = _pcre_ord2utf8(othercase, occhars); + else oclength = 0; +#endif /* SUPPORT_UCP */ + + for (i = 1; i <= min; i++) + { + if (memcmp(eptr, charptr, length) == 0) eptr += length; +#ifdef SUPPORT_UCP + /* Need braces because of following else */ + else if (oclength == 0) { RRETURN(MATCH_NOMATCH); } + else + { + if (memcmp(eptr, occhars, oclength) != 0) RRETURN(MATCH_NOMATCH); + eptr += oclength; + } +#else /* without SUPPORT_UCP */ + else { RRETURN(MATCH_NOMATCH); } +#endif /* SUPPORT_UCP */ + } + + if (min == max) continue; + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM22); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + if (memcmp(eptr, charptr, length) == 0) eptr += length; +#ifdef SUPPORT_UCP + /* Need braces because of following else */ + else if (oclength == 0) { RRETURN(MATCH_NOMATCH); } + else + { + if (memcmp(eptr, occhars, oclength) != 0) RRETURN(MATCH_NOMATCH); + eptr += oclength; + } +#else /* without SUPPORT_UCP */ + else { RRETURN (MATCH_NOMATCH); } +#endif /* SUPPORT_UCP */ + } + /* Control never gets here */ + } + + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + if (eptr > md->end_subject - length) break; + if (memcmp(eptr, charptr, length) == 0) eptr += length; +#ifdef SUPPORT_UCP + else if (oclength == 0) break; + else + { + if (memcmp(eptr, occhars, oclength) != 0) break; + eptr += oclength; + } +#else /* without SUPPORT_UCP */ + else break; +#endif /* SUPPORT_UCP */ + } + + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM23); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr == pp) RRETURN(MATCH_NOMATCH); +#ifdef SUPPORT_UCP + eptr--; + BACKCHAR(eptr); +#else /* without SUPPORT_UCP */ + eptr -= length; +#endif /* SUPPORT_UCP */ + } + } + /* Control never gets here */ + } + + /* If the length of a UTF-8 character is 1, we fall through here, and + obey the code as for non-UTF-8 characters below, though in this case the + value of fc will always be < 128. */ + } + else +#endif /* SUPPORT_UTF8 */ + + /* When not in UTF-8 mode, load a single-byte character. */ + { + if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + fc = *ecode++; + } + + /* The value of fc at this point is always less than 256, though we may or + may not be in UTF-8 mode. The code is duplicated for the caseless and + caseful cases, for speed, since matching characters is likely to be quite + common. First, ensure the minimum number of matches are present. If min = + max, continue at the same level without recursing. Otherwise, if + minimizing, keep trying the rest of the expression and advancing one + matching character if failing, up to the maximum. Alternatively, if + maximizing, find the maximum number of characters and work backwards. */ + + DPRINTF(("matching %c{%d,%d} against subject %.*s\n", fc, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + fc = md->lcc[fc]; + for (i = 1; i <= min; i++) + if (fc != md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); + if (min == max) continue; + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM24); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || + fc != md->lcc[*eptr++]) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || fc != md->lcc[*eptr]) break; + eptr++; + } + if (possessive) continue; + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM25); + eptr--; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + } + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + + /* Caseful comparisons (includes all multi-byte characters) */ + + else + { + for (i = 1; i <= min; i++) if (fc != *eptr++) RRETURN(MATCH_NOMATCH); + if (min == max) continue; + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM26); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || fc != *eptr++) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || fc != *eptr) break; + eptr++; + } + if (possessive) continue; + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM27); + eptr--; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + } + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + + /* Match a negated single one-byte character. The character we are + checking can be multibyte. */ + + case OP_NOT: + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + ecode++; + GETCHARINCTEST(c, eptr); + if ((ims & PCRE_CASELESS) != 0) + { +#ifdef SUPPORT_UTF8 + if (c < 256) +#endif + c = md->lcc[c]; + if (md->lcc[*ecode++] == c) RRETURN(MATCH_NOMATCH); + } + else + { + if (*ecode++ == c) RRETURN(MATCH_NOMATCH); + } + break; + + /* Match a negated single one-byte character repeatedly. This is almost a + repeat of the code for a repeated single character, but I haven't found a + nice way of commoning these up that doesn't require a test of the + positive/negative option for each character match. Maybe that wouldn't add + very much to the time taken, but character matching *is* what this is all + about... */ + + case OP_NOTEXACT: + min = max = GET2(ecode, 1); + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTUPTO: + case OP_NOTMINUPTO: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_NOTMINUPTO; + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTPOSSTAR: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSPLUS: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSQUERY: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSUPTO: + possessive = TRUE; + min = 0; + max = GET2(ecode, 1); + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + c = *ecode++ - OP_NOTSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-byte matches. We can give up quickly + if there are fewer than the minimum number of bytes left in the + subject. */ + + REPEATNOTCHAR: + if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + fc = *ecode++; + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", fc, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + fc = md->lcc[fc]; + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (i = 1; i <= min; i++) + { + GETCHARINC(d, eptr); + if (d < 256) d = md->lcc[d]; + if (fc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + + /* Not UTF-8 mode */ + { + for (i = 1; i <= min; i++) + if (fc == md->lcc[*eptr++]) RRETURN(MATCH_NOMATCH); + } + + if (min == max) continue; + + if (minimize) + { +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM28); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + GETCHARINC(d, eptr); + if (d < 256) d = md->lcc[d]; + if (fi >= max || eptr >= md->end_subject || fc == d) + RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM29); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || fc == md->lcc[*eptr++]) + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + /* Maximize case */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(d, eptr, len); + if (d < 256) d = md->lcc[d]; + if (fc == d) break; + eptr += len; + } + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM30); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + BACKCHAR(eptr); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || fc == md->lcc[*eptr]) break; + eptr++; + } + if (possessive) continue; + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM31); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + + /* Caseful comparisons */ + + else + { +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (i = 1; i <= min; i++) + { + GETCHARINC(d, eptr); + if (fc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (i = 1; i <= min; i++) + if (fc == *eptr++) RRETURN(MATCH_NOMATCH); + } + + if (min == max) continue; + + if (minimize) + { +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM32); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + GETCHARINC(d, eptr); + if (fi >= max || eptr >= md->end_subject || fc == d) + RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM33); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || fc == *eptr++) + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + /* Maximize case */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + register unsigned int d; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(d, eptr, len); + if (fc == d) break; + eptr += len; + } + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM34); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + BACKCHAR(eptr); + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || fc == *eptr) break; + eptr++; + } + if (possessive) continue; + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM35); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + + /* Match a single character type repeatedly; several different opcodes + share code. This is very similar to the code for single characters, but we + repeat it in the interests of efficiency. */ + + case OP_TYPEEXACT: + min = max = GET2(ecode, 1); + minimize = TRUE; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_TYPEMINUPTO; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPEPOSSTAR: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSPLUS: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSQUERY: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSUPTO: + possessive = TRUE; + min = 0; + max = GET2(ecode, 1); + ecode += 3; + goto REPEATTYPE; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + c = *ecode++ - OP_TYPESTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single character type matches. Note that + in UTF-8 mode, '.' matches a character of any length, but for the other + character types, the valid characters are all one-byte long. */ + + REPEATTYPE: + ctype = *ecode++; /* Code for the character type */ + +#ifdef SUPPORT_UCP + if (ctype == OP_PROP || ctype == OP_NOTPROP) + { + prop_fail_result = ctype == OP_NOTPROP; + prop_type = *ecode++; + prop_value = *ecode++; + } + else prop_type = -1; +#endif + + /* First, ensure the minimum number of matches are present. Use inline + code for maximizing the speed, and do the type test once at the start + (i.e. keep it out of the loop). Also we can test that there are at least + the minimum number of bytes before we start. This isn't as effective in + UTF-8 mode, but it does no harm. Separate the UTF-8 code completely as that + is tidier. Also separate the UCP code, which can be the same for both UTF-8 + and single-bytes. */ + + if (min > md->end_subject - eptr) RRETURN(MATCH_NOMATCH); + if (min > 0) + { +#ifdef SUPPORT_UCP + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + } + break; + + case PT_LAMP: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == ucp_Lu || + prop_chartype == ucp_Ll || + prop_chartype == ucp_Lt) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_GC: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_category == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_PC: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_SC: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_script == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + } + + /* Match extended Unicode sequences. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (i = 1; i <= min; i++) + { + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category == ucp_M) RRETURN(MATCH_NOMATCH); + while (eptr < md->end_subject) + { + int len = 1; + if (!utf8) c = *eptr; else + { + GETCHARLEN(c, eptr, len); + } + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category != ucp_M) break; + eptr += len; + } + } + } + + else +#endif /* SUPPORT_UCP */ + +/* Handle all other cases when the coding is UTF-8 */ + +#ifdef SUPPORT_UTF8 + if (utf8) switch(ctype) + { + case OP_ANY: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + ((ims & PCRE_DOTALL) == 0 && IS_NEWLINE(eptr))) + RRETURN(MATCH_NOMATCH); + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + break; + + case OP_ANYBYTE: + eptr += min; + break; + + case OP_ANYNL: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x000d: + if (eptr < md->end_subject && *eptr == 0x0a) eptr++; + break; + + case 0x000a: + break; + + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if (md->bsr_anycrlf) RRETURN(MATCH_NOMATCH); + break; + } + } + break; + + case OP_NOT_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(c) + { + default: break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + break; + } + } + break; + + case OP_NOT_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(c) + { + default: break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + break; + } + } + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (c < 128 && (md->ctypes[c] & ctype_digit) != 0) + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + *eptr >= 128 || (md->ctypes[*eptr++] & ctype_digit) == 0) + RRETURN(MATCH_NOMATCH); + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + (*eptr < 128 && (md->ctypes[*eptr] & ctype_space) != 0)) + RRETURN(MATCH_NOMATCH); + while (++eptr < md->end_subject && (*eptr & 0xc0) == 0x80); + } + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + *eptr >= 128 || (md->ctypes[*eptr++] & ctype_space) == 0) + RRETURN(MATCH_NOMATCH); + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + (*eptr < 128 && (md->ctypes[*eptr] & ctype_word) != 0)) + RRETURN(MATCH_NOMATCH); + while (++eptr < md->end_subject && (*eptr & 0xc0) == 0x80); + } + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + *eptr >= 128 || (md->ctypes[*eptr++] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } /* End switch(ctype) */ + + else +#endif /* SUPPORT_UTF8 */ + + /* Code for the non-UTF-8 case for minimum matching of operators other + than OP_PROP and OP_NOTPROP. We can assume that there are the minimum + number of bytes present, as this was tested above. */ + + switch(ctype) + { + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0) + { + for (i = 1; i <= min; i++) + { + if (IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); + eptr++; + } + } + else eptr += min; + break; + + case OP_ANYBYTE: + eptr += min; + break; + + /* Because of the CRLF case, we can't assume the minimum number of + bytes are present in this case. */ + + case OP_ANYNL: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + case 0x000d: + if (eptr < md->end_subject && *eptr == 0x0a) eptr++; + break; + case 0x000a: + break; + + case 0x000b: + case 0x000c: + case 0x0085: + if (md->bsr_anycrlf) RRETURN(MATCH_NOMATCH); + break; + } + } + break; + + case OP_NOT_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + switch(*eptr++) + { + default: break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + break; + } + } + break; + + case OP_NOT_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + switch(*eptr++) + { + default: break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + break; + } + } + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + } + + /* If min = max, continue at the same level without recursing */ + + if (min == max) continue; + + /* If minimizing, we have to test the rest of the pattern before each + subsequent match. Again, separate the UTF-8 case for speed, and also + separate the UCP cases. */ + + if (minimize) + { +#ifdef SUPPORT_UCP + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM36); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_LAMP: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM37); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == ucp_Lu || + prop_chartype == ucp_Ll || + prop_chartype == ucp_Lt) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_GC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM38); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_category == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_PC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM39); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_SC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM40); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_script == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + } + + /* Match extended Unicode sequences. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM41); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject) RRETURN(MATCH_NOMATCH); + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category == ucp_M) RRETURN(MATCH_NOMATCH); + while (eptr < md->end_subject) + { + int len = 1; + if (!utf8) c = *eptr; else + { + GETCHARLEN(c, eptr, len); + } + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category != ucp_M) break; + eptr += len; + } + } + } + + else +#endif /* SUPPORT_UCP */ + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + if (utf8) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM42); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || + (ctype == OP_ANY && (ims & PCRE_DOTALL) == 0 && + IS_NEWLINE(eptr))) + RRETURN(MATCH_NOMATCH); + + GETCHARINC(c, eptr); + switch(ctype) + { + case OP_ANY: /* This is the DOTALL case */ + break; + + case OP_ANYBYTE: + break; + + case OP_ANYNL: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x000d: + if (eptr < md->end_subject && *eptr == 0x0a) eptr++; + break; + case 0x000a: + break; + + case 0x000b: + case 0x000c: + case 0x0085: + case 0x2028: + case 0x2029: + if (md->bsr_anycrlf) RRETURN(MATCH_NOMATCH); + break; + } + break; + + case OP_NOT_HSPACE: + switch(c) + { + default: break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_HSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + break; + } + break; + + case OP_NOT_VSPACE: + switch(c) + { + default: break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_VSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + break; + } + break; + + case OP_NOT_DIGIT: + if (c < 256 && (md->ctypes[c] & ctype_digit) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_DIGIT: + if (c >= 256 || (md->ctypes[c] & ctype_digit) == 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WHITESPACE: + if (c < 256 && (md->ctypes[c] & ctype_space) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_WHITESPACE: + if (c >= 256 || (md->ctypes[c] & ctype_space) == 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WORDCHAR: + if (c < 256 && (md->ctypes[c] & ctype_word) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_WORDCHAR: + if (c >= 256 || (md->ctypes[c] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + } + } + else +#endif + /* Not UTF-8 mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM43); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max || eptr >= md->end_subject || + ((ims & PCRE_DOTALL) == 0 && IS_NEWLINE(eptr))) + RRETURN(MATCH_NOMATCH); + + c = *eptr++; + switch(ctype) + { + case OP_ANY: /* This is the DOTALL case */ + break; + + case OP_ANYBYTE: + break; + + case OP_ANYNL: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x000d: + if (eptr < md->end_subject && *eptr == 0x0a) eptr++; + break; + + case 0x000a: + break; + + case 0x000b: + case 0x000c: + case 0x0085: + if (md->bsr_anycrlf) RRETURN(MATCH_NOMATCH); + break; + } + break; + + case OP_NOT_HSPACE: + switch(c) + { + default: break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_HSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + break; + } + break; + + case OP_NOT_VSPACE: + switch(c) + { + default: break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_VSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + break; + } + break; + + case OP_NOT_DIGIT: + if ((md->ctypes[c] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_DIGIT: + if ((md->ctypes[c] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WHITESPACE: + if ((md->ctypes[c] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_WHITESPACE: + if ((md->ctypes[c] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WORDCHAR: + if ((md->ctypes[c] & ctype_word) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_WORDCHAR: + if ((md->ctypes[c] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + } + } + /* Control never gets here */ + } + + /* If maximizing, it is worth using inline code for speed, doing the type + test once at the start (i.e. keep it out of the loop). Again, keep the + UTF-8 and UCP stuff separate. */ + + else + { + pp = eptr; /* Remember where we started */ + +#ifdef SUPPORT_UCP + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (prop_fail_result) break; + eptr+= len; + } + break; + + case PT_LAMP: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == ucp_Lu || + prop_chartype == ucp_Ll || + prop_chartype == ucp_Lt) == prop_fail_result) + break; + eptr+= len; + } + break; + + case PT_GC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_category == prop_value) == prop_fail_result) + break; + eptr+= len; + } + break; + + case PT_PC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_chartype == prop_value) == prop_fail_result) + break; + eptr+= len; + } + break; + + case PT_SC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if ((prop_script == prop_value) == prop_fail_result) + break; + eptr+= len; + } + break; + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM44); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + if (utf8) BACKCHAR(eptr); + } + } + + /* Match extended Unicode sequences. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + GETCHARINCTEST(c, eptr); + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category == ucp_M) break; + while (eptr < md->end_subject) + { + int len = 1; + if (!utf8) c = *eptr; else + { + GETCHARLEN(c, eptr, len); + } + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category != ucp_M) break; + eptr += len; + } + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM45); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + for (;;) /* Move back over one extended */ + { + int len = 1; + if (!utf8) c = *eptr; else + { + BACKCHAR(eptr); + GETCHARLEN(c, eptr, len); + } + prop_category = _pcre_ucp_findprop(c, &prop_chartype, &prop_script); + if (prop_category != ucp_M) break; + eptr--; + } + } + } + + else +#endif /* SUPPORT_UCP */ + +#ifdef SUPPORT_UTF8 + /* UTF-8 mode */ + + if (utf8) + { + switch(ctype) + { + case OP_ANY: + if (max < INT_MAX) + { + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || IS_NEWLINE(eptr)) break; + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + else + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + } + + /* Handle unlimited UTF-8 repeat */ + + else + { + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || IS_NEWLINE(eptr)) break; + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + else + { + eptr = md->end_subject; + } + } + break; + + /* The byte case is the same as non-UTF8 */ + + case OP_ANYBYTE: + c = max - min; + if (c > (unsigned int)(md->end_subject - eptr)) + c = md->end_subject - eptr; + eptr += c; + break; + + case OP_ANYNL: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c == 0x000d) + { + if (++eptr >= md->end_subject) break; + if (*eptr == 0x000a) eptr++; + } + else + { + if (c != 0x000a && + (md->bsr_anycrlf || + (c != 0x000b && c != 0x000c && + c != 0x0085 && c != 0x2028 && c != 0x2029))) + break; + eptr += len; + } + } + break; + + case OP_NOT_HSPACE: + case OP_HSPACE: + for (i = min; i < max; i++) + { + BOOL gotspace; + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + switch(c) + { + default: gotspace = FALSE; break; + case 0x09: /* HT */ + case 0x20: /* SPACE */ + case 0xa0: /* NBSP */ + case 0x1680: /* OGHAM SPACE MARK */ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ + case 0x2000: /* EN QUAD */ + case 0x2001: /* EM QUAD */ + case 0x2002: /* EN SPACE */ + case 0x2003: /* EM SPACE */ + case 0x2004: /* THREE-PER-EM SPACE */ + case 0x2005: /* FOUR-PER-EM SPACE */ + case 0x2006: /* SIX-PER-EM SPACE */ + case 0x2007: /* FIGURE SPACE */ + case 0x2008: /* PUNCTUATION SPACE */ + case 0x2009: /* THIN SPACE */ + case 0x200A: /* HAIR SPACE */ + case 0x202f: /* NARROW NO-BREAK SPACE */ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ + case 0x3000: /* IDEOGRAPHIC SPACE */ + gotspace = TRUE; + break; + } + if (gotspace == (ctype == OP_NOT_HSPACE)) break; + eptr += len; + } + break; + + case OP_NOT_VSPACE: + case OP_VSPACE: + for (i = min; i < max; i++) + { + BOOL gotspace; + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + switch(c) + { + default: gotspace = FALSE; break; + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LINE SEPARATOR */ + case 0x2029: /* PARAGRAPH SEPARATOR */ + gotspace = TRUE; + break; + } + if (gotspace == (ctype == OP_NOT_VSPACE)) break; + eptr += len; + } + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c < 256 && (md->ctypes[c] & ctype_digit) != 0) break; + eptr+= len; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c >= 256 ||(md->ctypes[c] & ctype_digit) == 0) break; + eptr+= len; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c < 256 && (md->ctypes[c] & ctype_space) != 0) break; + eptr+= len; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c >= 256 ||(md->ctypes[c] & ctype_space) == 0) break; + eptr+= len; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c < 256 && (md->ctypes[c] & ctype_word) != 0) break; + eptr+= len; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len); + if (c >= 256 || (md->ctypes[c] & ctype_word) == 0) break; + eptr+= len; + } + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; + for(;;) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM46); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + BACKCHAR(eptr); + } + } + else +#endif /* SUPPORT_UTF8 */ + + /* Not UTF-8 mode */ + { + switch(ctype) + { + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || IS_NEWLINE(eptr)) break; + eptr++; + } + break; + } + /* For DOTALL case, fall through and treat as \C */ + + case OP_ANYBYTE: + c = max - min; + if (c > (unsigned int)(md->end_subject - eptr)) + c = md->end_subject - eptr; + eptr += c; + break; + + case OP_ANYNL: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if (c == 0x000d) + { + if (++eptr >= md->end_subject) break; + if (*eptr == 0x000a) eptr++; + } + else + { + if (c != 0x000a && + (md->bsr_anycrlf || + (c != 0x000b && c != 0x000c && c != 0x0085))) + break; + eptr++; + } + } + break; + + case OP_NOT_HSPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if (c == 0x09 || c == 0x20 || c == 0xa0) break; + eptr++; + } + break; + + case OP_HSPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if (c != 0x09 && c != 0x20 && c != 0xa0) break; + eptr++; + } + break; + + case OP_NOT_VSPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if (c == 0x0a || c == 0x0b || c == 0x0c || c == 0x0d || c == 0x85) + break; + eptr++; + } + break; + + case OP_VSPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + c = *eptr; + if (c != 0x0a && c != 0x0b && c != 0x0c && c != 0x0d && c != 0x85) + break; + eptr++; + } + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) != 0) + break; + eptr++; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) != 0) + break; + eptr++; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) != 0) + break; + eptr++; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) == 0) + break; + eptr++; + } + break; + + default: + RRETURN(PCRE_ERROR_INTERNAL); + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, md, ims, eptrb, 0, RM47); + eptr--; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + } + } + + /* Get here if we can't make it match with any permitted repetitions */ + + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + /* There's been some horrible disaster. Arrival here can only mean there is + something seriously wrong in the code above or the OP_xxx definitions. */ + + default: + DPRINTF(("Unknown opcode %d\n", *ecode)); + RRETURN(PCRE_ERROR_UNKNOWN_OPCODE); + } + + /* Do not stick any code in here without much thought; it is assumed + that "continue" in the code above comes out to here to repeat the main + loop. */ + + } /* End of main loop */ +/* Control never reaches here */ + + +/* When compiling to use the heap rather than the stack for recursive calls to +match(), the RRETURN() macro jumps here. The number that is saved in +frame->Xwhere indicates which label we actually want to return to. */ + +#ifdef NO_RECURSE +#define LBL(val) case val: goto L_RM##val; +HEAP_RETURN: +switch (frame->Xwhere) + { + LBL( 1) LBL( 2) LBL( 3) LBL( 4) LBL( 5) LBL( 6) LBL( 7) LBL( 8) + LBL( 9) LBL(10) LBL(11) LBL(12) LBL(13) LBL(14) LBL(15) LBL(17) + LBL(19) LBL(24) LBL(25) LBL(26) LBL(27) LBL(29) LBL(31) LBL(33) + LBL(35) LBL(43) LBL(47) LBL(48) LBL(49) LBL(50) LBL(51) LBL(52) + LBL(53) LBL(54) +#ifdef SUPPORT_UTF8 + LBL(16) LBL(18) LBL(20) LBL(21) LBL(22) LBL(23) LBL(28) LBL(30) + LBL(32) LBL(34) LBL(42) LBL(46) +#ifdef SUPPORT_UCP + LBL(36) LBL(37) LBL(38) LBL(39) LBL(40) LBL(41) LBL(44) LBL(45) +#endif /* SUPPORT_UCP */ +#endif /* SUPPORT_UTF8 */ + default: + DPRINTF(("jump error in pcre match: label %d non-existent\n", frame->Xwhere)); + return PCRE_ERROR_INTERNAL; + } +#undef LBL +#endif /* NO_RECURSE */ +} + + +/*************************************************************************** +**************************************************************************** + RECURSION IN THE match() FUNCTION + +Undefine all the macros that were defined above to handle this. */ + +#ifdef NO_RECURSE +#undef eptr +#undef ecode +#undef mstart +#undef offset_top +#undef ims +#undef eptrb +#undef flags + +#undef callpat +#undef charptr +#undef data +#undef next +#undef pp +#undef prev +#undef saved_eptr + +#undef new_recursive + +#undef cur_is_word +#undef condition +#undef prev_is_word + +#undef original_ims + +#undef ctype +#undef length +#undef max +#undef min +#undef number +#undef offset +#undef op +#undef save_capture_last +#undef save_offset1 +#undef save_offset2 +#undef save_offset3 +#undef stacksave + +#undef newptrb + +#endif + +/* These two are defined as macros in both cases */ + +#undef fc +#undef fi + +/*************************************************************************** +***************************************************************************/ + + + +/************************************************* +* Execute a Regular Expression * +*************************************************/ + +/* This function applies a compiled re to a subject string and picks out +portions of the string if it matches. Two elements in the vector are set for +each substring: the offsets to the start and end of the substring. + +Arguments: + argument_re points to the compiled expression + extra_data points to extra data or is NULL + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + offsets points to a vector of ints to be filled in with offsets + offsetcount the number of elements in the vector + +Returns: > 0 => success; value is the number of elements filled in + = 0 => success, but offsets is not big enough + -1 => failed to match + < -1 => some kind of unexpected problem +*/ + +PCRE_EXP_DEFN int +pcre_exec(const pcre *argument_re, const pcre_extra *extra_data, + PCRE_SPTR subject, int length, int start_offset, int options, int *offsets, + int offsetcount) +{ +int rc, resetcount, ocount; +int first_byte = -1; +int req_byte = -1; +int req_byte2 = -1; +int newline; +unsigned long int ims; +BOOL using_temporary_offsets = FALSE; +BOOL anchored; +BOOL startline; +BOOL firstline; +BOOL first_byte_caseless = FALSE; +BOOL req_byte_caseless = FALSE; +BOOL utf8; +match_data match_block; +match_data *md = &match_block; +const uschar *tables; +const uschar *start_bits = NULL; +USPTR start_match = (USPTR)subject + start_offset; +USPTR end_subject; +USPTR req_byte_ptr = start_match - 1; + +pcre_study_data internal_study; +const pcre_study_data *study; + +real_pcre internal_re; +const real_pcre *external_re = (const real_pcre *)argument_re; +const real_pcre *re = external_re; + +/* Plausibility checks */ + +if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION; +if (re == NULL || subject == NULL || + (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL; +if (offsetcount < 0) return PCRE_ERROR_BADCOUNT; + +/* Fish out the optional data from the extra_data structure, first setting +the default values. */ + +study = NULL; +md->match_limit = MATCH_LIMIT; +md->match_limit_recursion = MATCH_LIMIT_RECURSION; +md->callout_data = NULL; + +/* The table pointer is always in native byte order. */ + +tables = external_re->tables; + +if (extra_data != NULL) + { + register unsigned int flags = extra_data->flags; + if ((flags & PCRE_EXTRA_STUDY_DATA) != 0) + study = (const pcre_study_data *)extra_data->study_data; + if ((flags & PCRE_EXTRA_MATCH_LIMIT) != 0) + md->match_limit = extra_data->match_limit; + if ((flags & PCRE_EXTRA_MATCH_LIMIT_RECURSION) != 0) + md->match_limit_recursion = extra_data->match_limit_recursion; + if ((flags & PCRE_EXTRA_CALLOUT_DATA) != 0) + md->callout_data = extra_data->callout_data; + if ((flags & PCRE_EXTRA_TABLES) != 0) tables = extra_data->tables; + } + +/* If the exec call supplied NULL for tables, use the inbuilt ones. This +is a feature that makes it possible to save compiled regex and re-use them +in other programs later. */ + +if (tables == NULL) tables = _pcre_default_tables; + +/* Check that the first field in the block is the magic number. If it is not, +test for a regex that was compiled on a host of opposite endianness. If this is +the case, flipped values are put in internal_re and internal_study if there was +study data too. */ + +if (re->magic_number != MAGIC_NUMBER) + { + re = _pcre_try_flipped(re, &internal_re, study, &internal_study); + if (re == NULL) return PCRE_ERROR_BADMAGIC; + if (study != NULL) study = &internal_study; + } + +/* Set up other data */ + +anchored = ((re->options | options) & PCRE_ANCHORED) != 0; +startline = (re->flags & PCRE_STARTLINE) != 0; +firstline = (re->options & PCRE_FIRSTLINE) != 0; + +/* The code starts after the real_pcre block and the capture name table. */ + +md->start_code = (const uschar *)external_re + re->name_table_offset + + re->name_count * re->name_entry_size; + +md->start_subject = (USPTR)subject; +md->start_offset = start_offset; +md->end_subject = md->start_subject + length; +end_subject = md->end_subject; + +md->endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0; +utf8 = md->utf8 = (re->options & PCRE_UTF8) != 0; + +md->notbol = (options & PCRE_NOTBOL) != 0; +md->noteol = (options & PCRE_NOTEOL) != 0; +md->notempty = (options & PCRE_NOTEMPTY) != 0; +md->partial = (options & PCRE_PARTIAL) != 0; +md->hitend = FALSE; + +md->recursive = NULL; /* No recursion at top level */ + +md->lcc = tables + lcc_offset; +md->ctypes = tables + ctypes_offset; + +/* Handle different \R options. */ + +switch (options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) + { + case 0: + if ((re->options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) != 0) + md->bsr_anycrlf = (re->options & PCRE_BSR_ANYCRLF) != 0; + else +#ifdef BSR_ANYCRLF + md->bsr_anycrlf = TRUE; +#else + md->bsr_anycrlf = FALSE; +#endif + break; + + case PCRE_BSR_ANYCRLF: + md->bsr_anycrlf = TRUE; + break; + + case PCRE_BSR_UNICODE: + md->bsr_anycrlf = FALSE; + break; + + default: return PCRE_ERROR_BADNEWLINE; + } + +/* Handle different types of newline. The three bits give eight cases. If +nothing is set at run time, whatever was used at compile time applies. */ + +switch ((((options & PCRE_NEWLINE_BITS) == 0)? re->options : + (pcre_uint32)options) & PCRE_NEWLINE_BITS) + { + case 0: newline = NEWLINE; break; /* Compile-time default */ + case PCRE_NEWLINE_CR: newline = '\r'; break; + case PCRE_NEWLINE_LF: newline = '\n'; break; + case PCRE_NEWLINE_CR+ + PCRE_NEWLINE_LF: newline = ('\r' << 8) | '\n'; break; + case PCRE_NEWLINE_ANY: newline = -1; break; + case PCRE_NEWLINE_ANYCRLF: newline = -2; break; + default: return PCRE_ERROR_BADNEWLINE; + } + +if (newline == -2) + { + md->nltype = NLTYPE_ANYCRLF; + } +else if (newline < 0) + { + md->nltype = NLTYPE_ANY; + } +else + { + md->nltype = NLTYPE_FIXED; + if (newline > 255) + { + md->nllen = 2; + md->nl[0] = (newline >> 8) & 255; + md->nl[1] = newline & 255; + } + else + { + md->nllen = 1; + md->nl[0] = newline; + } + } + +/* Partial matching is supported only for a restricted set of regexes at the +moment. */ + +if (md->partial && (re->flags & PCRE_NOPARTIAL) != 0) + return PCRE_ERROR_BADPARTIAL; + +/* Check a UTF-8 string if required. Unfortunately there's no way of passing +back the character offset. */ + +#ifdef SUPPORT_UTF8 +if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0) + { + if (_pcre_valid_utf8((uschar *)subject, length) >= 0) + return PCRE_ERROR_BADUTF8; + if (start_offset > 0 && start_offset < length) + { + int tb = ((uschar *)subject)[start_offset]; + if (tb > 127) + { + tb &= 0xc0; + if (tb != 0 && tb != 0xc0) return PCRE_ERROR_BADUTF8_OFFSET; + } + } + } +#endif + +/* The ims options can vary during the matching as a result of the presence +of (?ims) items in the pattern. They are kept in a local variable so that +restoring at the exit of a group is easy. */ + +ims = re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL); + +/* If the expression has got more back references than the offsets supplied can +hold, we get a temporary chunk of working store to use during the matching. +Otherwise, we can use the vector supplied, rounding down its size to a multiple +of 3. */ + +ocount = offsetcount - (offsetcount % 3); + +if (re->top_backref > 0 && re->top_backref >= ocount/3) + { + ocount = re->top_backref * 3 + 3; + md->offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int)); + if (md->offset_vector == NULL) return PCRE_ERROR_NOMEMORY; + using_temporary_offsets = TRUE; + DPRINTF(("Got memory to hold back references\n")); + } +else md->offset_vector = offsets; + +md->offset_end = ocount; +md->offset_max = (2*ocount)/3; +md->offset_overflow = FALSE; +md->capture_last = -1; + +/* Compute the minimum number of offsets that we need to reset each time. Doing +this makes a huge difference to execution time when there aren't many brackets +in the pattern. */ + +resetcount = 2 + re->top_bracket * 2; +if (resetcount > offsetcount) resetcount = ocount; + +/* Reset the working variable associated with each extraction. These should +never be used unless previously set, but they get saved and restored, and so we +initialize them to avoid reading uninitialized locations. */ + +if (md->offset_vector != NULL) + { + register int *iptr = md->offset_vector + ocount; + register int *iend = iptr - resetcount/2 + 1; + while (--iptr >= iend) *iptr = -1; + } + +/* Set up the first character to match, if available. The first_byte value is +never set for an anchored regular expression, but the anchoring may be forced +at run time, so we have to test for anchoring. The first char may be unset for +an unanchored pattern, of course. If there's no first char and the pattern was +studied, there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->flags & PCRE_FIRSTSET) != 0) + { + first_byte = re->first_byte & 255; + if ((first_byte_caseless = ((re->first_byte & REQ_CASELESS) != 0)) == TRUE) + first_byte = md->lcc[first_byte]; + } + else + if (!startline && study != NULL && + (study->options & PCRE_STUDY_MAPPED) != 0) + start_bits = study->start_bits; + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. */ + +if ((re->flags & PCRE_REQCHSET) != 0) + { + req_byte = re->req_byte & 255; + req_byte_caseless = (re->req_byte & REQ_CASELESS) != 0; + req_byte2 = (tables + fcc_offset)[req_byte]; /* case flipped */ + } + + +/* ==========================================================================*/ + +/* Loop for handling unanchored repeated matching attempts; for anchored regexs +the loop runs just once. */ + +for(;;) + { + USPTR save_end_subject = end_subject; + USPTR new_start_match; + + /* Reset the maximum number of extractions we might see. */ + + if (md->offset_vector != NULL) + { + register int *iptr = md->offset_vector; + register int *iend = iptr + resetcount; + while (iptr < iend) *iptr++ = -1; + } + + /* Advance to a unique first char if possible. If firstline is TRUE, the + start of the match is constrained to the first line of a multiline string. + That is, the match must be before or at the first newline. Implement this by + temporarily adjusting end_subject so that we stop scanning at a newline. If + the match fails at the newline, later code breaks this loop. */ + + if (firstline) + { + USPTR t = start_match; + while (t < md->end_subject && !IS_NEWLINE(t)) t++; + end_subject = t; + } + + /* Now test for a unique first byte */ + + if (first_byte >= 0) + { + if (first_byte_caseless) + while (start_match < end_subject && + md->lcc[*start_match] != first_byte) + start_match++; + else + while (start_match < end_subject && *start_match != first_byte) + start_match++; + } + + /* Or to just after a linebreak for a multiline match if possible */ + + else if (startline) + { + if (start_match > md->start_subject + start_offset) + { + while (start_match <= end_subject && !WAS_NEWLINE(start_match)) + start_match++; + + /* If we have just passed a CR and the newline option is ANY or ANYCRLF, + and we are now at a LF, advance the match position by one more character. + */ + + if (start_match[-1] == '\r' && + (md->nltype == NLTYPE_ANY || md->nltype == NLTYPE_ANYCRLF) && + start_match < end_subject && + *start_match == '\n') + start_match++; + } + } + + /* Or to a non-unique first char after study */ + + else if (start_bits != NULL) + { + while (start_match < end_subject) + { + register unsigned int c = *start_match; + if ((start_bits[c/8] & (1 << (c&7))) == 0) start_match++; else break; + } + } + + /* Restore fudged end_subject */ + + end_subject = save_end_subject; + +#ifdef DEBUG /* Sigh. Some compilers never learn. */ + printf(">>>> Match against: "); + pchars(start_match, end_subject - start_match, TRUE, md); + printf("\n"); +#endif + + /* If req_byte is set, we know that that character must appear in the subject + for the match to succeed. If the first character is set, req_byte must be + later in the subject; otherwise the test starts at the match point. This + optimization can save a huge amount of backtracking in patterns with nested + unlimited repeats that aren't going to match. Writing separate code for + cased/caseless versions makes it go faster, as does using an autoincrement + and backing off on a match. + + HOWEVER: when the subject string is very, very long, searching to its end can + take a long time, and give bad performance on quite ordinary patterns. This + showed up when somebody was matching something like /^\d+C/ on a 32-megabyte + string... so we don't do this when the string is sufficiently long. + + ALSO: this processing is disabled when partial matching is requested. + */ + + if (req_byte >= 0 && + end_subject - start_match < REQ_BYTE_MAX && + !md->partial) + { + register USPTR p = start_match + ((first_byte >= 0)? 1 : 0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_byte_ptr) + { + if (req_byte_caseless) + { + while (p < end_subject) + { + register int pp = *p++; + if (pp == req_byte || pp == req_byte2) { p--; break; } + } + } + else + { + while (p < end_subject) + { + if (*p++ == req_byte) { p--; break; } + } + } + + /* If we can't find the required character, break the matching loop, + forcing a match failure. */ + + if (p >= end_subject) + { + rc = MATCH_NOMATCH; + break; + } + + /* If we have found the required character, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this character yet. */ + + req_byte_ptr = p; + } + } + + /* OK, we can now run the match. */ + + md->start_match_ptr = start_match; + md->match_call_count = 0; + rc = match(start_match, md->start_code, start_match, 2, md, ims, NULL, 0, 0); + + switch(rc) + { + /* NOMATCH and PRUNE advance by one character. THEN at this level acts + exactly like PRUNE. */ + + case MATCH_NOMATCH: + case MATCH_PRUNE: + case MATCH_THEN: + new_start_match = start_match + 1; +#ifdef SUPPORT_UTF8 + if (utf8) + while(new_start_match < end_subject && (*new_start_match & 0xc0) == 0x80) + new_start_match++; +#endif + break; + + /* SKIP passes back the next starting point explicitly. */ + + case MATCH_SKIP: + new_start_match = md->start_match_ptr; + break; + + /* COMMIT disables the bumpalong, but otherwise behaves as NOMATCH. */ + + case MATCH_COMMIT: + rc = MATCH_NOMATCH; + goto ENDLOOP; + + /* Any other return is some kind of error. */ + + default: + goto ENDLOOP; + } + + /* Control reaches here for the various types of "no match at this point" + result. Reset the code to MATCH_NOMATCH for subsequent checking. */ + + rc = MATCH_NOMATCH; + + /* If PCRE_FIRSTLINE is set, the match must happen before or at the first + newline in the subject (though it may continue over the newline). Therefore, + if we have just failed to match, starting at a newline, do not continue. */ + + if (firstline && IS_NEWLINE(start_match)) break; + + /* Advance to new matching position */ + + start_match = new_start_match; + + /* Break the loop if the pattern is anchored or if we have passed the end of + the subject. */ + + if (anchored || start_match > end_subject) break; + + /* If we have just passed a CR and we are now at a LF, and the pattern does + not contain any explicit matches for \r or \n, and the newline option is CRLF + or ANY or ANYCRLF, advance the match position by one more character. */ + + if (start_match[-1] == '\r' && + start_match < end_subject && + *start_match == '\n' && + (re->flags & PCRE_HASCRORLF) == 0 && + (md->nltype == NLTYPE_ANY || + md->nltype == NLTYPE_ANYCRLF || + md->nllen == 2)) + start_match++; + + } /* End of for(;;) "bumpalong" loop */ + +/* ==========================================================================*/ + +/* We reach here when rc is not MATCH_NOMATCH, or if one of the stopping +conditions is true: + +(1) The pattern is anchored or the match was failed by (*COMMIT); + +(2) We are past the end of the subject; + +(3) PCRE_FIRSTLINE is set and we have failed to match at a newline, because + this option requests that a match occur at or before the first newline in + the subject. + +When we have a match and the offset vector is big enough to deal with any +backreferences, captured substring offsets will already be set up. In the case +where we had to get some local store to hold offsets for backreference +processing, copy those that we can. In this case there need not be overflow if +certain parts of the pattern were not used, even though there are more +capturing parentheses than vector slots. */ + +ENDLOOP: + +if (rc == MATCH_MATCH) + { + if (using_temporary_offsets) + { + if (offsetcount >= 4) + { + memcpy(offsets + 2, md->offset_vector + 2, + (offsetcount - 2) * sizeof(int)); + DPRINTF(("Copied offsets from temporary memory\n")); + } + if (md->end_offset_top > offsetcount) md->offset_overflow = TRUE; + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(md->offset_vector); + } + + /* Set the return code to the number of captured strings, or 0 if there are + too many to fit into the vector. */ + + rc = md->offset_overflow? 0 : md->end_offset_top/2; + + /* If there is space, set up the whole thing as substring 0. The value of + md->start_match_ptr might be modified if \K was encountered on the success + matching path. */ + + if (offsetcount < 2) rc = 0; else + { + offsets[0] = md->start_match_ptr - md->start_subject; + offsets[1] = md->end_match_ptr - md->start_subject; + } + + DPRINTF((">>>> returning %d\n", rc)); + return rc; + } + +/* Control gets here if there has been an error, or if the overall match +attempt has failed at all permitted starting positions. */ + +if (using_temporary_offsets) + { + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(md->offset_vector); + } + +if (rc != MATCH_NOMATCH) + { + DPRINTF((">>>> error: returning %d\n", rc)); + return rc; + } +else if (md->partial && md->hitend) + { + DPRINTF((">>>> returning PCRE_ERROR_PARTIAL\n")); + return PCRE_ERROR_PARTIAL; + } +else + { + DPRINTF((">>>> returning PCRE_ERROR_NOMATCH\n")); + return PCRE_ERROR_NOMATCH; + } +} + +/* End of pcre_exec.c */ diff --git a/pcre-7.4/pcre_fullinfo.c b/pcre-7.4/pcre_fullinfo.c new file mode 100644 index 0000000..04e31f6 --- /dev/null +++ b/pcre-7.4/pcre_fullinfo.c @@ -0,0 +1,165 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/*PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_fullinfo(), which returns +information about a compiled pattern. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Return info about compiled pattern * +*************************************************/ + +/* This is a newer "info" function which has an extensible interface so +that additional items can be added compatibly. + +Arguments: + argument_re points to compiled code + extra_data points extra data, or NULL + what what information is required + where where to put the information + +Returns: 0 if data returned, negative on error +*/ + +PCRE_EXP_DEFN int +pcre_fullinfo(const pcre *argument_re, const pcre_extra *extra_data, int what, + void *where) +{ +real_pcre internal_re; +pcre_study_data internal_study; +const real_pcre *re = (const real_pcre *)argument_re; +const pcre_study_data *study = NULL; + +if (re == NULL || where == NULL) return PCRE_ERROR_NULL; + +if (extra_data != NULL && (extra_data->flags & PCRE_EXTRA_STUDY_DATA) != 0) + study = (const pcre_study_data *)extra_data->study_data; + +if (re->magic_number != MAGIC_NUMBER) + { + re = _pcre_try_flipped(re, &internal_re, study, &internal_study); + if (re == NULL) return PCRE_ERROR_BADMAGIC; + if (study != NULL) study = &internal_study; + } + +switch (what) + { + case PCRE_INFO_OPTIONS: + *((unsigned long int *)where) = re->options & PUBLIC_OPTIONS; + break; + + case PCRE_INFO_SIZE: + *((size_t *)where) = re->size; + break; + + case PCRE_INFO_STUDYSIZE: + *((size_t *)where) = (study == NULL)? 0 : study->size; + break; + + case PCRE_INFO_CAPTURECOUNT: + *((int *)where) = re->top_bracket; + break; + + case PCRE_INFO_BACKREFMAX: + *((int *)where) = re->top_backref; + break; + + case PCRE_INFO_FIRSTBYTE: + *((int *)where) = + ((re->flags & PCRE_FIRSTSET) != 0)? re->first_byte : + ((re->flags & PCRE_STARTLINE) != 0)? -1 : -2; + break; + + /* Make sure we pass back the pointer to the bit vector in the external + block, not the internal copy (with flipped integer fields). */ + + case PCRE_INFO_FIRSTTABLE: + *((const uschar **)where) = + (study != NULL && (study->options & PCRE_STUDY_MAPPED) != 0)? + ((const pcre_study_data *)extra_data->study_data)->start_bits : NULL; + break; + + case PCRE_INFO_LASTLITERAL: + *((int *)where) = + ((re->flags & PCRE_REQCHSET) != 0)? re->req_byte : -1; + break; + + case PCRE_INFO_NAMEENTRYSIZE: + *((int *)where) = re->name_entry_size; + break; + + case PCRE_INFO_NAMECOUNT: + *((int *)where) = re->name_count; + break; + + case PCRE_INFO_NAMETABLE: + *((const uschar **)where) = (const uschar *)re + re->name_table_offset; + break; + + case PCRE_INFO_DEFAULT_TABLES: + *((const uschar **)where) = (const uschar *)(_pcre_default_tables); + break; + + case PCRE_INFO_OKPARTIAL: + *((int *)where) = (re->flags & PCRE_NOPARTIAL) == 0; + break; + + case PCRE_INFO_JCHANGED: + *((int *)where) = (re->flags & PCRE_JCHANGED) != 0; + break; + + case PCRE_INFO_HASCRORLF: + *((int *)where) = (re->flags & PCRE_HASCRORLF) != 0; + break; + + default: return PCRE_ERROR_BADOPTION; + } + +return 0; +} + +/* End of pcre_fullinfo.c */ diff --git a/pcre-7.4/pcre_get.c b/pcre-7.4/pcre_get.c new file mode 100644 index 0000000..fc283c8 --- /dev/null +++ b/pcre-7.4/pcre_get.c @@ -0,0 +1,465 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains some convenience functions for extracting substrings +from the subject string after a regex match has succeeded. The original idea +for these functions came from Scott Wimer. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Find number for named string * +*************************************************/ + +/* This function is used by the get_first_set() function below, as well +as being generally available. It assumes that names are unique. + +Arguments: + code the compiled regex + stringname the name whose number is required + +Returns: the number of the named parentheses, or a negative number + (PCRE_ERROR_NOSUBSTRING) if not found +*/ + +int +pcre_get_stringnumber(const pcre *code, const char *stringname) +{ +int rc; +int entrysize; +int top, bot; +uschar *nametable; + +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMECOUNT, &top)) != 0) + return rc; +if (top <= 0) return PCRE_ERROR_NOSUBSTRING; + +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMEENTRYSIZE, &entrysize)) != 0) + return rc; +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMETABLE, &nametable)) != 0) + return rc; + +bot = 0; +while (top > bot) + { + int mid = (top + bot) / 2; + uschar *entry = nametable + entrysize*mid; + int c = strcmp(stringname, (char *)(entry + 2)); + if (c == 0) return (entry[0] << 8) + entry[1]; + if (c > 0) bot = mid + 1; else top = mid; + } + +return PCRE_ERROR_NOSUBSTRING; +} + + + +/************************************************* +* Find (multiple) entries for named string * +*************************************************/ + +/* This is used by the get_first_set() function below, as well as being +generally available. It is used when duplicated names are permitted. + +Arguments: + code the compiled regex + stringname the name whose entries required + firstptr where to put the pointer to the first entry + lastptr where to put the pointer to the last entry + +Returns: the length of each entry, or a negative number + (PCRE_ERROR_NOSUBSTRING) if not found +*/ + +int +pcre_get_stringtable_entries(const pcre *code, const char *stringname, + char **firstptr, char **lastptr) +{ +int rc; +int entrysize; +int top, bot; +uschar *nametable, *lastentry; + +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMECOUNT, &top)) != 0) + return rc; +if (top <= 0) return PCRE_ERROR_NOSUBSTRING; + +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMEENTRYSIZE, &entrysize)) != 0) + return rc; +if ((rc = pcre_fullinfo(code, NULL, PCRE_INFO_NAMETABLE, &nametable)) != 0) + return rc; + +lastentry = nametable + entrysize * (top - 1); +bot = 0; +while (top > bot) + { + int mid = (top + bot) / 2; + uschar *entry = nametable + entrysize*mid; + int c = strcmp(stringname, (char *)(entry + 2)); + if (c == 0) + { + uschar *first = entry; + uschar *last = entry; + while (first > nametable) + { + if (strcmp(stringname, (char *)(first - entrysize + 2)) != 0) break; + first -= entrysize; + } + while (last < lastentry) + { + if (strcmp(stringname, (char *)(last + entrysize + 2)) != 0) break; + last += entrysize; + } + *firstptr = (char *)first; + *lastptr = (char *)last; + return entrysize; + } + if (c > 0) bot = mid + 1; else top = mid; + } + +return PCRE_ERROR_NOSUBSTRING; +} + + + +/************************************************* +* Find first set of multiple named strings * +*************************************************/ + +/* This function allows for duplicate names in the table of named substrings. +It returns the number of the first one that was set in a pattern match. + +Arguments: + code the compiled regex + stringname the name of the capturing substring + ovector the vector of matched substrings + +Returns: the number of the first that is set, + or the number of the last one if none are set, + or a negative number on error +*/ + +static int +get_first_set(const pcre *code, const char *stringname, int *ovector) +{ +const real_pcre *re = (const real_pcre *)code; +int entrysize; +char *first, *last; +uschar *entry; +if ((re->options & PCRE_DUPNAMES) == 0 && (re->flags & PCRE_JCHANGED) == 0) + return pcre_get_stringnumber(code, stringname); +entrysize = pcre_get_stringtable_entries(code, stringname, &first, &last); +if (entrysize <= 0) return entrysize; +for (entry = (uschar *)first; entry <= (uschar *)last; entry += entrysize) + { + int n = (entry[0] << 8) + entry[1]; + if (ovector[n*2] >= 0) return n; + } +return (first[0] << 8) + first[1]; +} + + + + +/************************************************* +* Copy captured string to given buffer * +*************************************************/ + +/* This function copies a single captured substring into a given buffer. +Note that we use memcpy() rather than strncpy() in case there are binary zeros +in the string. + +Arguments: + subject the subject string that was matched + ovector pointer to the offsets table + stringcount the number of substrings that were captured + (i.e. the yield of the pcre_exec call, unless + that was zero, in which case it should be 1/3 + of the offset table size) + stringnumber the number of the required substring + buffer where to put the substring + size the size of the buffer + +Returns: if successful: + the length of the copied string, not including the zero + that is put on the end; can be zero + if not successful: + PCRE_ERROR_NOMEMORY (-6) buffer too small + PCRE_ERROR_NOSUBSTRING (-7) no such captured substring +*/ + +int +pcre_copy_substring(const char *subject, int *ovector, int stringcount, + int stringnumber, char *buffer, int size) +{ +int yield; +if (stringnumber < 0 || stringnumber >= stringcount) + return PCRE_ERROR_NOSUBSTRING; +stringnumber *= 2; +yield = ovector[stringnumber+1] - ovector[stringnumber]; +if (size < yield + 1) return PCRE_ERROR_NOMEMORY; +memcpy(buffer, subject + ovector[stringnumber], yield); +buffer[yield] = 0; +return yield; +} + + + +/************************************************* +* Copy named captured string to given buffer * +*************************************************/ + +/* This function copies a single captured substring into a given buffer, +identifying it by name. If the regex permits duplicate names, the first +substring that is set is chosen. + +Arguments: + code the compiled regex + subject the subject string that was matched + ovector pointer to the offsets table + stringcount the number of substrings that were captured + (i.e. the yield of the pcre_exec call, unless + that was zero, in which case it should be 1/3 + of the offset table size) + stringname the name of the required substring + buffer where to put the substring + size the size of the buffer + +Returns: if successful: + the length of the copied string, not including the zero + that is put on the end; can be zero + if not successful: + PCRE_ERROR_NOMEMORY (-6) buffer too small + PCRE_ERROR_NOSUBSTRING (-7) no such captured substring +*/ + +int +pcre_copy_named_substring(const pcre *code, const char *subject, int *ovector, + int stringcount, const char *stringname, char *buffer, int size) +{ +int n = get_first_set(code, stringname, ovector); +if (n <= 0) return n; +return pcre_copy_substring(subject, ovector, stringcount, n, buffer, size); +} + + + +/************************************************* +* Copy all captured strings to new store * +*************************************************/ + +/* This function gets one chunk of store and builds a list of pointers and all +of the captured substrings in it. A NULL pointer is put on the end of the list. + +Arguments: + subject the subject string that was matched + ovector pointer to the offsets table + stringcount the number of substrings that were captured + (i.e. the yield of the pcre_exec call, unless + that was zero, in which case it should be 1/3 + of the offset table size) + listptr set to point to the list of pointers + +Returns: if successful: 0 + if not successful: + PCRE_ERROR_NOMEMORY (-6) failed to get store +*/ + +int +pcre_get_substring_list(const char *subject, int *ovector, int stringcount, + const char ***listptr) +{ +int i; +int size = sizeof(char *); +int double_count = stringcount * 2; +char **stringlist; +char *p; + +for (i = 0; i < double_count; i += 2) + size += sizeof(char *) + ovector[i+1] - ovector[i] + 1; + +stringlist = (char **)(pcre_malloc)(size); +if (stringlist == NULL) return PCRE_ERROR_NOMEMORY; + +*listptr = (const char **)stringlist; +p = (char *)(stringlist + stringcount + 1); + +for (i = 0; i < double_count; i += 2) + { + int len = ovector[i+1] - ovector[i]; + memcpy(p, subject + ovector[i], len); + *stringlist++ = p; + p += len; + *p++ = 0; + } + +*stringlist = NULL; +return 0; +} + + + +/************************************************* +* Free store obtained by get_substring_list * +*************************************************/ + +/* This function exists for the benefit of people calling PCRE from non-C +programs that can call its functions, but not free() or (pcre_free)() directly. + +Argument: the result of a previous pcre_get_substring_list() +Returns: nothing +*/ + +void +pcre_free_substring_list(const char **pointer) +{ +(pcre_free)((void *)pointer); +} + + + +/************************************************* +* Copy captured string to new store * +*************************************************/ + +/* This function copies a single captured substring into a piece of new +store + +Arguments: + subject the subject string that was matched + ovector pointer to the offsets table + stringcount the number of substrings that were captured + (i.e. the yield of the pcre_exec call, unless + that was zero, in which case it should be 1/3 + of the offset table size) + stringnumber the number of the required substring + stringptr where to put a pointer to the substring + +Returns: if successful: + the length of the string, not including the zero that + is put on the end; can be zero + if not successful: + PCRE_ERROR_NOMEMORY (-6) failed to get store + PCRE_ERROR_NOSUBSTRING (-7) substring not present +*/ + +int +pcre_get_substring(const char *subject, int *ovector, int stringcount, + int stringnumber, const char **stringptr) +{ +int yield; +char *substring; +if (stringnumber < 0 || stringnumber >= stringcount) + return PCRE_ERROR_NOSUBSTRING; +stringnumber *= 2; +yield = ovector[stringnumber+1] - ovector[stringnumber]; +substring = (char *)(pcre_malloc)(yield + 1); +if (substring == NULL) return PCRE_ERROR_NOMEMORY; +memcpy(substring, subject + ovector[stringnumber], yield); +substring[yield] = 0; +*stringptr = substring; +return yield; +} + + + +/************************************************* +* Copy named captured string to new store * +*************************************************/ + +/* This function copies a single captured substring, identified by name, into +new store. If the regex permits duplicate names, the first substring that is +set is chosen. + +Arguments: + code the compiled regex + subject the subject string that was matched + ovector pointer to the offsets table + stringcount the number of substrings that were captured + (i.e. the yield of the pcre_exec call, unless + that was zero, in which case it should be 1/3 + of the offset table size) + stringname the name of the required substring + stringptr where to put the pointer + +Returns: if successful: + the length of the copied string, not including the zero + that is put on the end; can be zero + if not successful: + PCRE_ERROR_NOMEMORY (-6) couldn't get memory + PCRE_ERROR_NOSUBSTRING (-7) no such captured substring +*/ + +int +pcre_get_named_substring(const pcre *code, const char *subject, int *ovector, + int stringcount, const char *stringname, const char **stringptr) +{ +int n = get_first_set(code, stringname, ovector); +if (n <= 0) return n; +return pcre_get_substring(subject, ovector, stringcount, n, stringptr); +} + + + + +/************************************************* +* Free store obtained by get_substring * +*************************************************/ + +/* This function exists for the benefit of people calling PCRE from non-C +programs that can call its functions, but not free() or (pcre_free)() directly. + +Argument: the result of a previous pcre_get_substring() +Returns: nothing +*/ + +void +pcre_free_substring(const char *pointer) +{ +(pcre_free)((void *)pointer); +} + +/* End of pcre_get.c */ diff --git a/pcre-7.4/pcre_globals.c b/pcre-7.4/pcre_globals.c new file mode 100644 index 0000000..4794819 --- /dev/null +++ b/pcre-7.4/pcre_globals.c @@ -0,0 +1,63 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains global variables that are exported by the PCRE library. +PCRE is thread-clean and doesn't use any global variables in the normal sense. +However, it calls memory allocation and freeing functions via the four +indirections below, and it can optionally do callouts, using the fifth +indirection. These values can be changed by the caller, but are shared between +all threads. However, when compiling for Virtual Pascal, things are done +differently, and global variables are not used (see pcre.in). */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + +#ifndef VPCOMPAT +PCRE_EXP_DATA_DEFN void *(*pcre_malloc)(size_t) = malloc; +PCRE_EXP_DATA_DEFN void (*pcre_free)(void *) = free; +PCRE_EXP_DATA_DEFN void *(*pcre_stack_malloc)(size_t) = malloc; +PCRE_EXP_DATA_DEFN void (*pcre_stack_free)(void *) = free; +PCRE_EXP_DATA_DEFN int (*pcre_callout)(pcre_callout_block *) = NULL; +#endif + +/* End of pcre_globals.c */ diff --git a/pcre-7.4/pcre_info.c b/pcre-7.4/pcre_info.c new file mode 100644 index 0000000..9bcccbc --- /dev/null +++ b/pcre-7.4/pcre_info.c @@ -0,0 +1,93 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_info(), which gives some +information about a compiled pattern. However, use of this function is now +deprecated, as it has been superseded by pcre_fullinfo(). */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* (Obsolete) Return info about compiled pattern * +*************************************************/ + +/* This is the original "info" function. It picks potentially useful data out +of the private structure, but its interface was too rigid. It remains for +backwards compatibility. The public options are passed back in an int - though +the re->options field has been expanded to a long int, all the public options +at the low end of it, and so even on 16-bit systems this will still be OK. +Therefore, I haven't changed the API for pcre_info(). + +Arguments: + argument_re points to compiled code + optptr where to pass back the options + first_byte where to pass back the first character, + or -1 if multiline and all branches start ^, + or -2 otherwise + +Returns: number of capturing subpatterns + or negative values on error +*/ + +PCRE_EXP_DEFN int +pcre_info(const pcre *argument_re, int *optptr, int *first_byte) +{ +real_pcre internal_re; +const real_pcre *re = (const real_pcre *)argument_re; +if (re == NULL) return PCRE_ERROR_NULL; +if (re->magic_number != MAGIC_NUMBER) + { + re = _pcre_try_flipped(re, &internal_re, NULL, NULL); + if (re == NULL) return PCRE_ERROR_BADMAGIC; + } +if (optptr != NULL) *optptr = (int)(re->options & PUBLIC_OPTIONS); +if (first_byte != NULL) + *first_byte = ((re->flags & PCRE_FIRSTSET) != 0)? re->first_byte : + ((re->flags & PCRE_STARTLINE) != 0)? -1 : -2; +return re->top_bracket; +} + +/* End of pcre_info.c */ diff --git a/pcre-7.4/pcre_internal.h b/pcre-7.4/pcre_internal.h new file mode 100644 index 0000000..5fbb344 --- /dev/null +++ b/pcre-7.4/pcre_internal.h @@ -0,0 +1,1117 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This header contains definitions that are shared between the different +modules, but which are not relevant to the exported API. This includes some +functions whose names all begin with "_pcre_". */ + +#ifndef PCRE_INTERNAL_H +#define PCRE_INTERNAL_H + +/* Define DEBUG to get debugging output on stdout. */ + +#if 0 +#define DEBUG +#endif + +/* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef +inline, and there are *still* stupid compilers about that don't like indented +pre-processor statements, or at least there were when I first wrote this. After +all, it had only been about 10 years then... + +It turns out that the Mac Debugging.h header also defines the macro DPRINTF, so +be absolutely sure we get our version. */ + +#undef DPRINTF +#ifdef DEBUG +#define DPRINTF(p) printf p +#else +#define DPRINTF(p) /* Nothing */ +#endif + + +/* Standard C headers plus the external interface definition. The only time +setjmp and stdarg are used is when NO_RECURSE is set. */ + +#include <ctype.h> +#include <limits.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* When compiling a DLL for Windows, the exported symbols have to be declared +using some MS magic. I found some useful information on this web page: +http://msdn2.microsoft.com/en-us/library/y4h7bcy6(VS.80).aspx. According to the +information there, using __declspec(dllexport) without "extern" we have a +definition; with "extern" we have a declaration. The settings here override the +setting in pcre.h (which is included below); it defines only PCRE_EXP_DECL, +which is all that is needed for applications (they just import the symbols). We +use: + + PCRE_EXP_DECL for declarations + PCRE_EXP_DEFN for definitions of exported functions + PCRE_EXP_DATA_DEFN for definitions of exported variables + +The reason for the two DEFN macros is that in non-Windows environments, one +does not want to have "extern" before variable definitions because it leads to +compiler warnings. So we distinguish between functions and variables. In +Windows, the two should always be the same. + +The reason for wrapping this in #ifndef PCRE_EXP_DECL is so that pcretest, +which is an application, but needs to import this file in order to "peek" at +internals, can #include pcre.h first to get an application's-eye view. + +In principle, people compiling for non-Windows, non-Unix-like (i.e. uncommon, +special-purpose environments) might want to stick other stuff in front of +exported symbols. That's why, in the non-Windows case, we set PCRE_EXP_DEFN and +PCRE_EXP_DATA_DEFN only if they are not already set. */ + +#ifndef PCRE_EXP_DECL +/*# ifdef _WIN32 +# ifndef PCRE_STATIC +# define PCRE_EXP_DECL extern __declspec(dllexport) +# define PCRE_EXP_DEFN __declspec(dllexport) +# define PCRE_EXP_DATA_DEFN __declspec(dllexport) +# else +# define PCRE_EXP_DECL extern +# define PCRE_EXP_DEFN +# define PCRE_EXP_DATA_DEFN +# endif +# else*/ +# ifdef __cplusplus +# define PCRE_EXP_DECL extern "C" +# else +# define PCRE_EXP_DECL extern +# endif +# ifndef PCRE_EXP_DEFN +# define PCRE_EXP_DEFN PCRE_EXP_DECL +# endif +# ifndef PCRE_EXP_DATA_DEFN +# define PCRE_EXP_DATA_DEFN +# endif +# endif +/*#endif*/ + +/* We need to have types that specify unsigned 16-bit and 32-bit integers. We +cannot determine these outside the compilation (e.g. by running a program as +part of "configure") because PCRE is often cross-compiled for use on other +systems. Instead we make use of the maximum sizes that are available at +preprocessor time in standard C environments. */ + +#if USHRT_MAX == 65535 + typedef unsigned short pcre_uint16; +#elif UINT_MAX == 65535 + typedef unsigned int pcre_uint16; +#else + #error Cannot determine a type for 16-bit unsigned integers +#endif + +#if UINT_MAX == 4294967295 + typedef unsigned int pcre_uint32; +#elif ULONG_MAX == 4294967295 + typedef unsigned long int pcre_uint32; +#else + #error Cannot determine a type for 32-bit unsigned integers +#endif + +/* All character handling must be done as unsigned characters. Otherwise there +are problems with top-bit-set characters and functions such as isspace(). +However, we leave the interface to the outside world as char *, because that +should make things easier for callers. We define a short type for unsigned char +to save lots of typing. I tried "uchar", but it causes problems on Digital +Unix, where it is defined in sys/types, so use "uschar" instead. */ + +typedef unsigned char uschar; + +/* This is an unsigned int value that no character can ever have. UTF-8 +characters only go up to 0x7fffffff (though Unicode doesn't go beyond +0x0010ffff). */ + +#define NOTACHAR 0xffffffff + +/* PCRE is able to support several different kinds of newline (CR, LF, CRLF, +"any" and "anycrlf" at present). The following macros are used to package up +testing for newlines. NLBLOCK, PSSTART, and PSEND are defined in the various +modules to indicate in which datablock the parameters exist, and what the +start/end of string field names are. */ + +#define NLTYPE_FIXED 0 /* Newline is a fixed length string */ +#define NLTYPE_ANY 1 /* Newline is any Unicode line ending */ +#define NLTYPE_ANYCRLF 2 /* Newline is CR, LF, or CRLF */ + +/* This macro checks for a newline at the given position */ + +#define IS_NEWLINE(p) \ + ((NLBLOCK->nltype != NLTYPE_FIXED)? \ + ((p) < NLBLOCK->PSEND && \ + _pcre_is_newline((p), NLBLOCK->nltype, NLBLOCK->PSEND, &(NLBLOCK->nllen),\ + utf8)) \ + : \ + ((p) <= NLBLOCK->PSEND - NLBLOCK->nllen && \ + (p)[0] == NLBLOCK->nl[0] && \ + (NLBLOCK->nllen == 1 || (p)[1] == NLBLOCK->nl[1]) \ + ) \ + ) + +/* This macro checks for a newline immediately preceding the given position */ + +#define WAS_NEWLINE(p) \ + ((NLBLOCK->nltype != NLTYPE_FIXED)? \ + ((p) > NLBLOCK->PSSTART && \ + _pcre_was_newline((p), NLBLOCK->nltype, NLBLOCK->PSSTART, \ + &(NLBLOCK->nllen), utf8)) \ + : \ + ((p) >= NLBLOCK->PSSTART + NLBLOCK->nllen && \ + (p)[-NLBLOCK->nllen] == NLBLOCK->nl[0] && \ + (NLBLOCK->nllen == 1 || (p)[-NLBLOCK->nllen+1] == NLBLOCK->nl[1]) \ + ) \ + ) + +/* When PCRE is compiled as a C++ library, the subject pointer can be replaced +with a custom type. This makes it possible, for example, to allow pcre_exec() +to process subject strings that are discontinuous by using a smart pointer +class. It must always be possible to inspect all of the subject string in +pcre_exec() because of the way it backtracks. Two macros are required in the +normal case, for sign-unspecified and unsigned char pointers. The former is +used for the external interface and appears in pcre.h, which is why its name +must begin with PCRE_. */ + +#ifdef CUSTOM_SUBJECT_PTR +#define PCRE_SPTR CUSTOM_SUBJECT_PTR +#define USPTR CUSTOM_SUBJECT_PTR +#else +#define PCRE_SPTR const char * +#define USPTR const unsigned char * +#endif + + + +/* Include the public PCRE header and the definitions of UCP character property +values. */ + +#include "pcre.h" +#include "ucp.h" + +/* When compiling for use with the Virtual Pascal compiler, these functions +need to have their names changed. PCRE must be compiled with the -DVPCOMPAT +option on the command line. */ + +#ifdef VPCOMPAT +#define strlen(s) _strlen(s) +#define strncmp(s1,s2,m) _strncmp(s1,s2,m) +#define memcmp(s,c,n) _memcmp(s,c,n) +#define memcpy(d,s,n) _memcpy(d,s,n) +#define memmove(d,s,n) _memmove(d,s,n) +#define memset(s,c,n) _memset(s,c,n) +#else /* VPCOMPAT */ + +/* To cope with SunOS4 and other systems that lack memmove() but have bcopy(), +define a macro for memmove() if HAVE_MEMMOVE is false, provided that HAVE_BCOPY +is set. Otherwise, include an emulating function for those systems that have +neither (there some non-Unix environments where this is the case). */ + +#ifndef HAVE_MEMMOVE +#undef memmove /* some systems may have a macro */ +#ifdef HAVE_BCOPY +#define memmove(a, b, c) bcopy(b, a, c) +#else /* HAVE_BCOPY */ +static void * +pcre_memmove(void *d, const void *s, size_t n) +{ +size_t i; +unsigned char *dest = (unsigned char *)d; +const unsigned char *src = (const unsigned char *)s; +if (dest > src) + { + dest += n; + src += n; + for (i = 0; i < n; ++i) *(--dest) = *(--src); + return (void *)dest; + } +else + { + for (i = 0; i < n; ++i) *dest++ = *src++; + return (void *)(dest - n); + } +} +#define memmove(a, b, c) pcre_memmove(a, b, c) +#endif /* not HAVE_BCOPY */ +#endif /* not HAVE_MEMMOVE */ +#endif /* not VPCOMPAT */ + + +/* PCRE keeps offsets in its compiled code as 2-byte quantities (always stored +in big-endian order) by default. These are used, for example, to link from the +start of a subpattern to its alternatives and its end. The use of 2 bytes per +offset limits the size of the compiled regex to around 64K, which is big enough +for almost everybody. However, I received a request for an even bigger limit. +For this reason, and also to make the code easier to maintain, the storing and +loading of offsets from the byte string is now handled by the macros that are +defined here. + +The macros are controlled by the value of LINK_SIZE. This defaults to 2 in +the config.h file, but can be overridden by using -D on the command line. This +is automated on Unix systems via the "configure" command. */ + +#if LINK_SIZE == 2 + +#define PUT(a,n,d) \ + (a[n] = (d) >> 8), \ + (a[(n)+1] = (d) & 255) + +#define GET(a,n) \ + (((a)[n] << 8) | (a)[(n)+1]) + +#define MAX_PATTERN_SIZE (1 << 16) + + +#elif LINK_SIZE == 3 + +#define PUT(a,n,d) \ + (a[n] = (d) >> 16), \ + (a[(n)+1] = (d) >> 8), \ + (a[(n)+2] = (d) & 255) + +#define GET(a,n) \ + (((a)[n] << 16) | ((a)[(n)+1] << 8) | (a)[(n)+2]) + +#define MAX_PATTERN_SIZE (1 << 24) + + +#elif LINK_SIZE == 4 + +#define PUT(a,n,d) \ + (a[n] = (d) >> 24), \ + (a[(n)+1] = (d) >> 16), \ + (a[(n)+2] = (d) >> 8), \ + (a[(n)+3] = (d) & 255) + +#define GET(a,n) \ + (((a)[n] << 24) | ((a)[(n)+1] << 16) | ((a)[(n)+2] << 8) | (a)[(n)+3]) + +#define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */ + + +#else +#error LINK_SIZE must be either 2, 3, or 4 +#endif + + +/* Convenience macro defined in terms of the others */ + +#define PUTINC(a,n,d) PUT(a,n,d), a += LINK_SIZE + + +/* PCRE uses some other 2-byte quantities that do not change when the size of +offsets changes. There are used for repeat counts and for other things such as +capturing parenthesis numbers in back references. */ + +#define PUT2(a,n,d) \ + a[n] = (d) >> 8; \ + a[(n)+1] = (d) & 255 + +#define GET2(a,n) \ + (((a)[n] << 8) | (a)[(n)+1]) + +#define PUT2INC(a,n,d) PUT2(a,n,d), a += 2 + + +/* When UTF-8 encoding is being used, a character is no longer just a single +byte. The macros for character handling generate simple sequences when used in +byte-mode, and more complicated ones for UTF-8 characters. BACKCHAR should +never be called in byte mode. To make sure it can never even appear when UTF-8 +support is omitted, we don't even define it. */ + +#ifndef SUPPORT_UTF8 +#define GETCHAR(c, eptr) c = *eptr; +#define GETCHARTEST(c, eptr) c = *eptr; +#define GETCHARINC(c, eptr) c = *eptr++; +#define GETCHARINCTEST(c, eptr) c = *eptr++; +#define GETCHARLEN(c, eptr, len) c = *eptr; +/* #define BACKCHAR(eptr) */ + +#else /* SUPPORT_UTF8 */ + +/* Get the next UTF-8 character, not advancing the pointer. This is called when +we know we are in UTF-8 mode. */ + +#define GETCHAR(c, eptr) \ + c = *eptr; \ + if (c >= 0xc0) \ + { \ + int gcii; \ + int gcaa = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ + int gcss = 6*gcaa; \ + c = (c & _pcre_utf8_table3[gcaa]) << gcss; \ + for (gcii = 1; gcii <= gcaa; gcii++) \ + { \ + gcss -= 6; \ + c |= (eptr[gcii] & 0x3f) << gcss; \ + } \ + } + +/* Get the next UTF-8 character, testing for UTF-8 mode, and not advancing the +pointer. */ + +#define GETCHARTEST(c, eptr) \ + c = *eptr; \ + if (utf8 && c >= 0xc0) \ + { \ + int gcii; \ + int gcaa = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ + int gcss = 6*gcaa; \ + c = (c & _pcre_utf8_table3[gcaa]) << gcss; \ + for (gcii = 1; gcii <= gcaa; gcii++) \ + { \ + gcss -= 6; \ + c |= (eptr[gcii] & 0x3f) << gcss; \ + } \ + } + +/* Get the next UTF-8 character, advancing the pointer. This is called when we +know we are in UTF-8 mode. */ + +#define GETCHARINC(c, eptr) \ + c = *eptr++; \ + if (c >= 0xc0) \ + { \ + int gcaa = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ + int gcss = 6*gcaa; \ + c = (c & _pcre_utf8_table3[gcaa]) << gcss; \ + while (gcaa-- > 0) \ + { \ + gcss -= 6; \ + c |= (*eptr++ & 0x3f) << gcss; \ + } \ + } + +/* Get the next character, testing for UTF-8 mode, and advancing the pointer */ + +#define GETCHARINCTEST(c, eptr) \ + c = *eptr++; \ + if (utf8 && c >= 0xc0) \ + { \ + int gcaa = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ + int gcss = 6*gcaa; \ + c = (c & _pcre_utf8_table3[gcaa]) << gcss; \ + while (gcaa-- > 0) \ + { \ + gcss -= 6; \ + c |= (*eptr++ & 0x3f) << gcss; \ + } \ + } + +/* Get the next UTF-8 character, not advancing the pointer, incrementing length +if there are extra bytes. This is called when we know we are in UTF-8 mode. */ + +#define GETCHARLEN(c, eptr, len) \ + c = *eptr; \ + if (c >= 0xc0) \ + { \ + int gcii; \ + int gcaa = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ \ + int gcss = 6*gcaa; \ + c = (c & _pcre_utf8_table3[gcaa]) << gcss; \ + for (gcii = 1; gcii <= gcaa; gcii++) \ + { \ + gcss -= 6; \ + c |= (eptr[gcii] & 0x3f) << gcss; \ + } \ + len += gcaa; \ + } + +/* If the pointer is not at the start of a character, move it back until +it is. This is called only in UTF-8 mode - we don't put a test within the macro +because almost all calls are already within a block of UTF-8 only code. */ + +#define BACKCHAR(eptr) while((*eptr & 0xc0) == 0x80) eptr-- + +#endif + + +/* In case there is no definition of offsetof() provided - though any proper +Standard C system should have one. */ + +#ifndef offsetof +#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field)) +#endif + + +/* These are the public options that can change during matching. */ + +#define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL) + +/* Private flags containing information about the compiled regex. They used to +live at the top end of the options word, but that got almost full, so now they +are in a 16-bit flags word. */ + +#define PCRE_NOPARTIAL 0x0001 /* can't use partial with this regex */ +#define PCRE_FIRSTSET 0x0002 /* first_byte is set */ +#define PCRE_REQCHSET 0x0004 /* req_byte is set */ +#define PCRE_STARTLINE 0x0008 /* start after \n for multiline */ +#define PCRE_JCHANGED 0x0010 /* j option used in regex */ +#define PCRE_HASCRORLF 0x0020 /* explicit \r or \n in pattern */ + +/* Options for the "extra" block produced by pcre_study(). */ + +#define PCRE_STUDY_MAPPED 0x01 /* a map of starting chars exists */ + +/* Masks for identifying the public options that are permitted at compile +time, run time, or study time, respectively. */ + +#define PCRE_NEWLINE_BITS (PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|PCRE_NEWLINE_ANY| \ + PCRE_NEWLINE_ANYCRLF) + +#define PUBLIC_OPTIONS \ + (PCRE_CASELESS|PCRE_EXTENDED|PCRE_ANCHORED|PCRE_MULTILINE| \ + PCRE_DOTALL|PCRE_DOLLAR_ENDONLY|PCRE_EXTRA|PCRE_UNGREEDY|PCRE_UTF8| \ + PCRE_NO_AUTO_CAPTURE|PCRE_NO_UTF8_CHECK|PCRE_AUTO_CALLOUT|PCRE_FIRSTLINE| \ + PCRE_DUPNAMES|PCRE_NEWLINE_BITS|PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE) + +#define PUBLIC_EXEC_OPTIONS \ + (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NO_UTF8_CHECK| \ + PCRE_PARTIAL|PCRE_NEWLINE_BITS|PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE) + +#define PUBLIC_DFA_EXEC_OPTIONS \ + (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NO_UTF8_CHECK| \ + PCRE_PARTIAL|PCRE_DFA_SHORTEST|PCRE_DFA_RESTART|PCRE_NEWLINE_BITS| \ + PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE) + +#define PUBLIC_STUDY_OPTIONS 0 /* None defined */ + +/* Magic number to provide a small check against being handed junk. Also used +to detect whether a pattern was compiled on a host of different endianness. */ + +#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */ + +/* Negative values for the firstchar and reqchar variables */ + +#define REQ_UNSET (-2) +#define REQ_NONE (-1) + +/* The maximum remaining length of subject we are prepared to search for a +req_byte match. */ + +#define REQ_BYTE_MAX 1000 + +/* Flags added to firstbyte or reqbyte; a "non-literal" item is either a +variable-length repeat, or a anything other than literal characters. */ + +#define REQ_CASELESS 0x0100 /* indicates caselessness */ +#define REQ_VARY 0x0200 /* reqbyte followed non-literal item */ + +/* Miscellaneous definitions */ + +typedef int BOOL; + +#define FALSE 0 +#define TRUE 1 + +/* Escape items that are just an encoding of a particular data value. */ + +#ifndef ESC_e +#define ESC_e 27 +#endif + +#ifndef ESC_f +#define ESC_f '\f' +#endif + +#ifndef ESC_n +#define ESC_n '\n' +#endif + +#ifndef ESC_r +#define ESC_r '\r' +#endif + +/* We can't officially use ESC_t because it is a POSIX reserved identifier +(presumably because of all the others like size_t). */ + +#ifndef ESC_tee +#define ESC_tee '\t' +#endif + +/* Codes for different types of Unicode property */ + +#define PT_ANY 0 /* Any property - matches all chars */ +#define PT_LAMP 1 /* L& - the union of Lu, Ll, Lt */ +#define PT_GC 2 /* General characteristic (e.g. L) */ +#define PT_PC 3 /* Particular characteristic (e.g. Lu) */ +#define PT_SC 4 /* Script (e.g. Han) */ + +/* Flag bits and data types for the extended class (OP_XCLASS) for classes that +contain UTF-8 characters with values greater than 255. */ + +#define XCL_NOT 0x01 /* Flag: this is a negative class */ +#define XCL_MAP 0x02 /* Flag: a 32-byte map is present */ + +#define XCL_END 0 /* Marks end of individual items */ +#define XCL_SINGLE 1 /* Single item (one multibyte char) follows */ +#define XCL_RANGE 2 /* A range (two multibyte chars) follows */ +#define XCL_PROP 3 /* Unicode property (2-byte property code follows) */ +#define XCL_NOTPROP 4 /* Unicode inverted property (ditto) */ + +/* These are escaped items that aren't just an encoding of a particular data +value such as \n. They must have non-zero values, as check_escape() returns +their negation. Also, they must appear in the same order as in the opcode +definitions below, up to ESC_z. There's a dummy for OP_ANY because it +corresponds to "." rather than an escape sequence. The final one must be +ESC_REF as subsequent values are used for backreferences (\1, \2, \3, etc). +There are two tests in the code for an escape greater than ESC_b and less than +ESC_Z to detect the types that may be repeated. These are the types that +consume characters. If any new escapes are put in between that don't consume a +character, that code will have to change. */ + +enum { ESC_A = 1, ESC_G, ESC_K, ESC_B, ESC_b, ESC_D, ESC_d, ESC_S, ESC_s, + ESC_W, ESC_w, ESC_dum1, ESC_C, ESC_P, ESC_p, ESC_R, ESC_H, ESC_h, + ESC_V, ESC_v, ESC_X, ESC_Z, ESC_z, ESC_E, ESC_Q, ESC_k, ESC_REF }; + + +/* Opcode table: Starting from 1 (i.e. after OP_END), the values up to +OP_EOD must correspond in order to the list of escapes immediately above. + +*** NOTE NOTE NOTE *** Whenever this list is updated, the two macro definitions +that follow must also be updated to match. There is also a table called +"coptable" in pcre_dfa_exec.c that must be updated. */ + +enum { + OP_END, /* 0 End of pattern */ + + /* Values corresponding to backslashed metacharacters */ + + OP_SOD, /* 1 Start of data: \A */ + OP_SOM, /* 2 Start of match (subject + offset): \G */ + OP_SET_SOM, /* 3 Set start of match (\K) */ + OP_NOT_WORD_BOUNDARY, /* 4 \B */ + OP_WORD_BOUNDARY, /* 5 \b */ + OP_NOT_DIGIT, /* 6 \D */ + OP_DIGIT, /* 7 \d */ + OP_NOT_WHITESPACE, /* 8 \S */ + OP_WHITESPACE, /* 9 \s */ + OP_NOT_WORDCHAR, /* 10 \W */ + OP_WORDCHAR, /* 11 \w */ + OP_ANY, /* 12 Match any character */ + OP_ANYBYTE, /* 13 Match any byte (\C); different to OP_ANY for UTF-8 */ + OP_NOTPROP, /* 14 \P (not Unicode property) */ + OP_PROP, /* 15 \p (Unicode property) */ + OP_ANYNL, /* 16 \R (any newline sequence) */ + OP_NOT_HSPACE, /* 17 \H (not horizontal whitespace) */ + OP_HSPACE, /* 18 \h (horizontal whitespace) */ + OP_NOT_VSPACE, /* 19 \V (not vertical whitespace) */ + OP_VSPACE, /* 20 \v (vertical whitespace) */ + OP_EXTUNI, /* 21 \X (extended Unicode sequence */ + OP_EODN, /* 22 End of data or \n at end of data: \Z. */ + OP_EOD, /* 23 End of data: \z */ + + OP_OPT, /* 24 Set runtime options */ + OP_CIRC, /* 25 Start of line - varies with multiline switch */ + OP_DOLL, /* 26 End of line - varies with multiline switch */ + OP_CHAR, /* 27 Match one character, casefully */ + OP_CHARNC, /* 28 Match one character, caselessly */ + OP_NOT, /* 29 Match one character, not the following one */ + + OP_STAR, /* 30 The maximizing and minimizing versions of */ + OP_MINSTAR, /* 31 these six opcodes must come in pairs, with */ + OP_PLUS, /* 32 the minimizing one second. */ + OP_MINPLUS, /* 33 This first set applies to single characters.*/ + OP_QUERY, /* 34 */ + OP_MINQUERY, /* 35 */ + + OP_UPTO, /* 36 From 0 to n matches */ + OP_MINUPTO, /* 37 */ + OP_EXACT, /* 38 Exactly n matches */ + + OP_POSSTAR, /* 39 Possessified star */ + OP_POSPLUS, /* 40 Possessified plus */ + OP_POSQUERY, /* 41 Posesssified query */ + OP_POSUPTO, /* 42 Possessified upto */ + + OP_NOTSTAR, /* 43 The maximizing and minimizing versions of */ + OP_NOTMINSTAR, /* 44 these six opcodes must come in pairs, with */ + OP_NOTPLUS, /* 45 the minimizing one second. They must be in */ + OP_NOTMINPLUS, /* 46 exactly the same order as those above. */ + OP_NOTQUERY, /* 47 This set applies to "not" single characters. */ + OP_NOTMINQUERY, /* 48 */ + + OP_NOTUPTO, /* 49 From 0 to n matches */ + OP_NOTMINUPTO, /* 50 */ + OP_NOTEXACT, /* 51 Exactly n matches */ + + OP_NOTPOSSTAR, /* 52 Possessified versions */ + OP_NOTPOSPLUS, /* 53 */ + OP_NOTPOSQUERY, /* 54 */ + OP_NOTPOSUPTO, /* 55 */ + + OP_TYPESTAR, /* 56 The maximizing and minimizing versions of */ + OP_TYPEMINSTAR, /* 57 these six opcodes must come in pairs, with */ + OP_TYPEPLUS, /* 58 the minimizing one second. These codes must */ + OP_TYPEMINPLUS, /* 59 be in exactly the same order as those above. */ + OP_TYPEQUERY, /* 60 This set applies to character types such as \d */ + OP_TYPEMINQUERY, /* 61 */ + + OP_TYPEUPTO, /* 62 From 0 to n matches */ + OP_TYPEMINUPTO, /* 63 */ + OP_TYPEEXACT, /* 64 Exactly n matches */ + + OP_TYPEPOSSTAR, /* 65 Possessified versions */ + OP_TYPEPOSPLUS, /* 66 */ + OP_TYPEPOSQUERY, /* 67 */ + OP_TYPEPOSUPTO, /* 68 */ + + OP_CRSTAR, /* 69 The maximizing and minimizing versions of */ + OP_CRMINSTAR, /* 70 all these opcodes must come in pairs, with */ + OP_CRPLUS, /* 71 the minimizing one second. These codes must */ + OP_CRMINPLUS, /* 72 be in exactly the same order as those above. */ + OP_CRQUERY, /* 73 These are for character classes and back refs */ + OP_CRMINQUERY, /* 74 */ + OP_CRRANGE, /* 75 These are different to the three sets above. */ + OP_CRMINRANGE, /* 76 */ + + OP_CLASS, /* 77 Match a character class, chars < 256 only */ + OP_NCLASS, /* 78 Same, but the bitmap was created from a negative + class - the difference is relevant only when a UTF-8 + character > 255 is encountered. */ + + OP_XCLASS, /* 79 Extended class for handling UTF-8 chars within the + class. This does both positive and negative. */ + + OP_REF, /* 80 Match a back reference */ + OP_RECURSE, /* 81 Match a numbered subpattern (possibly recursive) */ + OP_CALLOUT, /* 82 Call out to external function if provided */ + + OP_ALT, /* 83 Start of alternation */ + OP_KET, /* 84 End of group that doesn't have an unbounded repeat */ + OP_KETRMAX, /* 85 These two must remain together and in this */ + OP_KETRMIN, /* 86 order. They are for groups the repeat for ever. */ + + /* The assertions must come before BRA, CBRA, ONCE, and COND.*/ + + OP_ASSERT, /* 87 Positive lookahead */ + OP_ASSERT_NOT, /* 88 Negative lookahead */ + OP_ASSERTBACK, /* 89 Positive lookbehind */ + OP_ASSERTBACK_NOT, /* 90 Negative lookbehind */ + OP_REVERSE, /* 91 Move pointer back - used in lookbehind assertions */ + + /* ONCE, BRA, CBRA, and COND must come after the assertions, with ONCE first, + as there's a test for >= ONCE for a subpattern that isn't an assertion. */ + + OP_ONCE, /* 92 Atomic group */ + OP_BRA, /* 93 Start of non-capturing bracket */ + OP_CBRA, /* 94 Start of capturing bracket */ + OP_COND, /* 95 Conditional group */ + + /* These three must follow the previous three, in the same order. There's a + check for >= SBRA to distinguish the two sets. */ + + OP_SBRA, /* 96 Start of non-capturing bracket, check empty */ + OP_SCBRA, /* 97 Start of capturing bracket, check empty */ + OP_SCOND, /* 98 Conditional group, check empty */ + + OP_CREF, /* 99 Used to hold a capture number as condition */ + OP_RREF, /* 100 Used to hold a recursion number as condition */ + OP_DEF, /* 101 The DEFINE condition */ + + OP_BRAZERO, /* 102 These two must remain together and in this */ + OP_BRAMINZERO, /* 103 order. */ + + /* These are backtracking control verbs */ + + OP_PRUNE, /* 104 */ + OP_SKIP, /* 105 */ + OP_THEN, /* 106 */ + OP_COMMIT, /* 107 */ + + /* These are forced failure and success verbs */ + + OP_FAIL, /* 108 */ + OP_ACCEPT /* 109 */ +}; + + +/* This macro defines textual names for all the opcodes. These are used only +for debugging. The macro is referenced only in pcre_printint.c. */ + +#define OP_NAME_LIST \ + "End", "\\A", "\\G", "\\K", "\\B", "\\b", "\\D", "\\d", \ + "\\S", "\\s", "\\W", "\\w", "Any", "Anybyte", \ + "notprop", "prop", "\\R", "\\H", "\\h", "\\V", "\\v", \ + "extuni", "\\Z", "\\z", \ + "Opt", "^", "$", "char", "charnc", "not", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", \ + "class", "nclass", "xclass", "Ref", "Recurse", "Callout", \ + "Alt", "Ket", "KetRmax", "KetRmin", "Assert", "Assert not", \ + "AssertB", "AssertB not", "Reverse", \ + "Once", "Bra", "CBra", "Cond", "SBra", "SCBra", "SCond", \ + "Cond ref", "Cond rec", "Cond def", "Brazero", "Braminzero", \ + "*PRUNE", "*SKIP", "*THEN", "*COMMIT", "*FAIL", "*ACCEPT" + + +/* This macro defines the length of fixed length operations in the compiled +regex. The lengths are used when searching for specific things, and also in the +debugging printing of a compiled regex. We use a macro so that it can be +defined close to the definitions of the opcodes themselves. + +As things have been extended, some of these are no longer fixed lenths, but are +minima instead. For example, the length of a single-character repeat may vary +in UTF-8 mode. The code that uses this table must know about such things. */ + +#define OP_LENGTHS \ + 1, /* End */ \ + 1, 1, 1, 1, 1, /* \A, \G, \K, \B, \b */ \ + 1, 1, 1, 1, 1, 1, /* \D, \d, \S, \s, \W, \w */ \ + 1, 1, /* Any, Anybyte */ \ + 3, 3, 1, /* NOTPROP, PROP, EXTUNI */ \ + 1, 1, 1, 1, 1, /* \R, \H, \h, \V, \v */ \ + 1, 1, 2, 1, 1, /* \Z, \z, Opt, ^, $ */ \ + 2, /* Char - the minimum length */ \ + 2, /* Charnc - the minimum length */ \ + 2, /* not */ \ + /* Positive single-char repeats ** These are */ \ + 2, 2, 2, 2, 2, 2, /* *, *?, +, +?, ?, ?? ** minima in */ \ + 4, 4, 4, /* upto, minupto, exact ** UTF-8 mode */ \ + 2, 2, 2, 4, /* *+, ++, ?+, upto+ */ \ + /* Negative single-char repeats - only for chars < 256 */ \ + 2, 2, 2, 2, 2, 2, /* NOT *, *?, +, +?, ?, ?? */ \ + 4, 4, 4, /* NOT upto, minupto, exact */ \ + 2, 2, 2, 4, /* Possessive *, +, ?, upto */ \ + /* Positive type repeats */ \ + 2, 2, 2, 2, 2, 2, /* Type *, *?, +, +?, ?, ?? */ \ + 4, 4, 4, /* Type upto, minupto, exact */ \ + 2, 2, 2, 4, /* Possessive *+, ++, ?+, upto+ */ \ + /* Character class & ref repeats */ \ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ \ + 5, 5, /* CRRANGE, CRMINRANGE */ \ + 33, /* CLASS */ \ + 33, /* NCLASS */ \ + 0, /* XCLASS - variable length */ \ + 3, /* REF */ \ + 1+LINK_SIZE, /* RECURSE */ \ + 2+2*LINK_SIZE, /* CALLOUT */ \ + 1+LINK_SIZE, /* Alt */ \ + 1+LINK_SIZE, /* Ket */ \ + 1+LINK_SIZE, /* KetRmax */ \ + 1+LINK_SIZE, /* KetRmin */ \ + 1+LINK_SIZE, /* Assert */ \ + 1+LINK_SIZE, /* Assert not */ \ + 1+LINK_SIZE, /* Assert behind */ \ + 1+LINK_SIZE, /* Assert behind not */ \ + 1+LINK_SIZE, /* Reverse */ \ + 1+LINK_SIZE, /* ONCE */ \ + 1+LINK_SIZE, /* BRA */ \ + 3+LINK_SIZE, /* CBRA */ \ + 1+LINK_SIZE, /* COND */ \ + 1+LINK_SIZE, /* SBRA */ \ + 3+LINK_SIZE, /* SCBRA */ \ + 1+LINK_SIZE, /* SCOND */ \ + 3, /* CREF */ \ + 3, /* RREF */ \ + 1, /* DEF */ \ + 1, 1, /* BRAZERO, BRAMINZERO */ \ + 1, 1, 1, 1, /* PRUNE, SKIP, THEN, COMMIT, */ \ + 1, 1 /* FAIL, ACCEPT */ + + +/* A magic value for OP_RREF to indicate the "any recursion" condition. */ + +#define RREF_ANY 0xffff + +/* Error code numbers. They are given names so that they can more easily be +tracked. */ + +enum { ERR0, ERR1, ERR2, ERR3, ERR4, ERR5, ERR6, ERR7, ERR8, ERR9, + ERR10, ERR11, ERR12, ERR13, ERR14, ERR15, ERR16, ERR17, ERR18, ERR19, + ERR20, ERR21, ERR22, ERR23, ERR24, ERR25, ERR26, ERR27, ERR28, ERR29, + ERR30, ERR31, ERR32, ERR33, ERR34, ERR35, ERR36, ERR37, ERR38, ERR39, + ERR40, ERR41, ERR42, ERR43, ERR44, ERR45, ERR46, ERR47, ERR48, ERR49, + ERR50, ERR51, ERR52, ERR53, ERR54, ERR55, ERR56, ERR57, ERR58, ERR59, + ERR60, ERR61 }; + +/* The real format of the start of the pcre block; the index of names and the +code vector run on as long as necessary after the end. We store an explicit +offset to the name table so that if a regex is compiled on one host, saved, and +then run on another where the size of pointers is different, all might still +be well. For the case of compiled-on-4 and run-on-8, we include an extra +pointer that is always NULL. For future-proofing, a few dummy fields were +originally included - even though you can never get this planning right - but +there is only one left now. + +NOTE NOTE NOTE: +Because people can now save and re-use compiled patterns, any additions to this +structure should be made at the end, and something earlier (e.g. a new +flag in the options or one of the dummy fields) should indicate that the new +fields are present. Currently PCRE always sets the dummy fields to zero. +NOTE NOTE NOTE: +*/ + +typedef struct real_pcre { + pcre_uint32 magic_number; + pcre_uint32 size; /* Total that was malloced */ + pcre_uint32 options; /* Public options */ + pcre_uint16 flags; /* Private flags */ + pcre_uint16 dummy1; /* For future use */ + pcre_uint16 top_bracket; + pcre_uint16 top_backref; + pcre_uint16 first_byte; + pcre_uint16 req_byte; + pcre_uint16 name_table_offset; /* Offset to name table that follows */ + pcre_uint16 name_entry_size; /* Size of any name items */ + pcre_uint16 name_count; /* Number of name items */ + pcre_uint16 ref_count; /* Reference count */ + + const unsigned char *tables; /* Pointer to tables or NULL for std */ + const unsigned char *nullpad; /* NULL padding */ +} real_pcre; + +/* The format of the block used to store data from pcre_study(). The same +remark (see NOTE above) about extending this structure applies. */ + +typedef struct pcre_study_data { + pcre_uint32 size; /* Total that was malloced */ + pcre_uint32 options; + uschar start_bits[32]; +} pcre_study_data; + +/* Structure for passing "static" information around between the functions +doing the compiling, so that they are thread-safe. */ + +typedef struct compile_data { + const uschar *lcc; /* Points to lower casing table */ + const uschar *fcc; /* Points to case-flipping table */ + const uschar *cbits; /* Points to character type table */ + const uschar *ctypes; /* Points to table of type maps */ + const uschar *start_workspace;/* The start of working space */ + const uschar *start_code; /* The start of the compiled code */ + const uschar *start_pattern; /* The start of the pattern */ + const uschar *end_pattern; /* The end of the pattern */ + uschar *hwm; /* High watermark of workspace */ + uschar *name_table; /* The name/number table */ + int names_found; /* Number of entries so far */ + int name_entry_size; /* Size of each entry */ + int bracount; /* Count of capturing parens */ + int top_backref; /* Maximum back reference */ + unsigned int backref_map; /* Bitmap of low back refs */ + int external_options; /* External (initial) options */ + int external_flags; /* External flag bits to be set */ + int req_varyopt; /* "After variable item" flag for reqbyte */ + BOOL had_accept; /* (*ACCEPT) encountered */ + int nltype; /* Newline type */ + int nllen; /* Newline string length */ + uschar nl[4]; /* Newline string when fixed length */ +} compile_data; + +/* Structure for maintaining a chain of pointers to the currently incomplete +branches, for testing for left recursion. */ + +typedef struct branch_chain { + struct branch_chain *outer; + uschar *current; +} branch_chain; + +/* Structure for items in a linked list that represents an explicit recursive +call within the pattern. */ + +typedef struct recursion_info { + struct recursion_info *prevrec; /* Previous recursion record (or NULL) */ + int group_num; /* Number of group that was called */ + const uschar *after_call; /* "Return value": points after the call in the expr */ + USPTR save_start; /* Old value of mstart */ + int *offset_save; /* Pointer to start of saved offsets */ + int saved_max; /* Number of saved offsets */ +} recursion_info; + +/* Structure for building a chain of data for holding the values of the subject +pointer at the start of each subpattern, so as to detect when an empty string +has been matched by a subpattern - to break infinite loops. */ + +typedef struct eptrblock { + struct eptrblock *epb_prev; + USPTR epb_saved_eptr; +} eptrblock; + + +/* Structure for passing "static" information around between the functions +doing traditional NFA matching, so that they are thread-safe. */ + +typedef struct match_data { + unsigned long int match_call_count; /* As it says */ + unsigned long int match_limit; /* As it says */ + unsigned long int match_limit_recursion; /* As it says */ + int *offset_vector; /* Offset vector */ + int offset_end; /* One past the end */ + int offset_max; /* The maximum usable for return data */ + int nltype; /* Newline type */ + int nllen; /* Newline string length */ + uschar nl[4]; /* Newline string when fixed */ + const uschar *lcc; /* Points to lower casing table */ + const uschar *ctypes; /* Points to table of type maps */ + BOOL offset_overflow; /* Set if too many extractions */ + BOOL notbol; /* NOTBOL flag */ + BOOL noteol; /* NOTEOL flag */ + BOOL utf8; /* UTF8 flag */ + BOOL endonly; /* Dollar not before final \n */ + BOOL notempty; /* Empty string match not wanted */ + BOOL partial; /* PARTIAL flag */ + BOOL hitend; /* Hit the end of the subject at some point */ + BOOL bsr_anycrlf; /* \R is just any CRLF, not full Unicode */ + const uschar *start_code; /* For use when recursing */ + USPTR start_subject; /* Start of the subject string */ + USPTR end_subject; /* End of the subject string */ + USPTR start_match_ptr; /* Start of matched string */ + USPTR end_match_ptr; /* Subject position at end match */ + int end_offset_top; /* Highwater mark at end of match */ + int capture_last; /* Most recent capture number */ + int start_offset; /* The start offset value */ + eptrblock *eptrchain; /* Chain of eptrblocks for tail recursions */ + int eptrn; /* Next free eptrblock */ + recursion_info *recursive; /* Linked list of recursion data */ + void *callout_data; /* To pass back to callouts */ +} match_data; + +/* A similar structure is used for the same purpose by the DFA matching +functions. */ + +typedef struct dfa_match_data { + const uschar *start_code; /* Start of the compiled pattern */ + const uschar *start_subject; /* Start of the subject string */ + const uschar *end_subject; /* End of subject string */ + const uschar *tables; /* Character tables */ + int moptions; /* Match options */ + int poptions; /* Pattern options */ + int nltype; /* Newline type */ + int nllen; /* Newline string length */ + uschar nl[4]; /* Newline string when fixed */ + void *callout_data; /* To pass back to callouts */ +} dfa_match_data; + +/* Bit definitions for entries in the pcre_ctypes table. */ + +#define ctype_space 0x01 +#define ctype_letter 0x02 +#define ctype_digit 0x04 +#define ctype_xdigit 0x08 +#define ctype_word 0x10 /* alphameric or '_' */ +#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */ + +/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set +of bits for a class map. Some classes are built by combining these tables. */ + +#define cbit_space 0 /* [:space:] or \s */ +#define cbit_xdigit 32 /* [:xdigit:] */ +#define cbit_digit 64 /* [:digit:] or \d */ +#define cbit_upper 96 /* [:upper:] */ +#define cbit_lower 128 /* [:lower:] */ +#define cbit_word 160 /* [:word:] or \w */ +#define cbit_graph 192 /* [:graph:] */ +#define cbit_print 224 /* [:print:] */ +#define cbit_punct 256 /* [:punct:] */ +#define cbit_cntrl 288 /* [:cntrl:] */ +#define cbit_length 320 /* Length of the cbits table */ + +/* Offsets of the various tables from the base tables pointer, and +total length. */ + +#define lcc_offset 0 +#define fcc_offset 256 +#define cbits_offset 512 +#define ctypes_offset (cbits_offset + cbit_length) +#define tables_length (ctypes_offset + 256) + +/* Layout of the UCP type table that translates property names into types and +codes. Each entry used to point directly to a name, but to reduce the number of +relocations in shared libraries, it now has an offset into a single string +instead. */ + +typedef struct { + pcre_uint16 name_offset; + pcre_uint16 type; + pcre_uint16 value; +} ucp_type_table; + + +/* Internal shared data tables. These are tables that are used by more than one +of the exported public functions. They have to be "external" in the C sense, +but are not part of the PCRE public API. The data for these tables is in the +pcre_tables.c module. */ + +extern const int _pcre_utf8_table1[]; +extern const int _pcre_utf8_table2[]; +extern const int _pcre_utf8_table3[]; +extern const uschar _pcre_utf8_table4[]; + +extern const int _pcre_utf8_table1_size; + +extern const char _pcre_utt_names[]; +extern const ucp_type_table _pcre_utt[]; +extern const int _pcre_utt_size; + +extern const uschar _pcre_default_tables[]; + +extern const uschar _pcre_OP_lengths[]; + + +/* Internal shared functions. These are functions that are used by more than +one of the exported public functions. They have to be "external" in the C +sense, but are not part of the PCRE public API. */ + +extern BOOL _pcre_is_newline(const uschar *, int, const uschar *, + int *, BOOL); +extern int _pcre_ord2utf8(int, uschar *); +extern real_pcre *_pcre_try_flipped(const real_pcre *, real_pcre *, + const pcre_study_data *, pcre_study_data *); +extern int _pcre_ucp_findprop(const unsigned int, int *, int *); +extern unsigned int _pcre_ucp_othercase(const unsigned int); +extern int _pcre_valid_utf8(const uschar *, int); +extern BOOL _pcre_was_newline(const uschar *, int, const uschar *, + int *, BOOL); +extern BOOL _pcre_xclass(int, const uschar *); + +#endif + +/* End of pcre_internal.h */ diff --git a/pcre-7.4/pcre_maketables.c b/pcre-7.4/pcre_maketables.c new file mode 100644 index 0000000..352bea9 --- /dev/null +++ b/pcre-7.4/pcre_maketables.c @@ -0,0 +1,143 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_maketables(), which builds +character tables for PCRE in the current locale. The file is compiled on its +own as part of the PCRE library. However, it is also included in the +compilation of dftables.c, in which case the macro DFTABLES is defined. */ + + +#ifndef DFTABLES +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif +# include "pcre_internal.h" +#endif + + +/************************************************* +* Create PCRE character tables * +*************************************************/ + +/* This function builds a set of character tables for use by PCRE and returns +a pointer to them. They are build using the ctype functions, and consequently +their contents will depend upon the current locale setting. When compiled as +part of the library, the store is obtained via pcre_malloc(), but when compiled +inside dftables, use malloc(). + +Arguments: none +Returns: pointer to the contiguous block of data +*/ + +const unsigned char * +pcre_maketables(void) +{ +unsigned char *yield, *p; +int i; + +#ifndef DFTABLES +yield = (unsigned char*)(pcre_malloc)(tables_length); +#else +yield = (unsigned char*)malloc(tables_length); +#endif + +if (yield == NULL) return NULL; +p = yield; + +/* First comes the lower casing table */ + +for (i = 0; i < 256; i++) *p++ = tolower(i); + +/* Next the case-flipping table */ + +for (i = 0; i < 256; i++) *p++ = islower(i)? toupper(i) : tolower(i); + +/* Then the character class tables. Don't try to be clever and save effort on +exclusive ones - in some locales things may be different. Note that the table +for "space" includes everything "isspace" gives, including VT in the default +locale. This makes it work for the POSIX class [:space:]. Note also that it is +possible for a character to be alnum or alpha without being lower or upper, +such as "male and female ordinals" (\xAA and \xBA) in the fr_FR locale (at +least under Debian Linux's locales as of 12/2005). So we must test for alnum +specially. */ + +memset(p, 0, cbit_length); +for (i = 0; i < 256; i++) + { + if (isdigit(i)) p[cbit_digit + i/8] |= 1 << (i&7); + if (isupper(i)) p[cbit_upper + i/8] |= 1 << (i&7); + if (islower(i)) p[cbit_lower + i/8] |= 1 << (i&7); + if (isalnum(i)) p[cbit_word + i/8] |= 1 << (i&7); + if (i == '_') p[cbit_word + i/8] |= 1 << (i&7); + if (isspace(i)) p[cbit_space + i/8] |= 1 << (i&7); + if (isxdigit(i))p[cbit_xdigit + i/8] |= 1 << (i&7); + if (isgraph(i)) p[cbit_graph + i/8] |= 1 << (i&7); + if (isprint(i)) p[cbit_print + i/8] |= 1 << (i&7); + if (ispunct(i)) p[cbit_punct + i/8] |= 1 << (i&7); + if (iscntrl(i)) p[cbit_cntrl + i/8] |= 1 << (i&7); + } +p += cbit_length; + +/* Finally, the character type table. In this, we exclude VT from the white +space chars, because Perl doesn't recognize it as such for \s and for comments +within regexes. */ + +for (i = 0; i < 256; i++) + { + int x = 0; + if (i != 0x0b && isspace(i)) x += ctype_space; + if (isalpha(i)) x += ctype_letter; + if (isdigit(i)) x += ctype_digit; + if (isxdigit(i)) x += ctype_xdigit; + if (isalnum(i) || i == '_') x += ctype_word; + + /* Note: strchr includes the terminating zero in the characters it considers. + In this instance, that is ok because we want binary zero to be flagged as a + meta-character, which in this sense is any character that terminates a run + of data characters. */ + + if (strchr("\\*+?{^.$|()[", i) != 0) x += ctype_meta; + *p++ = x; + } + +return yield; +} + +/* End of pcre_maketables.c */ diff --git a/pcre-7.4/pcre_newline.c b/pcre-7.4/pcre_newline.c new file mode 100644 index 0000000..1708d93 --- /dev/null +++ b/pcre-7.4/pcre_newline.c @@ -0,0 +1,164 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains internal functions for testing newlines when more than +one kind of newline is to be recognized. When a newline is found, its length is +returned. In principle, we could implement several newline "types", each +referring to a different set of newline characters. At present, PCRE supports +only NLTYPE_FIXED, which gets handled without these functions, NLTYPE_ANYCRLF, +and NLTYPE_ANY. The full list of Unicode newline characters is taken from +http://unicode.org/unicode/reports/tr18/. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + + +/************************************************* +* Check for newline at given position * +*************************************************/ + +/* It is guaranteed that the initial value of ptr is less than the end of the +string that is being processed. + +Arguments: + ptr pointer to possible newline + type the newline type + endptr pointer to the end of the string + lenptr where to return the length + utf8 TRUE if in utf8 mode + +Returns: TRUE or FALSE +*/ + +BOOL +_pcre_is_newline(const uschar *ptr, int type, const uschar *endptr, + int *lenptr, BOOL utf8) +{ +int c; +if (utf8) { GETCHAR(c, ptr); } else c = *ptr; + +if (type == NLTYPE_ANYCRLF) switch(c) + { + case 0x000a: *lenptr = 1; return TRUE; /* LF */ + case 0x000d: *lenptr = (ptr < endptr - 1 && ptr[1] == 0x0a)? 2 : 1; + return TRUE; /* CR */ + default: return FALSE; + } + +/* NLTYPE_ANY */ + +else switch(c) + { + case 0x000a: /* LF */ + case 0x000b: /* VT */ + case 0x000c: *lenptr = 1; return TRUE; /* FF */ + case 0x000d: *lenptr = (ptr < endptr - 1 && ptr[1] == 0x0a)? 2 : 1; + return TRUE; /* CR */ + case 0x0085: *lenptr = utf8? 2 : 1; return TRUE; /* NEL */ + case 0x2028: /* LS */ + case 0x2029: *lenptr = 3; return TRUE; /* PS */ + default: return FALSE; + } +} + + + +/************************************************* +* Check for newline at previous position * +*************************************************/ + +/* It is guaranteed that the initial value of ptr is greater than the start of +the string that is being processed. + +Arguments: + ptr pointer to possible newline + type the newline type + startptr pointer to the start of the string + lenptr where to return the length + utf8 TRUE if in utf8 mode + +Returns: TRUE or FALSE +*/ + +BOOL +_pcre_was_newline(const uschar *ptr, int type, const uschar *startptr, + int *lenptr, BOOL utf8) +{ +int c; +ptr--; +#ifdef SUPPORT_UTF8 +if (utf8) + { + BACKCHAR(ptr); + GETCHAR(c, ptr); + } +else c = *ptr; +#else /* no UTF-8 support */ +c = *ptr; +#endif /* SUPPORT_UTF8 */ + +if (type == NLTYPE_ANYCRLF) switch(c) + { + case 0x000a: *lenptr = (ptr > startptr && ptr[-1] == 0x0d)? 2 : 1; + return TRUE; /* LF */ + case 0x000d: *lenptr = 1; return TRUE; /* CR */ + default: return FALSE; + } + +else switch(c) + { + case 0x000a: *lenptr = (ptr > startptr && ptr[-1] == 0x0d)? 2 : 1; + return TRUE; /* LF */ + case 0x000b: /* VT */ + case 0x000c: /* FF */ + case 0x000d: *lenptr = 1; return TRUE; /* CR */ + case 0x0085: *lenptr = utf8? 2 : 1; return TRUE; /* NEL */ + case 0x2028: /* LS */ + case 0x2029: *lenptr = 3; return TRUE; /* PS */ + default: return FALSE; + } +} + +/* End of pcre_newline.c */ diff --git a/pcre-7.4/pcre_ord2utf8.c b/pcre-7.4/pcre_ord2utf8.c new file mode 100644 index 0000000..d3904c6 --- /dev/null +++ b/pcre-7.4/pcre_ord2utf8.c @@ -0,0 +1,85 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This file contains a private PCRE function that converts an ordinal +character value into a UTF8 string. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Convert character value to UTF-8 * +*************************************************/ + +/* This function takes an integer value in the range 0 - 0x7fffffff +and encodes it as a UTF-8 character in 0 to 6 bytes. + +Arguments: + cvalue the character value + buffer pointer to buffer for result - at least 6 bytes long + +Returns: number of characters placed in the buffer +*/ + +int +_pcre_ord2utf8(int cvalue, uschar *buffer) +{ +#ifdef SUPPORT_UTF8 +register int i, j; +for (i = 0; i < _pcre_utf8_table1_size; i++) + if (cvalue <= _pcre_utf8_table1[i]) break; +buffer += i; +for (j = i; j > 0; j--) + { + *buffer-- = 0x80 | (cvalue & 0x3f); + cvalue >>= 6; + } +*buffer = _pcre_utf8_table2[i] | cvalue; +return i + 1; +#else +return 0; /* Keep compiler happy; this function won't ever be */ +#endif /* called when SUPPORT_UTF8 is not defined. */ +} + +/* End of pcre_ord2utf8.c */ diff --git a/pcre-7.4/pcre_refcount.c b/pcre-7.4/pcre_refcount.c new file mode 100644 index 0000000..b14103c --- /dev/null +++ b/pcre-7.4/pcre_refcount.c @@ -0,0 +1,82 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_refcount(), which is an +auxiliary function that can be used to maintain a reference count in a compiled +pattern data block. This might be helpful in applications where the block is +shared by different users. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Maintain reference count * +*************************************************/ + +/* The reference count is a 16-bit field, initialized to zero. It is not +possible to transfer a non-zero count from one host to a different host that +has a different byte order - though I can't see why anyone in their right mind +would ever want to do that! + +Arguments: + argument_re points to compiled code + adjust value to add to the count + +Returns: the (possibly updated) count value (a non-negative number), or + a negative error number +*/ + +PCRE_EXP_DEFN int +pcre_refcount(pcre *argument_re, int adjust) +{ +real_pcre *re = (real_pcre *)argument_re; +if (re == NULL) return PCRE_ERROR_NULL; +re->ref_count = (-adjust > re->ref_count)? 0 : + (adjust + re->ref_count > 65535)? 65535 : + re->ref_count + adjust; +return re->ref_count; +} + +/* End of pcre_refcount.c */ diff --git a/pcre-7.4/pcre_scanner.cc b/pcre-7.4/pcre_scanner.cc new file mode 100644 index 0000000..a817a68 --- /dev/null +++ b/pcre-7.4/pcre_scanner.cc @@ -0,0 +1,199 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <vector> +#include <assert.h> + +#include "pcrecpp_internal.h" +#include "pcre_scanner.h" + +using std::vector; + +namespace pcrecpp { + +Scanner::Scanner() + : data_(), + input_(data_), + skip_(NULL), + should_skip_(false), + skip_repeat_(false), + save_comments_(false), + comments_(NULL), + comments_offset_(0) { +} + +Scanner::Scanner(const string& in) + : data_(in), + input_(data_), + skip_(NULL), + should_skip_(false), + skip_repeat_(false), + save_comments_(false), + comments_(NULL), + comments_offset_(0) { +} + +Scanner::~Scanner() { + delete skip_; + delete comments_; +} + +void Scanner::SetSkipExpression(const char* re) { + delete skip_; + if (re != NULL) { + skip_ = new RE(re); + should_skip_ = true; + skip_repeat_ = true; + ConsumeSkip(); + } else { + skip_ = NULL; + should_skip_ = false; + skip_repeat_ = false; + } +} + +void Scanner::Skip(const char* re) { + delete skip_; + if (re != NULL) { + skip_ = new RE(re); + should_skip_ = true; + skip_repeat_ = false; + ConsumeSkip(); + } else { + skip_ = NULL; + should_skip_ = false; + skip_repeat_ = false; + } +} + +void Scanner::DisableSkip() { + assert(skip_ != NULL); + should_skip_ = false; +} + +void Scanner::EnableSkip() { + assert(skip_ != NULL); + should_skip_ = true; + ConsumeSkip(); +} + +int Scanner::LineNumber() const { + // TODO: Make it more efficient by keeping track of the last point + // where we computed line numbers and counting newlines since then. + // We could use std:count, but not all systems have it. :-( + int count = 1; + for (const char* p = data_.data(); p < input_.data(); ++p) + if (*p == '\n') + ++count; + return count; +} + +int Scanner::Offset() const { + return input_.data() - data_.c_str(); +} + +bool Scanner::LookingAt(const RE& re) const { + int consumed; + return re.DoMatch(input_, RE::ANCHOR_START, &consumed, 0, 0); +} + + +bool Scanner::Consume(const RE& re, + const Arg& arg0, + const Arg& arg1, + const Arg& arg2) { + const bool result = re.Consume(&input_, arg0, arg1, arg2); + if (result && should_skip_) ConsumeSkip(); + return result; +} + +// helper function to consume *skip_ and honour save_comments_ +void Scanner::ConsumeSkip() { + const char* start_data = input_.data(); + while (skip_->Consume(&input_)) { + if (!skip_repeat_) { + // Only one skip allowed. + break; + } + } + if (save_comments_) { + if (comments_ == NULL) { + comments_ = new vector<StringPiece>; + } + // already pointing one past end, so no need to +1 + int length = input_.data() - start_data; + if (length > 0) { + comments_->push_back(StringPiece(start_data, length)); + } + } +} + + +void Scanner::GetComments(int start, int end, vector<StringPiece> *ranges) { + // short circuit out if we've not yet initialized comments_ + // (e.g., when save_comments is false) + if (!comments_) { + return; + } + // TODO: if we guarantee that comments_ will contain StringPieces + // that are ordered by their start, then we can do a binary search + // for the first StringPiece at or past start and then scan for the + // ones contained in the range, quit early (use equal_range or + // lower_bound) + for (vector<StringPiece>::const_iterator it = comments_->begin(); + it != comments_->end(); ++it) { + if ((it->data() >= data_.c_str() + start && + it->data() + it->size() <= data_.c_str() + end)) { + ranges->push_back(*it); + } + } +} + + +void Scanner::GetNextComments(vector<StringPiece> *ranges) { + // short circuit out if we've not yet initialized comments_ + // (e.g., when save_comments is false) + if (!comments_) { + return; + } + for (vector<StringPiece>::const_iterator it = + comments_->begin() + comments_offset_; + it != comments_->end(); ++it) { + ranges->push_back(*it); + ++comments_offset_; + } +} + +} // namespace pcrecpp diff --git a/pcre-7.4/pcre_scanner.h b/pcre-7.4/pcre_scanner.h new file mode 100644 index 0000000..f32e9e0 --- /dev/null +++ b/pcre-7.4/pcre_scanner.h @@ -0,0 +1,172 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// +// Regular-expression based scanner for parsing an input stream. +// +// Example 1: parse a sequence of "var = number" entries from input: +// +// Scanner scanner(input); +// string var; +// int number; +// scanner.SetSkipExpression("\\s+"); // Skip any white space we encounter +// while (scanner.Consume("(\\w+) = (\\d+)", &var, &number)) { +// ...; +// } + +#ifndef _PCRE_SCANNER_H +#define _PCRE_SCANNER_H + +#include <assert.h> +#include <string> +#include <vector> + +#include <pcrecpp.h> +#include <pcre_stringpiece.h> + +namespace pcrecpp { + +class Scanner { + public: + Scanner(); + explicit Scanner(const std::string& input); + ~Scanner(); + + // Return current line number. The returned line-number is + // one-based. I.e. it returns 1 + the number of consumed newlines. + // + // Note: this method may be slow. It may take time proportional to + // the size of the input. + int LineNumber() const; + + // Return the byte-offset that the scanner is looking in the + // input data; + int Offset() const; + + // Return true iff the start of the remaining input matches "re" + bool LookingAt(const RE& re) const; + + // Return true iff all of the following are true + // a. the start of the remaining input matches "re", + // b. if any arguments are supplied, matched sub-patterns can be + // parsed and stored into the arguments. + // If it returns true, it skips over the matched input and any + // following input that matches the "skip" regular expression. + bool Consume(const RE& re, + const Arg& arg0 = no_arg, + const Arg& arg1 = no_arg, + const Arg& arg2 = no_arg + // TODO: Allow more arguments? + ); + + // Set the "skip" regular expression. If after consuming some data, + // a prefix of the input matches this RE, it is automatically + // skipped. For example, a programming language scanner would use + // a skip RE that matches white space and comments. + // + // scanner.SetSkipExpression("\\s+|//.*|/[*](.|\n)*?[*]/"); + // + // Skipping repeats as long as it succeeds. We used to let people do + // this by writing "(...)*" in the regular expression, but that added + // up to lots of recursive calls within the pcre library, so now we + // control repetition explicitly via the function call API. + // + // You can pass NULL for "re" if you do not want any data to be skipped. + void Skip(const char* re); // DEPRECATED; does *not* repeat + void SetSkipExpression(const char* re); + + // Temporarily pause "skip"ing. This + // Skip("Foo"); code ; DisableSkip(); code; EnableSkip() + // is similar to + // Skip("Foo"); code ; Skip(NULL); code ; Skip("Foo"); + // but avoids creating/deleting new RE objects. + void DisableSkip(); + + // Reenable previously paused skipping. Any prefix of the input + // that matches the skip pattern is immediately dropped. + void EnableSkip(); + + /***** Special wrappers around SetSkip() for some common idioms *****/ + + // Arranges to skip whitespace, C comments, C++ comments. + // The overall RE is a disjunction of the following REs: + // \\s whitespace + // //.*\n C++ comment + // /[*](.|\n)*?[*]/ C comment (x*? means minimal repetitions of x) + // We get repetition via the semantics of SetSkipExpression, not by using * + void SkipCXXComments() { + SetSkipExpression("\\s|//.*\n|/[*](?:\n|.)*?[*]/"); + } + + void set_save_comments(bool comments) { + save_comments_ = comments; + } + + bool save_comments() { + return save_comments_; + } + + // Append to vector ranges the comments found in the + // byte range [start,end] (inclusive) of the input data. + // Only comments that were extracted entirely within that + // range are returned: no range splitting of atomically-extracted + // comments is performed. + void GetComments(int start, int end, std::vector<StringPiece> *ranges); + + // Append to vector ranges the comments added + // since the last time this was called. This + // functionality is provided for efficiency when + // interleaving scanning with parsing. + void GetNextComments(std::vector<StringPiece> *ranges); + + private: + std::string data_; // All the input data + StringPiece input_; // Unprocessed input + RE* skip_; // If non-NULL, RE for skipping input + bool should_skip_; // If true, use skip_ + bool skip_repeat_; // If true, repeat skip_ as long as it works + bool save_comments_; // If true, aggregate the skip expression + + // the skipped comments + // TODO: later consider requiring that the StringPieces be added + // in order by their start position + std::vector<StringPiece> *comments_; + + // the offset into comments_ that has been returned by GetNextComments + int comments_offset_; + + // helper function to consume *skip_ and honour + // save_comments_ + void ConsumeSkip(); +}; + +} // namespace pcrecpp + +#endif /* _PCRE_SCANNER_H */ diff --git a/pcre-7.4/pcre_scanner_unittest.cc b/pcre-7.4/pcre_scanner_unittest.cc new file mode 100644 index 0000000..284c8ea --- /dev/null +++ b/pcre-7.4/pcre_scanner_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Greg J. Badros +// +// Unittest for scanner, especially GetNextComments and GetComments() +// functionality. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string> +#include <vector> + +#include "pcrecpp.h" +#include "pcre_stringpiece.h" +#include "pcre_scanner.h" + +#define FLAGS_unittest_stack_size 49152 + +// Dies with a fatal error if the two values are not equal. +#define CHECK_EQ(a, b) do { \ + if ( (a) != (b) ) { \ + fprintf(stderr, "%s:%d: Check failed because %s != %s\n", \ + __FILE__, __LINE__, #a, #b); \ + exit(1); \ + } \ +} while (0) + +using std::vector; +using pcrecpp::StringPiece; +using pcrecpp::Scanner; + +static void TestScanner() { + const char input[] = "\n" + "alpha = 1; // this sets alpha\n" + "bravo = 2; // bravo is set here\n" + "gamma = 33; /* and here is gamma */\n"; + + const char *re = "(\\w+) = (\\d+);"; + + Scanner s(input); + string var; + int number; + s.SkipCXXComments(); + s.set_save_comments(true); + vector<StringPiece> comments; + + s.Consume(re, &var, &number); + CHECK_EQ(var, "alpha"); + CHECK_EQ(number, 1); + CHECK_EQ(s.LineNumber(), 3); + s.GetNextComments(&comments); + CHECK_EQ(comments.size(), 1); + CHECK_EQ(comments[0].as_string(), " // this sets alpha\n"); + comments.resize(0); + + s.Consume(re, &var, &number); + CHECK_EQ(var, "bravo"); + CHECK_EQ(number, 2); + s.GetNextComments(&comments); + CHECK_EQ(comments.size(), 1); + CHECK_EQ(comments[0].as_string(), " // bravo is set here\n"); + comments.resize(0); + + s.Consume(re, &var, &number); + CHECK_EQ(var, "gamma"); + CHECK_EQ(number, 33); + s.GetNextComments(&comments); + CHECK_EQ(comments.size(), 1); + CHECK_EQ(comments[0].as_string(), " /* and here is gamma */\n"); + comments.resize(0); + + s.GetComments(0, sizeof(input), &comments); + CHECK_EQ(comments.size(), 3); + CHECK_EQ(comments[0].as_string(), " // this sets alpha\n"); + CHECK_EQ(comments[1].as_string(), " // bravo is set here\n"); + CHECK_EQ(comments[2].as_string(), " /* and here is gamma */\n"); + comments.resize(0); + + s.GetComments(0, strchr(input, '/') - input, &comments); + CHECK_EQ(comments.size(), 0); + comments.resize(0); + + s.GetComments(strchr(input, '/') - input - 1, sizeof(input), + &comments); + CHECK_EQ(comments.size(), 3); + CHECK_EQ(comments[0].as_string(), " // this sets alpha\n"); + CHECK_EQ(comments[1].as_string(), " // bravo is set here\n"); + CHECK_EQ(comments[2].as_string(), " /* and here is gamma */\n"); + comments.resize(0); + + s.GetComments(strchr(input, '/') - input - 1, + strchr(input + 1, '\n') - input + 1, &comments); + CHECK_EQ(comments.size(), 1); + CHECK_EQ(comments[0].as_string(), " // this sets alpha\n"); + comments.resize(0); +} + +static void TestBigComment() { + string input; + for (int i = 0; i < 1024; ++i) { + char buf[1024]; // definitely big enough + sprintf(buf, " # Comment %d\n", i); + input += buf; + } + input += "name = value;\n"; + + Scanner s(input.c_str()); + s.SetSkipExpression("\\s+|#.*\n"); + + string name; + string value; + s.Consume("(\\w+) = (\\w+);", &name, &value); + CHECK_EQ(name, "name"); + CHECK_EQ(value, "value"); +} + +// TODO: also test scanner and big-comment in a thread with a +// small stack size + +int main(int argc, char** argv) { + TestScanner(); + TestBigComment(); + + // Done + printf("OK\n"); + + return 0; +} diff --git a/pcre-7.4/pcre_stringpiece.cc b/pcre-7.4/pcre_stringpiece.cc new file mode 100644 index 0000000..67c0f1f --- /dev/null +++ b/pcre-7.4/pcre_stringpiece.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wilsonh@google.com (Wilson Hsieh) +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <iostream> +#include "pcrecpp_internal.h" +#include "pcre_stringpiece.h" + +std::ostream& operator<<(std::ostream& o, const pcrecpp::StringPiece& piece) { + return (o << piece.as_string()); +} diff --git a/pcre-7.4/pcre_stringpiece.h b/pcre-7.4/pcre_stringpiece.h new file mode 100644 index 0000000..599a351 --- /dev/null +++ b/pcre-7.4/pcre_stringpiece.h @@ -0,0 +1,177 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// +// A string like object that points into another piece of memory. +// Useful for providing an interface that allows clients to easily +// pass in either a "const char*" or a "string". +// +// Arghh! I wish C++ literals were automatically of type "string". + +#ifndef _PCRE_STRINGPIECE_H +#define _PCRE_STRINGPIECE_H + +#include <string.h> +#include <string> +#include <iosfwd> // for ostream forward-declaration + +#if 0 +#define HAVE_TYPE_TRAITS +#include <type_traits.h> +#elif 0 +#define HAVE_TYPE_TRAITS +#include <bits/type_traits.h> +#endif + +#include <pcre.h> + +using std::string; + +namespace pcrecpp { + +class PCRECPP_EXP_DEFN StringPiece { + private: + const char* ptr_; + int length_; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() + : ptr_(NULL), length_(0) { } + StringPiece(const char* str) + : ptr_(str), length_(static_cast<int>(strlen(ptr_))) { } + StringPiece(const unsigned char* str) + : ptr_(reinterpret_cast<const char*>(str)), + length_(static_cast<int>(strlen(ptr_))) { } + StringPiece(const string& str) + : ptr_(str.data()), length_(static_cast<int>(str.size())) { } + StringPiece(const char* offset, int len) + : ptr_(offset), length_(len) { } + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. Use "as_string().c_str()" if you really need to do + // this. Or better yet, change your routine so it does not rely on NUL + // termination. + const char* data() const { return ptr_; } + int size() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { ptr_ = NULL; length_ = 0; } + void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; } + void set(const char* str) { + ptr_ = str; + length_ = static_cast<int>(strlen(str)); + } + void set(const void* buffer, int len) { + ptr_ = reinterpret_cast<const char*>(buffer); + length_ = len; + } + + char operator[](int i) const { return ptr_[i]; } + + void remove_prefix(int n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(int n) { + length_ -= n; + } + + bool operator==(const StringPiece& x) const { + return ((length_ == x.length_) && + (memcmp(ptr_, x.ptr_, length_) == 0)); + } + bool operator!=(const StringPiece& x) const { + return !(*this == x); + } + +#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp) \ + bool operator cmp (const StringPiece& x) const { \ + int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \ + return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \ + } + STRINGPIECE_BINARY_PREDICATE(<, <); + STRINGPIECE_BINARY_PREDICATE(<=, <); + STRINGPIECE_BINARY_PREDICATE(>=, >); + STRINGPIECE_BINARY_PREDICATE(>, >); +#undef STRINGPIECE_BINARY_PREDICATE + + int compare(const StringPiece& x) const { + int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + string as_string() const { + return string(data(), size()); + } + + void CopyToString(string* target) const { + target->assign(ptr_, length_); + } + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const { + return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0)); + } +}; + +} // namespace pcrecpp + +// ------------------------------------------------------------------ +// Functions used to create STL containers that use StringPiece +// Remember that a StringPiece's lifetime had better be less than +// that of the underlying string or char*. If it is not, then you +// cannot safely store a StringPiece into an STL container +// ------------------------------------------------------------------ + +#ifdef HAVE_TYPE_TRAITS +// This makes vector<StringPiece> really fast for some STL implementations +template<> struct __type_traits<pcrecpp::StringPiece> { + typedef __true_type has_trivial_default_constructor; + typedef __true_type has_trivial_copy_constructor; + typedef __true_type has_trivial_assignment_operator; + typedef __true_type has_trivial_destructor; + typedef __true_type is_POD_type; +}; +#endif + +// allow StringPiece to be logged +std::ostream& operator<<(std::ostream& o, const pcrecpp::StringPiece& piece); + +#endif /* _PCRE_STRINGPIECE_H */ diff --git a/pcre-7.4/pcre_stringpiece.h.in b/pcre-7.4/pcre_stringpiece.h.in new file mode 100644 index 0000000..b017661 --- /dev/null +++ b/pcre-7.4/pcre_stringpiece.h.in @@ -0,0 +1,177 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// +// A string like object that points into another piece of memory. +// Useful for providing an interface that allows clients to easily +// pass in either a "const char*" or a "string". +// +// Arghh! I wish C++ literals were automatically of type "string". + +#ifndef _PCRE_STRINGPIECE_H +#define _PCRE_STRINGPIECE_H + +#include <string.h> +#include <string> +#include <iosfwd> // for ostream forward-declaration + +#if @pcre_have_type_traits@ +#define HAVE_TYPE_TRAITS +#include <type_traits.h> +#elif @pcre_have_bits_type_traits@ +#define HAVE_TYPE_TRAITS +#include <bits/type_traits.h> +#endif + +#include <pcre.h> + +using std::string; + +namespace pcrecpp { + +class PCRECPP_EXP_DEFN StringPiece { + private: + const char* ptr_; + int length_; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() + : ptr_(NULL), length_(0) { } + StringPiece(const char* str) + : ptr_(str), length_(static_cast<int>(strlen(ptr_))) { } + StringPiece(const unsigned char* str) + : ptr_(reinterpret_cast<const char*>(str)), + length_(static_cast<int>(strlen(ptr_))) { } + StringPiece(const string& str) + : ptr_(str.data()), length_(static_cast<int>(str.size())) { } + StringPiece(const char* offset, int len) + : ptr_(offset), length_(len) { } + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. Use "as_string().c_str()" if you really need to do + // this. Or better yet, change your routine so it does not rely on NUL + // termination. + const char* data() const { return ptr_; } + int size() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { ptr_ = NULL; length_ = 0; } + void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; } + void set(const char* str) { + ptr_ = str; + length_ = static_cast<int>(strlen(str)); + } + void set(const void* buffer, int len) { + ptr_ = reinterpret_cast<const char*>(buffer); + length_ = len; + } + + char operator[](int i) const { return ptr_[i]; } + + void remove_prefix(int n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(int n) { + length_ -= n; + } + + bool operator==(const StringPiece& x) const { + return ((length_ == x.length_) && + (memcmp(ptr_, x.ptr_, length_) == 0)); + } + bool operator!=(const StringPiece& x) const { + return !(*this == x); + } + +#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp) \ + bool operator cmp (const StringPiece& x) const { \ + int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \ + return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \ + } + STRINGPIECE_BINARY_PREDICATE(<, <); + STRINGPIECE_BINARY_PREDICATE(<=, <); + STRINGPIECE_BINARY_PREDICATE(>=, >); + STRINGPIECE_BINARY_PREDICATE(>, >); +#undef STRINGPIECE_BINARY_PREDICATE + + int compare(const StringPiece& x) const { + int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; + } + + string as_string() const { + return string(data(), size()); + } + + void CopyToString(string* target) const { + target->assign(ptr_, length_); + } + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const { + return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0)); + } +}; + +} // namespace pcrecpp + +// ------------------------------------------------------------------ +// Functions used to create STL containers that use StringPiece +// Remember that a StringPiece's lifetime had better be less than +// that of the underlying string or char*. If it is not, then you +// cannot safely store a StringPiece into an STL container +// ------------------------------------------------------------------ + +#ifdef HAVE_TYPE_TRAITS +// This makes vector<StringPiece> really fast for some STL implementations +template<> struct __type_traits<pcrecpp::StringPiece> { + typedef __true_type has_trivial_default_constructor; + typedef __true_type has_trivial_copy_constructor; + typedef __true_type has_trivial_assignment_operator; + typedef __true_type has_trivial_destructor; + typedef __true_type is_POD_type; +}; +#endif + +// allow StringPiece to be logged +std::ostream& operator<<(std::ostream& o, const pcrecpp::StringPiece& piece); + +#endif /* _PCRE_STRINGPIECE_H */ diff --git a/pcre-7.4/pcre_stringpiece_unittest.cc b/pcre-7.4/pcre_stringpiece_unittest.cc new file mode 100644 index 0000000..1e821ab --- /dev/null +++ b/pcre-7.4/pcre_stringpiece_unittest.cc @@ -0,0 +1,151 @@ +// Copyright 2003 and onwards Google Inc. +// Author: Sanjay Ghemawat + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <map> +#include <algorithm> // for make_pair + +#include "pcrecpp.h" +#include "pcre_stringpiece.h" + +// CHECK dies with a fatal error if condition is not true. It is *not* +// controlled by NDEBUG, so the check will be executed regardless of +// compilation mode. Therefore, it is safe to do things like: +// CHECK(fp->Write(x) == 4) +#define CHECK(condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s:%d: Check failed: %s\n", \ + __FILE__, __LINE__, #condition); \ + exit(1); \ + } \ +} while (0) + +using std::map; +using std::make_pair; +using pcrecpp::StringPiece; + +static void CheckSTLComparator() { + string s1("foo"); + string s2("bar"); + string s3("baz"); + + StringPiece p1(s1); + StringPiece p2(s2); + StringPiece p3(s3); + + typedef map<StringPiece, int> TestMap; + TestMap map; + + map.insert(make_pair(p1, 0)); + map.insert(make_pair(p2, 1)); + map.insert(make_pair(p3, 2)); + CHECK(map.size() == 3); + + TestMap::const_iterator iter = map.begin(); + CHECK(iter->second == 1); + ++iter; + CHECK(iter->second == 2); + ++iter; + CHECK(iter->second == 0); + ++iter; + CHECK(iter == map.end()); + + TestMap::iterator new_iter = map.find("zot"); + CHECK(new_iter == map.end()); + + new_iter = map.find("bar"); + CHECK(new_iter != map.end()); + + map.erase(new_iter); + CHECK(map.size() == 2); + + iter = map.begin(); + CHECK(iter->second == 2); + ++iter; + CHECK(iter->second == 0); + ++iter; + CHECK(iter == map.end()); +} + +static void CheckComparisonOperators() { +#define CMP_Y(op, x, y) \ + CHECK( (StringPiece((x)) op StringPiece((y)))); \ + CHECK( (StringPiece((x)).compare(StringPiece((y))) op 0)) + +#define CMP_N(op, x, y) \ + CHECK(!(StringPiece((x)) op StringPiece((y)))); \ + CHECK(!(StringPiece((x)).compare(StringPiece((y))) op 0)) + + CMP_Y(==, "", ""); + CMP_Y(==, "a", "a"); + CMP_Y(==, "aa", "aa"); + CMP_N(==, "a", ""); + CMP_N(==, "", "a"); + CMP_N(==, "a", "b"); + CMP_N(==, "a", "aa"); + CMP_N(==, "aa", "a"); + + CMP_N(!=, "", ""); + CMP_N(!=, "a", "a"); + CMP_N(!=, "aa", "aa"); + CMP_Y(!=, "a", ""); + CMP_Y(!=, "", "a"); + CMP_Y(!=, "a", "b"); + CMP_Y(!=, "a", "aa"); + CMP_Y(!=, "aa", "a"); + + CMP_Y(<, "a", "b"); + CMP_Y(<, "a", "aa"); + CMP_Y(<, "aa", "b"); + CMP_Y(<, "aa", "bb"); + CMP_N(<, "a", "a"); + CMP_N(<, "b", "a"); + CMP_N(<, "aa", "a"); + CMP_N(<, "b", "aa"); + CMP_N(<, "bb", "aa"); + + CMP_Y(<=, "a", "a"); + CMP_Y(<=, "a", "b"); + CMP_Y(<=, "a", "aa"); + CMP_Y(<=, "aa", "b"); + CMP_Y(<=, "aa", "bb"); + CMP_N(<=, "b", "a"); + CMP_N(<=, "aa", "a"); + CMP_N(<=, "b", "aa"); + CMP_N(<=, "bb", "aa"); + + CMP_N(>=, "a", "b"); + CMP_N(>=, "a", "aa"); + CMP_N(>=, "aa", "b"); + CMP_N(>=, "aa", "bb"); + CMP_Y(>=, "a", "a"); + CMP_Y(>=, "b", "a"); + CMP_Y(>=, "aa", "a"); + CMP_Y(>=, "b", "aa"); + CMP_Y(>=, "bb", "aa"); + + CMP_N(>, "a", "a"); + CMP_N(>, "a", "b"); + CMP_N(>, "a", "aa"); + CMP_N(>, "aa", "b"); + CMP_N(>, "aa", "bb"); + CMP_Y(>, "b", "a"); + CMP_Y(>, "aa", "a"); + CMP_Y(>, "b", "aa"); + CMP_Y(>, "bb", "aa"); + +#undef CMP_Y +#undef CMP_N +} + +int main(int argc, char** argv) { + CheckComparisonOperators(); + CheckSTLComparator(); + + printf("OK\n"); + return 0; +} diff --git a/pcre-7.4/pcre_study.c b/pcre-7.4/pcre_study.c new file mode 100644 index 0000000..1c28384 --- /dev/null +++ b/pcre-7.4/pcre_study.c @@ -0,0 +1,579 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_study(), along with local +supporting functions. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/* Returns from set_start_bits() */ + +enum { SSB_FAIL, SSB_DONE, SSB_CONTINUE }; + + +/************************************************* +* Set a bit and maybe its alternate case * +*************************************************/ + +/* Given a character, set its bit in the table, and also the bit for the other +version of a letter if we are caseless. + +Arguments: + start_bits points to the bit map + c is the character + caseless the caseless flag + cd the block with char table pointers + +Returns: nothing +*/ + +static void +set_bit(uschar *start_bits, unsigned int c, BOOL caseless, compile_data *cd) +{ +start_bits[c/8] |= (1 << (c&7)); +if (caseless && (cd->ctypes[c] & ctype_letter) != 0) + start_bits[cd->fcc[c]/8] |= (1 << (cd->fcc[c]&7)); +} + + + +/************************************************* +* Create bitmap of starting bytes * +*************************************************/ + +/* This function scans a compiled unanchored expression recursively and +attempts to build a bitmap of the set of possible starting bytes. As time goes +by, we may be able to get more clever at doing this. The SSB_CONTINUE return is +useful for parenthesized groups in patterns such as (a*)b where the group +provides some optional starting bytes but scanning must continue at the outer +level to find at least one mandatory byte. At the outermost level, this +function fails unless the result is SSB_DONE. + +Arguments: + code points to an expression + start_bits points to a 32-byte table, initialized to 0 + caseless the current state of the caseless flag + utf8 TRUE if in UTF-8 mode + cd the block with char table pointers + +Returns: SSB_FAIL => Failed to find any starting bytes + SSB_DONE => Found mandatory starting bytes + SSB_CONTINUE => Found optional starting bytes +*/ + +static int +set_start_bits(const uschar *code, uschar *start_bits, BOOL caseless, + BOOL utf8, compile_data *cd) +{ +register int c; +int yield = SSB_DONE; + +#if 0 +/* ========================================================================= */ +/* The following comment and code was inserted in January 1999. In May 2006, +when it was observed to cause compiler warnings about unused values, I took it +out again. If anybody is still using OS/2, they will have to put it back +manually. */ + +/* This next statement and the later reference to dummy are here in order to +trick the optimizer of the IBM C compiler for OS/2 into generating correct +code. Apparently IBM isn't going to fix the problem, and we would rather not +disable optimization (in this module it actually makes a big difference, and +the pcre module can use all the optimization it can get). */ + +volatile int dummy; +/* ========================================================================= */ +#endif + +do + { + const uschar *tcode = code + (((int)*code == OP_CBRA)? 3:1) + LINK_SIZE; + BOOL try_next = TRUE; + + while (try_next) /* Loop for items in this branch */ + { + int rc; + switch(*tcode) + { + /* Fail if we reach something we don't understand */ + + default: + return SSB_FAIL; + + /* If we hit a bracket or a positive lookahead assertion, recurse to set + bits from within the subpattern. If it can't find anything, we have to + give up. If it finds some mandatory character(s), we are done for this + branch. Otherwise, carry on scanning after the subpattern. */ + + case OP_BRA: + case OP_SBRA: + case OP_CBRA: + case OP_SCBRA: + case OP_ONCE: + case OP_ASSERT: + rc = set_start_bits(tcode, start_bits, caseless, utf8, cd); + if (rc == SSB_FAIL) return SSB_FAIL; + if (rc == SSB_DONE) try_next = FALSE; else + { + do tcode += GET(tcode, 1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + } + break; + + /* If we hit ALT or KET, it means we haven't found anything mandatory in + this branch, though we might have found something optional. For ALT, we + continue with the next alternative, but we have to arrange that the final + result from subpattern is SSB_CONTINUE rather than SSB_DONE. For KET, + return SSB_CONTINUE: if this is the top level, that indicates failure, + but after a nested subpattern, it causes scanning to continue. */ + + case OP_ALT: + yield = SSB_CONTINUE; + try_next = FALSE; + break; + + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + return SSB_CONTINUE; + + /* Skip over callout */ + + case OP_CALLOUT: + tcode += 2 + 2*LINK_SIZE; + break; + + /* Skip over lookbehind and negative lookahead assertions */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + do tcode += GET(tcode, 1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + break; + + /* Skip over an option setting, changing the caseless flag */ + + case OP_OPT: + caseless = (tcode[1] & PCRE_CASELESS) != 0; + tcode += 2; + break; + + /* BRAZERO does the bracket, but carries on. */ + + case OP_BRAZERO: + case OP_BRAMINZERO: + if (set_start_bits(++tcode, start_bits, caseless, utf8, cd) == SSB_FAIL) + return SSB_FAIL; +/* ========================================================================= + See the comment at the head of this function concerning the next line, + which was an old fudge for the benefit of OS/2. + dummy = 1; + ========================================================================= */ + do tcode += GET(tcode,1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + break; + + /* Single-char * or ? sets the bit and tries the next item */ + + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + set_bit(start_bits, tcode[1], caseless, cd); + tcode += 2; +#ifdef SUPPORT_UTF8 + if (utf8 && tcode[-1] >= 0xc0) + tcode += _pcre_utf8_table4[tcode[-1] & 0x3f]; +#endif + break; + + /* Single-char upto sets the bit and tries the next */ + + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + set_bit(start_bits, tcode[3], caseless, cd); + tcode += 4; +#ifdef SUPPORT_UTF8 + if (utf8 && tcode[-1] >= 0xc0) + tcode += _pcre_utf8_table4[tcode[-1] & 0x3f]; +#endif + break; + + /* At least one single char sets the bit and stops */ + + case OP_EXACT: /* Fall through */ + tcode += 2; + + case OP_CHAR: + case OP_CHARNC: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + set_bit(start_bits, tcode[1], caseless, cd); + try_next = FALSE; + break; + + /* Single character type sets the bits and stops */ + + case OP_NOT_DIGIT: + for (c = 0; c < 32; c++) + start_bits[c] |= ~cd->cbits[c+cbit_digit]; + try_next = FALSE; + break; + + case OP_DIGIT: + for (c = 0; c < 32; c++) + start_bits[c] |= cd->cbits[c+cbit_digit]; + try_next = FALSE; + break; + + /* The cbit_space table has vertical tab as whitespace; we have to + discard it. */ + + case OP_NOT_WHITESPACE: + for (c = 0; c < 32; c++) + { + int d = cd->cbits[c+cbit_space]; + if (c == 1) d &= ~0x08; + start_bits[c] |= ~d; + } + try_next = FALSE; + break; + + /* The cbit_space table has vertical tab as whitespace; we have to + discard it. */ + + case OP_WHITESPACE: + for (c = 0; c < 32; c++) + { + int d = cd->cbits[c+cbit_space]; + if (c == 1) d &= ~0x08; + start_bits[c] |= d; + } + try_next = FALSE; + break; + + case OP_NOT_WORDCHAR: + for (c = 0; c < 32; c++) + start_bits[c] |= ~cd->cbits[c+cbit_word]; + try_next = FALSE; + break; + + case OP_WORDCHAR: + for (c = 0; c < 32; c++) + start_bits[c] |= cd->cbits[c+cbit_word]; + try_next = FALSE; + break; + + /* One or more character type fudges the pointer and restarts, knowing + it will hit a single character type and stop there. */ + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + tcode++; + break; + + case OP_TYPEEXACT: + tcode += 3; + break; + + /* Zero or more repeats of character types set the bits and then + try again. */ + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + tcode += 2; /* Fall through */ + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + switch(tcode[1]) + { + case OP_ANY: + return SSB_FAIL; + + case OP_NOT_DIGIT: + for (c = 0; c < 32; c++) + start_bits[c] |= ~cd->cbits[c+cbit_digit]; + break; + + case OP_DIGIT: + for (c = 0; c < 32; c++) + start_bits[c] |= cd->cbits[c+cbit_digit]; + break; + + /* The cbit_space table has vertical tab as whitespace; we have to + discard it. */ + + case OP_NOT_WHITESPACE: + for (c = 0; c < 32; c++) + { + int d = cd->cbits[c+cbit_space]; + if (c == 1) d &= ~0x08; + start_bits[c] |= ~d; + } + break; + + /* The cbit_space table has vertical tab as whitespace; we have to + discard it. */ + + case OP_WHITESPACE: + for (c = 0; c < 32; c++) + { + int d = cd->cbits[c+cbit_space]; + if (c == 1) d &= ~0x08; + start_bits[c] |= d; + } + break; + + case OP_NOT_WORDCHAR: + for (c = 0; c < 32; c++) + start_bits[c] |= ~cd->cbits[c+cbit_word]; + break; + + case OP_WORDCHAR: + for (c = 0; c < 32; c++) + start_bits[c] |= cd->cbits[c+cbit_word]; + break; + } + + tcode += 2; + break; + + /* Character class where all the information is in a bit map: set the + bits and either carry on or not, according to the repeat count. If it was + a negative class, and we are operating with UTF-8 characters, any byte + with a value >= 0xc4 is a potentially valid starter because it starts a + character with a value > 255. */ + + case OP_NCLASS: +#ifdef SUPPORT_UTF8 + if (utf8) + { + start_bits[24] |= 0xf0; /* Bits for 0xc4 - 0xc8 */ + memset(start_bits+25, 0xff, 7); /* Bits for 0xc9 - 0xff */ + } +#endif + /* Fall through */ + + case OP_CLASS: + { + tcode++; + + /* In UTF-8 mode, the bits in a bit map correspond to character + values, not to byte values. However, the bit map we are constructing is + for byte values. So we have to do a conversion for characters whose + value is > 127. In fact, there are only two possible starting bytes for + characters in the range 128 - 255. */ + +#ifdef SUPPORT_UTF8 + if (utf8) + { + for (c = 0; c < 16; c++) start_bits[c] |= tcode[c]; + for (c = 128; c < 256; c++) + { + if ((tcode[c/8] && (1 << (c&7))) != 0) + { + int d = (c >> 6) | 0xc0; /* Set bit for this starter */ + start_bits[d/8] |= (1 << (d&7)); /* and then skip on to the */ + c = (c & 0xc0) + 0x40 - 1; /* next relevant character. */ + } + } + } + + /* In non-UTF-8 mode, the two bit maps are completely compatible. */ + + else +#endif + { + for (c = 0; c < 32; c++) start_bits[c] |= tcode[c]; + } + + /* Advance past the bit map, and act on what follows */ + + tcode += 32; + switch (*tcode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + tcode++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + if (((tcode[1] << 8) + tcode[2]) == 0) tcode += 5; + else try_next = FALSE; + break; + + default: + try_next = FALSE; + break; + } + } + break; /* End of bitmap class handling */ + + } /* End of switch */ + } /* End of try_next loop */ + + code += GET(code, 1); /* Advance to next branch */ + } +while (*code == OP_ALT); +return yield; +} + + + +/************************************************* +* Study a compiled expression * +*************************************************/ + +/* This function is handed a compiled expression that it must study to produce +information that will speed up the matching. It returns a pcre_extra block +which then gets handed back to pcre_exec(). + +Arguments: + re points to the compiled expression + options contains option bits + errorptr points to where to place error messages; + set NULL unless error + +Returns: pointer to a pcre_extra block, with study_data filled in and the + appropriate flag set; + NULL on error or if no optimization possible +*/ + +PCRE_EXP_DEFN pcre_extra * +pcre_study(const pcre *external_re, int options, const char **errorptr) +{ +uschar start_bits[32]; +pcre_extra *extra; +pcre_study_data *study; +const uschar *tables; +uschar *code; +compile_data compile_block; +const real_pcre *re = (const real_pcre *)external_re; + +*errorptr = NULL; + +if (re == NULL || re->magic_number != MAGIC_NUMBER) + { + *errorptr = "argument is not a compiled regular expression"; + return NULL; + } + +if ((options & ~PUBLIC_STUDY_OPTIONS) != 0) + { + *errorptr = "unknown or incorrect option bit(s) set"; + return NULL; + } + +code = (uschar *)re + re->name_table_offset + + (re->name_count * re->name_entry_size); + +/* For an anchored pattern, or an unanchored pattern that has a first char, or +a multiline pattern that matches only at "line starts", no further processing +at present. */ + +if ((re->options & PCRE_ANCHORED) != 0 || + (re->flags & (PCRE_FIRSTSET|PCRE_STARTLINE)) != 0) + return NULL; + +/* Set the character tables in the block that is passed around */ + +tables = re->tables; +if (tables == NULL) + (void)pcre_fullinfo(external_re, NULL, PCRE_INFO_DEFAULT_TABLES, + (void *)(&tables)); + +compile_block.lcc = tables + lcc_offset; +compile_block.fcc = tables + fcc_offset; +compile_block.cbits = tables + cbits_offset; +compile_block.ctypes = tables + ctypes_offset; + +/* See if we can find a fixed set of initial characters for the pattern. */ + +memset(start_bits, 0, 32 * sizeof(uschar)); +if (set_start_bits(code, start_bits, (re->options & PCRE_CASELESS) != 0, + (re->options & PCRE_UTF8) != 0, &compile_block) != SSB_DONE) return NULL; + +/* Get a pcre_extra block and a pcre_study_data block. The study data is put in +the latter, which is pointed to by the former, which may also get additional +data set later by the calling program. At the moment, the size of +pcre_study_data is fixed. We nevertheless save it in a field for returning via +the pcre_fullinfo() function so that if it becomes variable in the future, we +don't have to change that code. */ + +extra = (pcre_extra *)(pcre_malloc) + (sizeof(pcre_extra) + sizeof(pcre_study_data)); + +if (extra == NULL) + { + *errorptr = "failed to get memory"; + return NULL; + } + +study = (pcre_study_data *)((char *)extra + sizeof(pcre_extra)); +extra->flags = PCRE_EXTRA_STUDY_DATA; +extra->study_data = study; + +study->size = sizeof(pcre_study_data); +study->options = PCRE_STUDY_MAPPED; +memcpy(study->start_bits, start_bits, sizeof(start_bits)); + +return extra; +} + +/* End of pcre_study.c */ diff --git a/pcre-7.4/pcre_tables.c b/pcre-7.4/pcre_tables.c new file mode 100644 index 0000000..4b14fd1 --- /dev/null +++ b/pcre-7.4/pcre_tables.c @@ -0,0 +1,318 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains some fixed tables that are used by more than one of the +PCRE code modules. The tables are also #included by the pcretest program, which +uses macros to change their names from _pcre_xxx to xxxx, thereby avoiding name +clashes with the library. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/* Table of sizes for the fixed-length opcodes. It's defined in a macro so that +the definition is next to the definition of the opcodes in pcre_internal.h. */ + +const uschar _pcre_OP_lengths[] = { OP_LENGTHS }; + + + +/************************************************* +* Tables for UTF-8 support * +*************************************************/ + +/* These are the breakpoints for different numbers of bytes in a UTF-8 +character. */ + +#ifdef SUPPORT_UTF8 + +const int _pcre_utf8_table1[] = + { 0x7f, 0x7ff, 0xffff, 0x1fffff, 0x3ffffff, 0x7fffffff}; + +const int _pcre_utf8_table1_size = sizeof(_pcre_utf8_table1)/sizeof(int); + +/* These are the indicator bits and the mask for the data bits to set in the +first byte of a character, indexed by the number of additional bytes. */ + +const int _pcre_utf8_table2[] = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}; +const int _pcre_utf8_table3[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01}; + +/* Table of the number of extra bytes, indexed by the first byte masked with +0x3f. The highest number for a valid UTF-8 first byte is in fact 0x3d. */ + +const uschar _pcre_utf8_table4[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; + +/* The pcre_utt[] table below translates Unicode property names into type and +code values. It is searched by binary chop, so must be in collating sequence of +name. Originally, the table contained pointers to the name strings in the first +field of each entry. However, that leads to a large number of relocations when +a shared library is dynamically loaded. A significant reduction is made by +putting all the names into a single, large string and then using offsets in the +table itself. Maintenance is more error-prone, but frequent changes to this +data is unlikely. */ + +const char _pcre_utt_names[] = + "Any\0" + "Arabic\0" + "Armenian\0" + "Balinese\0" + "Bengali\0" + "Bopomofo\0" + "Braille\0" + "Buginese\0" + "Buhid\0" + "C\0" + "Canadian_Aboriginal\0" + "Cc\0" + "Cf\0" + "Cherokee\0" + "Cn\0" + "Co\0" + "Common\0" + "Coptic\0" + "Cs\0" + "Cuneiform\0" + "Cypriot\0" + "Cyrillic\0" + "Deseret\0" + "Devanagari\0" + "Ethiopic\0" + "Georgian\0" + "Glagolitic\0" + "Gothic\0" + "Greek\0" + "Gujarati\0" + "Gurmukhi\0" + "Han\0" + "Hangul\0" + "Hanunoo\0" + "Hebrew\0" + "Hiragana\0" + "Inherited\0" + "Kannada\0" + "Katakana\0" + "Kharoshthi\0" + "Khmer\0" + "L\0" + "L&\0" + "Lao\0" + "Latin\0" + "Limbu\0" + "Linear_B\0" + "Ll\0" + "Lm\0" + "Lo\0" + "Lt\0" + "Lu\0" + "M\0" + "Malayalam\0" + "Mc\0" + "Me\0" + "Mn\0" + "Mongolian\0" + "Myanmar\0" + "N\0" + "Nd\0" + "New_Tai_Lue\0" + "Nko\0" + "Nl\0" + "No\0" + "Ogham\0" + "Old_Italic\0" + "Old_Persian\0" + "Oriya\0" + "Osmanya\0" + "P\0" + "Pc\0" + "Pd\0" + "Pe\0" + "Pf\0" + "Phags_Pa\0" + "Phoenician\0" + "Pi\0" + "Po\0" + "Ps\0" + "Runic\0" + "S\0" + "Sc\0" + "Shavian\0" + "Sinhala\0" + "Sk\0" + "Sm\0" + "So\0" + "Syloti_Nagri\0" + "Syriac\0" + "Tagalog\0" + "Tagbanwa\0" + "Tai_Le\0" + "Tamil\0" + "Telugu\0" + "Thaana\0" + "Thai\0" + "Tibetan\0" + "Tifinagh\0" + "Ugaritic\0" + "Yi\0" + "Z\0" + "Zl\0" + "Zp\0" + "Zs\0"; + +const ucp_type_table _pcre_utt[] = { + { 0, PT_ANY, 0 }, + { 4, PT_SC, ucp_Arabic }, + { 11, PT_SC, ucp_Armenian }, + { 20, PT_SC, ucp_Balinese }, + { 29, PT_SC, ucp_Bengali }, + { 37, PT_SC, ucp_Bopomofo }, + { 46, PT_SC, ucp_Braille }, + { 54, PT_SC, ucp_Buginese }, + { 63, PT_SC, ucp_Buhid }, + { 69, PT_GC, ucp_C }, + { 71, PT_SC, ucp_Canadian_Aboriginal }, + { 91, PT_PC, ucp_Cc }, + { 94, PT_PC, ucp_Cf }, + { 97, PT_SC, ucp_Cherokee }, + { 106, PT_PC, ucp_Cn }, + { 109, PT_PC, ucp_Co }, + { 112, PT_SC, ucp_Common }, + { 119, PT_SC, ucp_Coptic }, + { 126, PT_PC, ucp_Cs }, + { 129, PT_SC, ucp_Cuneiform }, + { 139, PT_SC, ucp_Cypriot }, + { 147, PT_SC, ucp_Cyrillic }, + { 156, PT_SC, ucp_Deseret }, + { 164, PT_SC, ucp_Devanagari }, + { 175, PT_SC, ucp_Ethiopic }, + { 184, PT_SC, ucp_Georgian }, + { 193, PT_SC, ucp_Glagolitic }, + { 204, PT_SC, ucp_Gothic }, + { 211, PT_SC, ucp_Greek }, + { 217, PT_SC, ucp_Gujarati }, + { 226, PT_SC, ucp_Gurmukhi }, + { 235, PT_SC, ucp_Han }, + { 239, PT_SC, ucp_Hangul }, + { 246, PT_SC, ucp_Hanunoo }, + { 254, PT_SC, ucp_Hebrew }, + { 261, PT_SC, ucp_Hiragana }, + { 270, PT_SC, ucp_Inherited }, + { 280, PT_SC, ucp_Kannada }, + { 288, PT_SC, ucp_Katakana }, + { 297, PT_SC, ucp_Kharoshthi }, + { 308, PT_SC, ucp_Khmer }, + { 314, PT_GC, ucp_L }, + { 316, PT_LAMP, 0 }, + { 319, PT_SC, ucp_Lao }, + { 323, PT_SC, ucp_Latin }, + { 329, PT_SC, ucp_Limbu }, + { 335, PT_SC, ucp_Linear_B }, + { 344, PT_PC, ucp_Ll }, + { 347, PT_PC, ucp_Lm }, + { 350, PT_PC, ucp_Lo }, + { 353, PT_PC, ucp_Lt }, + { 356, PT_PC, ucp_Lu }, + { 359, PT_GC, ucp_M }, + { 361, PT_SC, ucp_Malayalam }, + { 371, PT_PC, ucp_Mc }, + { 374, PT_PC, ucp_Me }, + { 377, PT_PC, ucp_Mn }, + { 380, PT_SC, ucp_Mongolian }, + { 390, PT_SC, ucp_Myanmar }, + { 398, PT_GC, ucp_N }, + { 400, PT_PC, ucp_Nd }, + { 403, PT_SC, ucp_New_Tai_Lue }, + { 415, PT_SC, ucp_Nko }, + { 419, PT_PC, ucp_Nl }, + { 422, PT_PC, ucp_No }, + { 425, PT_SC, ucp_Ogham }, + { 431, PT_SC, ucp_Old_Italic }, + { 442, PT_SC, ucp_Old_Persian }, + { 454, PT_SC, ucp_Oriya }, + { 460, PT_SC, ucp_Osmanya }, + { 468, PT_GC, ucp_P }, + { 470, PT_PC, ucp_Pc }, + { 473, PT_PC, ucp_Pd }, + { 476, PT_PC, ucp_Pe }, + { 479, PT_PC, ucp_Pf }, + { 482, PT_SC, ucp_Phags_Pa }, + { 491, PT_SC, ucp_Phoenician }, + { 502, PT_PC, ucp_Pi }, + { 505, PT_PC, ucp_Po }, + { 508, PT_PC, ucp_Ps }, + { 511, PT_SC, ucp_Runic }, + { 517, PT_GC, ucp_S }, + { 519, PT_PC, ucp_Sc }, + { 522, PT_SC, ucp_Shavian }, + { 530, PT_SC, ucp_Sinhala }, + { 538, PT_PC, ucp_Sk }, + { 541, PT_PC, ucp_Sm }, + { 544, PT_PC, ucp_So }, + { 547, PT_SC, ucp_Syloti_Nagri }, + { 560, PT_SC, ucp_Syriac }, + { 567, PT_SC, ucp_Tagalog }, + { 575, PT_SC, ucp_Tagbanwa }, + { 584, PT_SC, ucp_Tai_Le }, + { 591, PT_SC, ucp_Tamil }, + { 597, PT_SC, ucp_Telugu }, + { 604, PT_SC, ucp_Thaana }, + { 611, PT_SC, ucp_Thai }, + { 616, PT_SC, ucp_Tibetan }, + { 624, PT_SC, ucp_Tifinagh }, + { 633, PT_SC, ucp_Ugaritic }, + { 642, PT_SC, ucp_Yi }, + { 645, PT_GC, ucp_Z }, + { 647, PT_PC, ucp_Zl }, + { 650, PT_PC, ucp_Zp }, + { 653, PT_PC, ucp_Zs } +}; + +const int _pcre_utt_size = sizeof(_pcre_utt)/sizeof(ucp_type_table); + +#endif /* SUPPORT_UTF8 */ + +/* End of pcre_tables.c */ diff --git a/pcre-7.4/pcre_try_flipped.c b/pcre-7.4/pcre_try_flipped.c new file mode 100644 index 0000000..412902b --- /dev/null +++ b/pcre-7.4/pcre_try_flipped.c @@ -0,0 +1,137 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains an internal function that tests a compiled pattern to +see if it was compiled with the opposite endianness. If so, it uses an +auxiliary local function to flip the appropriate bytes. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Flip bytes in an integer * +*************************************************/ + +/* This function is called when the magic number in a regex doesn't match, in +order to flip its bytes to see if we are dealing with a pattern that was +compiled on a host of different endianness. If so, this function is used to +flip other byte values. + +Arguments: + value the number to flip + n the number of bytes to flip (assumed to be 2 or 4) + +Returns: the flipped value +*/ + +static unsigned long int +byteflip(unsigned long int value, int n) +{ +if (n == 2) return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8); +return ((value & 0x000000ff) << 24) | + ((value & 0x0000ff00) << 8) | + ((value & 0x00ff0000) >> 8) | + ((value & 0xff000000) >> 24); +} + + + +/************************************************* +* Test for a byte-flipped compiled regex * +*************************************************/ + +/* This function is called from pcre_exec(), pcre_dfa_exec(), and also from +pcre_fullinfo(). Its job is to test whether the regex is byte-flipped - that +is, it was compiled on a system of opposite endianness. The function is called +only when the native MAGIC_NUMBER test fails. If the regex is indeed flipped, +we flip all the relevant values into a different data block, and return it. + +Arguments: + re points to the regex + study points to study data, or NULL + internal_re points to a new regex block + internal_study points to a new study block + +Returns: the new block if is is indeed a byte-flipped regex + NULL if it is not +*/ + +real_pcre * +_pcre_try_flipped(const real_pcre *re, real_pcre *internal_re, + const pcre_study_data *study, pcre_study_data *internal_study) +{ +if (byteflip(re->magic_number, sizeof(re->magic_number)) != MAGIC_NUMBER) + return NULL; + +*internal_re = *re; /* To copy other fields */ +internal_re->size = byteflip(re->size, sizeof(re->size)); +internal_re->options = byteflip(re->options, sizeof(re->options)); +internal_re->flags = (pcre_uint16)byteflip(re->flags, sizeof(re->flags)); +internal_re->top_bracket = + (pcre_uint16)byteflip(re->top_bracket, sizeof(re->top_bracket)); +internal_re->top_backref = + (pcre_uint16)byteflip(re->top_backref, sizeof(re->top_backref)); +internal_re->first_byte = + (pcre_uint16)byteflip(re->first_byte, sizeof(re->first_byte)); +internal_re->req_byte = + (pcre_uint16)byteflip(re->req_byte, sizeof(re->req_byte)); +internal_re->name_table_offset = + (pcre_uint16)byteflip(re->name_table_offset, sizeof(re->name_table_offset)); +internal_re->name_entry_size = + (pcre_uint16)byteflip(re->name_entry_size, sizeof(re->name_entry_size)); +internal_re->name_count = + (pcre_uint16)byteflip(re->name_count, sizeof(re->name_count)); + +if (study != NULL) + { + *internal_study = *study; /* To copy other fields */ + internal_study->size = byteflip(study->size, sizeof(study->size)); + internal_study->options = byteflip(study->options, sizeof(study->options)); + } + +return internal_re; +} + +/* End of pcre_tryflipped.c */ diff --git a/pcre-7.4/pcre_ucp_searchfuncs.c b/pcre-7.4/pcre_ucp_searchfuncs.c new file mode 100644 index 0000000..316163e --- /dev/null +++ b/pcre-7.4/pcre_ucp_searchfuncs.c @@ -0,0 +1,179 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains code for searching the table of Unicode character +properties. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + +#include "ucp.h" /* Category definitions */ +#include "ucpinternal.h" /* Internal table details */ +#include "ucptable.h" /* The table itself */ + + +/* Table to translate from particular type value to the general value. */ + +static const int ucp_gentype[] = { + ucp_C, ucp_C, ucp_C, ucp_C, ucp_C, /* Cc, Cf, Cn, Co, Cs */ + ucp_L, ucp_L, ucp_L, ucp_L, ucp_L, /* Ll, Lu, Lm, Lo, Lt */ + ucp_M, ucp_M, ucp_M, /* Mc, Me, Mn */ + ucp_N, ucp_N, ucp_N, /* Nd, Nl, No */ + ucp_P, ucp_P, ucp_P, ucp_P, ucp_P, /* Pc, Pd, Pe, Pf, Pi */ + ucp_P, ucp_P, /* Ps, Po */ + ucp_S, ucp_S, ucp_S, ucp_S, /* Sc, Sk, Sm, So */ + ucp_Z, ucp_Z, ucp_Z /* Zl, Zp, Zs */ +}; + + + +/************************************************* +* Search table and return type * +*************************************************/ + +/* Three values are returned: the category is ucp_C, ucp_L, etc. The detailed +character type is ucp_Lu, ucp_Nd, etc. The script is ucp_Latin, etc. + +Arguments: + c the character value + type_ptr the detailed character type is returned here + script_ptr the script is returned here + +Returns: the character type category +*/ + +int +_pcre_ucp_findprop(const unsigned int c, int *type_ptr, int *script_ptr) +{ +int bot = 0; +int top = sizeof(ucp_table)/sizeof(cnode); +int mid; + +/* The table is searched using a binary chop. You might think that using +intermediate variables to hold some of the common expressions would speed +things up, but tests with gcc 3.4.4 on Linux showed that, on the contrary, it +makes things a lot slower. */ + +for (;;) + { + if (top <= bot) + { + *type_ptr = ucp_Cn; + *script_ptr = ucp_Common; + return ucp_C; + } + mid = (bot + top) >> 1; + if (c == (ucp_table[mid].f0 & f0_charmask)) break; + if (c < (ucp_table[mid].f0 & f0_charmask)) top = mid; + else + { + if ((ucp_table[mid].f0 & f0_rangeflag) != 0 && + c <= (ucp_table[mid].f0 & f0_charmask) + + (ucp_table[mid].f1 & f1_rangemask)) break; + bot = mid + 1; + } + } + +/* Found an entry in the table. Set the script and detailed type values, and +return the general type. */ + +*script_ptr = (ucp_table[mid].f0 & f0_scriptmask) >> f0_scriptshift; +*type_ptr = (ucp_table[mid].f1 & f1_typemask) >> f1_typeshift; + +return ucp_gentype[*type_ptr]; +} + + + +/************************************************* +* Search table and return other case * +*************************************************/ + +/* If the given character is a letter, and there is another case for the +letter, return the other case. Otherwise, return -1. + +Arguments: + c the character value + +Returns: the other case or NOTACHAR if none +*/ + +unsigned int +_pcre_ucp_othercase(const unsigned int c) +{ +int bot = 0; +int top = sizeof(ucp_table)/sizeof(cnode); +int mid, offset; + +/* The table is searched using a binary chop. You might think that using +intermediate variables to hold some of the common expressions would speed +things up, but tests with gcc 3.4.4 on Linux showed that, on the contrary, it +makes things a lot slower. */ + +for (;;) + { + if (top <= bot) return -1; + mid = (bot + top) >> 1; + if (c == (ucp_table[mid].f0 & f0_charmask)) break; + if (c < (ucp_table[mid].f0 & f0_charmask)) top = mid; + else + { + if ((ucp_table[mid].f0 & f0_rangeflag) != 0 && + c <= (ucp_table[mid].f0 & f0_charmask) + + (ucp_table[mid].f1 & f1_rangemask)) break; + bot = mid + 1; + } + } + +/* Found an entry in the table. Return NOTACHAR for a range entry. Otherwise +return the other case if there is one, else NOTACHAR. */ + +if ((ucp_table[mid].f0 & f0_rangeflag) != 0) return NOTACHAR; + +offset = ucp_table[mid].f1 & f1_casemask; +if ((offset & f1_caseneg) != 0) offset |= f1_caseneg; +return (offset == 0)? NOTACHAR : c + offset; +} + + +/* End of pcre_ucp_searchfuncs.c */ diff --git a/pcre-7.4/pcre_valid_utf8.c b/pcre-7.4/pcre_valid_utf8.c new file mode 100644 index 0000000..1899142 --- /dev/null +++ b/pcre-7.4/pcre_valid_utf8.c @@ -0,0 +1,162 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains an internal function for validating UTF-8 character +strings. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Validate a UTF-8 string * +*************************************************/ + +/* This function is called (optionally) at the start of compile or match, to +validate that a supposed UTF-8 string is actually valid. The early check means +that subsequent code can assume it is dealing with a valid string. The check +can be turned off for maximum performance, but the consequences of supplying +an invalid string are then undefined. + +Originally, this function checked according to RFC 2279, allowing for values in +the range 0 to 0x7fffffff, up to 6 bytes long, but ensuring that they were in +the canonical format. Once somebody had pointed out RFC 3629 to me (it +obsoletes 2279), additional restrictions were applies. The values are now +limited to be between 0 and 0x0010ffff, no more than 4 bytes long, and the +subrange 0xd000 to 0xdfff is excluded. + +Arguments: + string points to the string + length length of string, or -1 if the string is zero-terminated + +Returns: < 0 if the string is a valid UTF-8 string + >= 0 otherwise; the value is the offset of the bad byte +*/ + +int +_pcre_valid_utf8(const uschar *string, int length) +{ +#ifdef SUPPORT_UTF8 +register const uschar *p; + +if (length < 0) + { + for (p = string; *p != 0; p++); + length = p - string; + } + +for (p = string; length-- > 0; p++) + { + register int ab; + register int c = *p; + if (c < 128) continue; + if (c < 0xc0) return p - string; + ab = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */ + if (length < ab || ab > 3) return p - string; + length -= ab; + + /* Check top bits in the second byte */ + if ((*(++p) & 0xc0) != 0x80) return p - string; + + /* Check for overlong sequences for each different length, and for the + excluded range 0xd000 to 0xdfff. */ + + switch (ab) + { + /* Check for xx00 000x (overlong sequence) */ + + case 1: + if ((c & 0x3e) == 0) return p - string; + continue; /* We know there aren't any more bytes to check */ + + /* Check for 1110 0000, xx0x xxxx (overlong sequence) or + 1110 1101, 1010 xxxx (0xd000 - 0xdfff) */ + + case 2: + if ((c == 0xe0 && (*p & 0x20) == 0) || + (c == 0xed && *p >= 0xa0)) + return p - string; + break; + + /* Check for 1111 0000, xx00 xxxx (overlong sequence) or + greater than 0x0010ffff (f4 8f bf bf) */ + + case 3: + if ((c == 0xf0 && (*p & 0x30) == 0) || + (c > 0xf4 ) || + (c == 0xf4 && *p > 0x8f)) + return p - string; + break; + +#if 0 + /* These cases can no longer occur, as we restrict to a maximum of four + bytes nowadays. Leave the code here in case we ever want to add an option + for longer sequences. */ + + /* Check for 1111 1000, xx00 0xxx */ + case 4: + if (c == 0xf8 && (*p & 0x38) == 0) return p - string; + break; + + /* Check for leading 0xfe or 0xff, and then for 1111 1100, xx00 00xx */ + case 5: + if (c == 0xfe || c == 0xff || + (c == 0xfc && (*p & 0x3c) == 0)) return p - string; + break; +#endif + + } + + /* Check for valid bytes after the 2nd, if any; all must start 10 */ + while (--ab > 0) + { + if ((*(++p) & 0xc0) != 0x80) return p - string; + } + } +#endif + +return -1; +} + +/* End of pcre_valid_utf8.c */ diff --git a/pcre-7.4/pcre_version.c b/pcre-7.4/pcre_version.c new file mode 100644 index 0000000..c3b9cee --- /dev/null +++ b/pcre-7.4/pcre_version.c @@ -0,0 +1,90 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre_version(), which returns a +string that identifies the PCRE version that is in use. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Return version string * +*************************************************/ + +/* These macros are the standard way of turning unquoted text into C strings. +They allow macros like PCRE_MAJOR to be defined without quotes, which is +convenient for user programs that want to test its value. */ + +#define STRING(a) # a +#define XSTRING(s) STRING(s) + +/* A problem turned up with PCRE_PRERELEASE, which is defined empty for +production releases. Originally, it was used naively in this code: + + return XSTRING(PCRE_MAJOR) + "." XSTRING(PCRE_MINOR) + XSTRING(PCRE_PRERELEASE) + " " XSTRING(PCRE_DATE); + +However, when PCRE_PRERELEASE is empty, this leads to an attempted expansion of +STRING(). The C standard states: "If (before argument substitution) any +argument consists of no preprocessing tokens, the behavior is undefined." It +turns out the gcc treats this case as a single empty string - which is what we +really want - but Visual C grumbles about the lack of an argument for the +macro. Unfortunately, both are within their rights. To cope with both ways of +handling this, I had resort to some messy hackery that does a test at run time. +I could find no way of detecting that a macro is defined as an empty string at +pre-processor time. This hack uses a standard trick for avoiding calling +the STRING macro with an empty argument when doing the test. */ + +const char * +pcre_version(void) +{ +return (XSTRING(Z PCRE_PRERELEASE)[1] == 0)? + XSTRING(PCRE_MAJOR.PCRE_MINOR PCRE_DATE) : + XSTRING(PCRE_MAJOR.PCRE_MINOR) XSTRING(PCRE_PRERELEASE PCRE_DATE); +} + +/* End of pcre_version.c */ diff --git a/pcre-7.4/pcre_xclass.c b/pcre-7.4/pcre_xclass.c new file mode 100644 index 0000000..cdf1af1 --- /dev/null +++ b/pcre-7.4/pcre_xclass.c @@ -0,0 +1,148 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains an internal function that is used to match an extended +class (one that contains characters whose values are > 255). It is used by both +pcre_exec() and pcre_def_exec(). */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre_internal.h" + + +/************************************************* +* Match character against an XCLASS * +*************************************************/ + +/* This function is called to match a character against an extended class that +might contain values > 255. + +Arguments: + c the character + data points to the flag byte of the XCLASS data + +Returns: TRUE if character matches, else FALSE +*/ + +BOOL +_pcre_xclass(int c, const uschar *data) +{ +int t; +BOOL negated = (*data & XCL_NOT) != 0; + +/* Character values < 256 are matched against a bitmap, if one is present. If +not, we still carry on, because there may be ranges that start below 256 in the +additional data. */ + +if (c < 256) + { + if ((*data & XCL_MAP) != 0 && (data[1 + c/8] & (1 << (c&7))) != 0) + return !negated; /* char found */ + } + +/* First skip the bit map if present. Then match against the list of Unicode +properties or large chars or ranges that end with a large char. We won't ever +encounter XCL_PROP or XCL_NOTPROP when UCP support is not compiled. */ + +if ((*data++ & XCL_MAP) != 0) data += 32; + +while ((t = *data++) != XCL_END) + { + int x, y; + if (t == XCL_SINGLE) + { + GETCHARINC(x, data); + if (c == x) return !negated; + } + else if (t == XCL_RANGE) + { + GETCHARINC(x, data); + GETCHARINC(y, data); + if (c >= x && c <= y) return !negated; + } + +#ifdef SUPPORT_UCP + else /* XCL_PROP & XCL_NOTPROP */ + { + int chartype, script; + int category = _pcre_ucp_findprop(c, &chartype, &script); + + switch(*data) + { + case PT_ANY: + if (t == XCL_PROP) return !negated; + break; + + case PT_LAMP: + if ((chartype == ucp_Lu || chartype == ucp_Ll || chartype == ucp_Lt) == + (t == XCL_PROP)) return !negated; + break; + + case PT_GC: + if ((data[1] == category) == (t == XCL_PROP)) return !negated; + break; + + case PT_PC: + if ((data[1] == chartype) == (t == XCL_PROP)) return !negated; + break; + + case PT_SC: + if ((data[1] == script) == (t == XCL_PROP)) return !negated; + break; + + /* This should never occur, but compilers may mutter if there is no + default. */ + + default: + return FALSE; + } + + data += 2; + } +#endif /* SUPPORT_UCP */ + } + +return negated; /* char did not match */ +} + +/* End of pcre_xclass.c */ diff --git a/pcre-7.4/pcrecpp.cc b/pcre-7.4/pcrecpp.cc new file mode 100644 index 0000000..a0c2b83 --- /dev/null +++ b/pcre-7.4/pcrecpp.cc @@ -0,0 +1,857 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <limits.h> /* for SHRT_MIN, USHRT_MAX, etc */ +#include <assert.h> +#include <errno.h> +#include <string> +#include <algorithm> + +#include "pcrecpp_internal.h" +#include "pcre.h" +#include "pcrecpp.h" +#include "pcre_stringpiece.h" + + +namespace pcrecpp { + +// Maximum number of args we can set +static const int kMaxArgs = 16; +static const int kVecSize = (1 + kMaxArgs) * 3; // results + PCRE workspace + +// Special object that stands-in for no argument +PCRECPP_EXP_DEFN Arg no_arg((void*)NULL); + +// If a regular expression has no error, its error_ field points here +static const string empty_string; + +// If the user doesn't ask for any options, we just use this one +static RE_Options default_options; + +void RE::Init(const string& pat, const RE_Options* options) { + pattern_ = pat; + if (options == NULL) { + options_ = default_options; + } else { + options_ = *options; + } + error_ = &empty_string; + re_full_ = NULL; + re_partial_ = NULL; + + re_partial_ = Compile(UNANCHORED); + if (re_partial_ != NULL) { + re_full_ = Compile(ANCHOR_BOTH); + } +} + +void RE::Cleanup() { + if (re_full_ != NULL) (*pcre_free)(re_full_); + if (re_partial_ != NULL) (*pcre_free)(re_partial_); + if (error_ != &empty_string) delete error_; +} + + +RE::~RE() { + Cleanup(); +} + + +pcre* RE::Compile(Anchor anchor) { + // First, convert RE_Options into pcre options + int pcre_options = 0; + pcre_options = options_.all_options(); + + // Special treatment for anchoring. This is needed because at + // runtime pcre only provides an option for anchoring at the + // beginning of a string (unless you use offset). + // + // There are three types of anchoring we want: + // UNANCHORED Compile the original pattern, and use + // a pcre unanchored match. + // ANCHOR_START Compile the original pattern, and use + // a pcre anchored match. + // ANCHOR_BOTH Tack a "\z" to the end of the original pattern + // and use a pcre anchored match. + + const char* compile_error; + int eoffset; + pcre* re; + if (anchor != ANCHOR_BOTH) { + re = pcre_compile(pattern_.c_str(), pcre_options, + &compile_error, &eoffset, NULL); + } else { + // Tack a '\z' at the end of RE. Parenthesize it first so that + // the '\z' applies to all top-level alternatives in the regexp. + string wrapped = "(?:"; // A non-counting grouping operator + wrapped += pattern_; + wrapped += ")\\z"; + re = pcre_compile(wrapped.c_str(), pcre_options, + &compile_error, &eoffset, NULL); + } + if (re == NULL) { + if (error_ == &empty_string) error_ = new string(compile_error); + } + return re; +} + +/***** Matching interfaces *****/ + +bool RE::FullMatch(const StringPiece& text, + const Arg& ptr1, + const Arg& ptr2, + const Arg& ptr3, + const Arg& ptr4, + const Arg& ptr5, + const Arg& ptr6, + const Arg& ptr7, + const Arg& ptr8, + const Arg& ptr9, + const Arg& ptr10, + const Arg& ptr11, + const Arg& ptr12, + const Arg& ptr13, + const Arg& ptr14, + const Arg& ptr15, + const Arg& ptr16) const { + const Arg* args[kMaxArgs]; + int n = 0; + if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; + if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; + if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; + if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; + if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; + if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; + if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; + if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; + if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; + if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; + if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; + if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; + if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; + if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; + if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; + if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; + done: + + int consumed; + int vec[kVecSize]; + return DoMatchImpl(text, ANCHOR_BOTH, &consumed, args, n, vec, kVecSize); +} + +bool RE::PartialMatch(const StringPiece& text, + const Arg& ptr1, + const Arg& ptr2, + const Arg& ptr3, + const Arg& ptr4, + const Arg& ptr5, + const Arg& ptr6, + const Arg& ptr7, + const Arg& ptr8, + const Arg& ptr9, + const Arg& ptr10, + const Arg& ptr11, + const Arg& ptr12, + const Arg& ptr13, + const Arg& ptr14, + const Arg& ptr15, + const Arg& ptr16) const { + const Arg* args[kMaxArgs]; + int n = 0; + if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; + if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; + if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; + if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; + if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; + if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; + if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; + if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; + if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; + if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; + if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; + if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; + if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; + if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; + if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; + if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; + done: + + int consumed; + int vec[kVecSize]; + return DoMatchImpl(text, UNANCHORED, &consumed, args, n, vec, kVecSize); +} + +bool RE::Consume(StringPiece* input, + const Arg& ptr1, + const Arg& ptr2, + const Arg& ptr3, + const Arg& ptr4, + const Arg& ptr5, + const Arg& ptr6, + const Arg& ptr7, + const Arg& ptr8, + const Arg& ptr9, + const Arg& ptr10, + const Arg& ptr11, + const Arg& ptr12, + const Arg& ptr13, + const Arg& ptr14, + const Arg& ptr15, + const Arg& ptr16) const { + const Arg* args[kMaxArgs]; + int n = 0; + if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; + if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; + if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; + if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; + if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; + if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; + if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; + if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; + if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; + if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; + if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; + if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; + if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; + if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; + if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; + if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; + done: + + int consumed; + int vec[kVecSize]; + if (DoMatchImpl(*input, ANCHOR_START, &consumed, + args, n, vec, kVecSize)) { + input->remove_prefix(consumed); + return true; + } else { + return false; + } +} + +bool RE::FindAndConsume(StringPiece* input, + const Arg& ptr1, + const Arg& ptr2, + const Arg& ptr3, + const Arg& ptr4, + const Arg& ptr5, + const Arg& ptr6, + const Arg& ptr7, + const Arg& ptr8, + const Arg& ptr9, + const Arg& ptr10, + const Arg& ptr11, + const Arg& ptr12, + const Arg& ptr13, + const Arg& ptr14, + const Arg& ptr15, + const Arg& ptr16) const { + const Arg* args[kMaxArgs]; + int n = 0; + if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; + if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; + if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; + if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; + if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; + if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; + if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; + if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; + if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; + if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; + if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; + if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; + if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; + if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; + if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; + if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; + done: + + int consumed; + int vec[kVecSize]; + if (DoMatchImpl(*input, UNANCHORED, &consumed, + args, n, vec, kVecSize)) { + input->remove_prefix(consumed); + return true; + } else { + return false; + } +} + +bool RE::Replace(const StringPiece& rewrite, + string *str) const { + int vec[kVecSize]; + int matches = TryMatch(*str, 0, UNANCHORED, vec, kVecSize); + if (matches == 0) + return false; + + string s; + if (!Rewrite(&s, rewrite, *str, vec, matches)) + return false; + + assert(vec[0] >= 0); + assert(vec[1] >= 0); + str->replace(vec[0], vec[1] - vec[0], s); + return true; +} + +// Returns PCRE_NEWLINE_CRLF, PCRE_NEWLINE_CR, or PCRE_NEWLINE_LF. +// Note that PCRE_NEWLINE_CRLF is defined to be P_N_CR | P_N_LF. +// Modified by PH to add PCRE_NEWLINE_ANY and PCRE_NEWLINE_ANYCRLF. + +static int NewlineMode(int pcre_options) { + // TODO: if we can make it threadsafe, cache this var + int newline_mode = 0; + /* if (newline_mode) return newline_mode; */ // do this once it's cached + if (pcre_options & (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF| + PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)) { + newline_mode = (pcre_options & + (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF| + PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)); + } else { + int newline; + pcre_config(PCRE_CONFIG_NEWLINE, &newline); + if (newline == 10) + newline_mode = PCRE_NEWLINE_LF; + else if (newline == 13) + newline_mode = PCRE_NEWLINE_CR; + else if (newline == 3338) + newline_mode = PCRE_NEWLINE_CRLF; + else if (newline == -1) + newline_mode = PCRE_NEWLINE_ANY; + else if (newline == -2) + newline_mode = PCRE_NEWLINE_ANYCRLF; + else + assert("" == "Unexpected return value from pcre_config(NEWLINE)"); + } + return newline_mode; +} + +int RE::GlobalReplace(const StringPiece& rewrite, + string *str) const { + int count = 0; + int vec[kVecSize]; + string out; + int start = 0; + int lastend = -1; + + for (; start <= static_cast<int>(str->length()); count++) { + int matches = TryMatch(*str, start, UNANCHORED, vec, kVecSize); + if (matches <= 0) + break; + int matchstart = vec[0], matchend = vec[1]; + assert(matchstart >= start); + assert(matchend >= matchstart); + if (matchstart == matchend && matchstart == lastend) { + // advance one character if we matched an empty string at the same + // place as the last match occurred + matchend = start + 1; + // If the current char is CR and we're in CRLF mode, skip LF too. + // Note it's better to call pcre_fullinfo() than to examine + // all_options(), since options_ could have changed bewteen + // compile-time and now, but this is simpler and safe enough. + // Modified by PH to add ANY and ANYCRLF. + if (start+1 < static_cast<int>(str->length()) && + (*str)[start] == '\r' && (*str)[start+1] == '\n' && + (NewlineMode(options_.all_options()) == PCRE_NEWLINE_CRLF || + NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANY || + NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANYCRLF) + ) { + matchend++; + } + // We also need to advance more than one char if we're in utf8 mode. +#ifdef SUPPORT_UTF8 + if (options_.utf8()) { + while (matchend < static_cast<int>(str->length()) && + ((*str)[matchend] & 0xc0) == 0x80) + matchend++; + } +#endif + if (matchend <= static_cast<int>(str->length())) + out.append(*str, start, matchend - start); + start = matchend; + } else { + out.append(*str, start, matchstart - start); + Rewrite(&out, rewrite, *str, vec, matches); + start = matchend; + lastend = matchend; + count++; + } + } + + if (count == 0) + return 0; + + if (start < static_cast<int>(str->length())) + out.append(*str, start, str->length() - start); + swap(out, *str); + return count; +} + +bool RE::Extract(const StringPiece& rewrite, + const StringPiece& text, + string *out) const { + int vec[kVecSize]; + int matches = TryMatch(text, 0, UNANCHORED, vec, kVecSize); + if (matches == 0) + return false; + out->erase(); + return Rewrite(out, rewrite, text, vec, matches); +} + +/*static*/ string RE::QuoteMeta(const StringPiece& unquoted) { + string result; + + // Escape any ascii character not in [A-Za-z_0-9]. + // + // Note that it's legal to escape a character even if it has no + // special meaning in a regular expression -- so this function does + // that. (This also makes it identical to the perl function of the + // same name; see `perldoc -f quotemeta`.) + for (int ii = 0; ii < unquoted.size(); ++ii) { + // Note that using 'isalnum' here raises the benchmark time from + // 32ns to 58ns: + if ((unquoted[ii] < 'a' || unquoted[ii] > 'z') && + (unquoted[ii] < 'A' || unquoted[ii] > 'Z') && + (unquoted[ii] < '0' || unquoted[ii] > '9') && + unquoted[ii] != '_' && + // If this is the part of a UTF8 or Latin1 character, we need + // to copy this byte without escaping. Experimentally this is + // what works correctly with the regexp library. + !(unquoted[ii] & 128)) { + result += '\\'; + } + result += unquoted[ii]; + } + + return result; +} + +/***** Actual matching and rewriting code *****/ + +int RE::TryMatch(const StringPiece& text, + int startpos, + Anchor anchor, + int *vec, + int vecsize) const { + pcre* re = (anchor == ANCHOR_BOTH) ? re_full_ : re_partial_; + if (re == NULL) { + //fprintf(stderr, "Matching against invalid re: %s\n", error_->c_str()); + return 0; + } + + pcre_extra extra = { 0, 0, 0, 0, 0, 0 }; + if (options_.match_limit() > 0) { + extra.flags |= PCRE_EXTRA_MATCH_LIMIT; + extra.match_limit = options_.match_limit(); + } + if (options_.match_limit_recursion() > 0) { + extra.flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; + extra.match_limit_recursion = options_.match_limit_recursion(); + } + int rc = pcre_exec(re, // The regular expression object + &extra, + (text.data() == NULL) ? "" : text.data(), + text.size(), + startpos, + (anchor == UNANCHORED) ? 0 : PCRE_ANCHORED, + vec, + vecsize); + + // Handle errors + if (rc == PCRE_ERROR_NOMATCH) { + return 0; + } else if (rc < 0) { + //fprintf(stderr, "Unexpected return code: %d when matching '%s'\n", + // re, pattern_.c_str()); + return 0; + } else if (rc == 0) { + // pcre_exec() returns 0 as a special case when the number of + // capturing subpatterns exceeds the size of the vector. + // When this happens, there is a match and the output vector + // is filled, but we miss out on the positions of the extra subpatterns. + rc = vecsize / 2; + } + + return rc; +} + +bool RE::DoMatchImpl(const StringPiece& text, + Anchor anchor, + int* consumed, + const Arg* const* args, + int n, + int* vec, + int vecsize) const { + assert((1 + n) * 3 <= vecsize); // results + PCRE workspace + int matches = TryMatch(text, 0, anchor, vec, vecsize); + assert(matches >= 0); // TryMatch never returns negatives + if (matches == 0) + return false; + + *consumed = vec[1]; + + if (n == 0 || args == NULL) { + // We are not interested in results + return true; + } + + if (NumberOfCapturingGroups() < n) { + // RE has fewer capturing groups than number of arg pointers passed in + return false; + } + + // If we got here, we must have matched the whole pattern. + // We do not need (can not do) any more checks on the value of 'matches' here + // -- see the comment for TryMatch. + for (int i = 0; i < n; i++) { + const int start = vec[2*(i+1)]; + const int limit = vec[2*(i+1)+1]; + if (!args[i]->Parse(text.data() + start, limit-start)) { + // TODO: Should we indicate what the error was? + return false; + } + } + + return true; +} + +bool RE::DoMatch(const StringPiece& text, + Anchor anchor, + int* consumed, + const Arg* const args[], + int n) const { + assert(n >= 0); + size_t const vecsize = (1 + n) * 3; // results + PCRE workspace + // (as for kVecSize) + int space[21]; // use stack allocation for small vecsize (common case) + int* vec = vecsize <= 21 ? space : new int[vecsize]; + bool retval = DoMatchImpl(text, anchor, consumed, args, n, vec, vecsize); + if (vec != space) delete [] vec; + return retval; +} + +bool RE::Rewrite(string *out, const StringPiece &rewrite, + const StringPiece &text, int *vec, int veclen) const { + for (const char *s = rewrite.data(), *end = s + rewrite.size(); + s < end; s++) { + int c = *s; + if (c == '\\') { + c = *++s; + if (isdigit(c)) { + int n = (c - '0'); + if (n >= veclen) { + //fprintf(stderr, requested group %d in regexp %.*s\n", + // n, rewrite.size(), rewrite.data()); + return false; + } + int start = vec[2 * n]; + if (start >= 0) + out->append(text.data() + start, vec[2 * n + 1] - start); + } else if (c == '\\') { + out->push_back('\\'); + } else { + //fprintf(stderr, "invalid rewrite pattern: %.*s\n", + // rewrite.size(), rewrite.data()); + return false; + } + } else { + out->push_back(c); + } + } + return true; +} + +// Return the number of capturing subpatterns, or -1 if the +// regexp wasn't valid on construction. +int RE::NumberOfCapturingGroups() const { + if (re_partial_ == NULL) return -1; + + int result; + int pcre_retval = pcre_fullinfo(re_partial_, // The regular expression object + NULL, // We did not study the pattern + PCRE_INFO_CAPTURECOUNT, + &result); + assert(pcre_retval == 0); + return result; +} + +/***** Parsers for various types *****/ + +bool Arg::parse_null(const char* str, int n, void* dest) { + // We fail if somebody asked us to store into a non-NULL void* pointer + return (dest == NULL); +} + +bool Arg::parse_string(const char* str, int n, void* dest) { + reinterpret_cast<string*>(dest)->assign(str, n); + return true; +} + +bool Arg::parse_stringpiece(const char* str, int n, void* dest) { + reinterpret_cast<StringPiece*>(dest)->set(str, n); + return true; +} + +bool Arg::parse_char(const char* str, int n, void* dest) { + if (n != 1) return false; + *(reinterpret_cast<char*>(dest)) = str[0]; + return true; +} + +bool Arg::parse_uchar(const char* str, int n, void* dest) { + if (n != 1) return false; + *(reinterpret_cast<unsigned char*>(dest)) = str[0]; + return true; +} + +// Largest number spec that we are willing to parse +static const int kMaxNumberLength = 32; + +// REQUIRES "buf" must have length at least kMaxNumberLength+1 +// REQUIRES "n > 0" +// Copies "str" into "buf" and null-terminates if necessary. +// Returns one of: +// a. "str" if no termination is needed +// b. "buf" if the string was copied and null-terminated +// c. "" if the input was invalid and has no hope of being parsed +static const char* TerminateNumber(char* buf, const char* str, int n) { + if ((n > 0) && isspace(*str)) { + // We are less forgiving than the strtoxxx() routines and do not + // allow leading spaces. + return ""; + } + + // See if the character right after the input text may potentially + // look like a digit. + if (isdigit(str[n]) || + ((str[n] >= 'a') && (str[n] <= 'f')) || + ((str[n] >= 'A') && (str[n] <= 'F'))) { + if (n > kMaxNumberLength) return ""; // Input too big to be a valid number + memcpy(buf, str, n); + buf[n] = '\0'; + return buf; + } else { + // We can parse right out of the supplied string, so return it. + return str; + } +} + +bool Arg::parse_long_radix(const char* str, + int n, + void* dest, + int radix) { + if (n == 0) return false; + char buf[kMaxNumberLength+1]; + str = TerminateNumber(buf, str, n); + char* end; + errno = 0; + long r = strtol(str, &end, radix); + if (end != str + n) return false; // Leftover junk + if (errno) return false; + *(reinterpret_cast<long*>(dest)) = r; + return true; +} + +bool Arg::parse_ulong_radix(const char* str, + int n, + void* dest, + int radix) { + if (n == 0) return false; + char buf[kMaxNumberLength+1]; + str = TerminateNumber(buf, str, n); + if (str[0] == '-') return false; // strtoul() on a negative number?! + char* end; + errno = 0; + unsigned long r = strtoul(str, &end, radix); + if (end != str + n) return false; // Leftover junk + if (errno) return false; + *(reinterpret_cast<unsigned long*>(dest)) = r; + return true; +} + +bool Arg::parse_short_radix(const char* str, + int n, + void* dest, + int radix) { + long r; + if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse + if (r < SHRT_MIN || r > SHRT_MAX) return false; // Out of range + *(reinterpret_cast<short*>(dest)) = static_cast<short>(r); + return true; +} + +bool Arg::parse_ushort_radix(const char* str, + int n, + void* dest, + int radix) { + unsigned long r; + if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse + if (r > USHRT_MAX) return false; // Out of range + *(reinterpret_cast<unsigned short*>(dest)) = static_cast<unsigned short>(r); + return true; +} + +bool Arg::parse_int_radix(const char* str, + int n, + void* dest, + int radix) { + long r; + if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse + if (r < INT_MIN || r > INT_MAX) return false; // Out of range + *(reinterpret_cast<int*>(dest)) = r; + return true; +} + +bool Arg::parse_uint_radix(const char* str, + int n, + void* dest, + int radix) { + unsigned long r; + if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse + if (r > UINT_MAX) return false; // Out of range + *(reinterpret_cast<unsigned int*>(dest)) = r; + return true; +} + +bool Arg::parse_longlong_radix(const char* str, + int n, + void* dest, + int radix) { +#ifndef HAVE_LONG_LONG + return false; +#else + if (n == 0) return false; + char buf[kMaxNumberLength+1]; + str = TerminateNumber(buf, str, n); + char* end; + errno = 0; +#if defined HAVE_STRTOQ + long long r = strtoq(str, &end, radix); +#elif defined HAVE_STRTOLL + long long r = strtoll(str, &end, radix); +#elif defined HAVE__STRTOI64 + long long r = _strtoi64(str, &end, radix); +#else +#error parse_longlong_radix: cannot convert input to a long-long +#endif + if (end != str + n) return false; // Leftover junk + if (errno) return false; + *(reinterpret_cast<long long*>(dest)) = r; + return true; +#endif /* HAVE_LONG_LONG */ +} + +bool Arg::parse_ulonglong_radix(const char* str, + int n, + void* dest, + int radix) { +#ifndef HAVE_UNSIGNED_LONG_LONG + return false; +#else + if (n == 0) return false; + char buf[kMaxNumberLength+1]; + str = TerminateNumber(buf, str, n); + if (str[0] == '-') return false; // strtoull() on a negative number?! + char* end; + errno = 0; +#if defined HAVE_STRTOQ + unsigned long long r = strtouq(str, &end, radix); +#elif defined HAVE_STRTOLL + unsigned long long r = strtoull(str, &end, radix); +#elif defined HAVE__STRTOI64 + unsigned long long r = _strtoui64(str, &end, radix); +#else +#error parse_ulonglong_radix: cannot convert input to a long-long +#endif + if (end != str + n) return false; // Leftover junk + if (errno) return false; + *(reinterpret_cast<unsigned long long*>(dest)) = r; + return true; +#endif /* HAVE_UNSIGNED_LONG_LONG */ +} + +bool Arg::parse_double(const char* str, int n, void* dest) { + if (n == 0) return false; + static const int kMaxLength = 200; + char buf[kMaxLength]; + if (n >= kMaxLength) return false; + memcpy(buf, str, n); + buf[n] = '\0'; + errno = 0; + char* end; + double r = strtod(buf, &end); + if (end != buf + n) return false; // Leftover junk + if (errno) return false; + *(reinterpret_cast<double*>(dest)) = r; + return true; +} + +bool Arg::parse_float(const char* str, int n, void* dest) { + double r; + if (!parse_double(str, n, &r)) return false; + *(reinterpret_cast<float*>(dest)) = static_cast<float>(r); + return true; +} + + +#define DEFINE_INTEGER_PARSERS(name) \ + bool Arg::parse_##name(const char* str, int n, void* dest) { \ + return parse_##name##_radix(str, n, dest, 10); \ + } \ + bool Arg::parse_##name##_hex(const char* str, int n, void* dest) { \ + return parse_##name##_radix(str, n, dest, 16); \ + } \ + bool Arg::parse_##name##_octal(const char* str, int n, void* dest) { \ + return parse_##name##_radix(str, n, dest, 8); \ + } \ + bool Arg::parse_##name##_cradix(const char* str, int n, void* dest) { \ + return parse_##name##_radix(str, n, dest, 0); \ + } + +DEFINE_INTEGER_PARSERS(short) /* */ +DEFINE_INTEGER_PARSERS(ushort) /* */ +DEFINE_INTEGER_PARSERS(int) /* Don't use semicolons after these */ +DEFINE_INTEGER_PARSERS(uint) /* statements because they can cause */ +DEFINE_INTEGER_PARSERS(long) /* compiler warnings if the checking */ +DEFINE_INTEGER_PARSERS(ulong) /* level is turned up high enough. */ +DEFINE_INTEGER_PARSERS(longlong) /* */ +DEFINE_INTEGER_PARSERS(ulonglong) /* */ + +#undef DEFINE_INTEGER_PARSERS + +} // namespace pcrecpp diff --git a/pcre-7.4/pcrecpp.h b/pcre-7.4/pcrecpp.h new file mode 100644 index 0000000..5a0d597 --- /dev/null +++ b/pcre-7.4/pcrecpp.h @@ -0,0 +1,700 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// Support for PCRE_XXX modifiers added by Giuseppe Maxia, July 2005 + +#ifndef _PCRECPP_H +#define _PCRECPP_H + +// C++ interface to the pcre regular-expression library. RE supports +// Perl-style regular expressions (with extensions like \d, \w, \s, +// ...). +// +// ----------------------------------------------------------------------- +// REGEXP SYNTAX: +// +// This module is part of the pcre library and hence supports its syntax +// for regular expressions. +// +// The syntax is pretty similar to Perl's. For those not familiar +// with Perl's regular expressions, here are some examples of the most +// commonly used extensions: +// +// "hello (\\w+) world" -- \w matches a "word" character +// "version (\\d+)" -- \d matches a digit +// "hello\\s+world" -- \s matches any whitespace character +// "\\b(\\w+)\\b" -- \b matches empty string at a word boundary +// "(?i)hello" -- (?i) turns on case-insensitive matching +// "/\\*(.*?)\\*/" -- .*? matches . minimum no. of times possible +// +// ----------------------------------------------------------------------- +// MATCHING INTERFACE: +// +// The "FullMatch" operation checks that supplied text matches a +// supplied pattern exactly. +// +// Example: successful match +// pcrecpp::RE re("h.*o"); +// re.FullMatch("hello"); +// +// Example: unsuccessful match (requires full match): +// pcrecpp::RE re("e"); +// !re.FullMatch("hello"); +// +// Example: creating a temporary RE object: +// pcrecpp::RE("h.*o").FullMatch("hello"); +// +// You can pass in a "const char*" or a "string" for "text". The +// examples below tend to use a const char*. +// +// You can, as in the different examples above, store the RE object +// explicitly in a variable or use a temporary RE object. The +// examples below use one mode or the other arbitrarily. Either +// could correctly be used for any of these examples. +// +// ----------------------------------------------------------------------- +// MATCHING WITH SUB-STRING EXTRACTION: +// +// You can supply extra pointer arguments to extract matched subpieces. +// +// Example: extracts "ruby" into "s" and 1234 into "i" +// int i; +// string s; +// pcrecpp::RE re("(\\w+):(\\d+)"); +// re.FullMatch("ruby:1234", &s, &i); +// +// Example: does not try to extract any extra sub-patterns +// re.FullMatch("ruby:1234", &s); +// +// Example: does not try to extract into NULL +// re.FullMatch("ruby:1234", NULL, &i); +// +// Example: integer overflow causes failure +// !re.FullMatch("ruby:1234567891234", NULL, &i); +// +// Example: fails because there aren't enough sub-patterns: +// !pcrecpp::RE("\\w+:\\d+").FullMatch("ruby:1234", &s); +// +// Example: fails because string cannot be stored in integer +// !pcrecpp::RE("(.*)").FullMatch("ruby", &i); +// +// The provided pointer arguments can be pointers to any scalar numeric +// type, or one of +// string (matched piece is copied to string) +// StringPiece (StringPiece is mutated to point to matched piece) +// T (where "bool T::ParseFrom(const char*, int)" exists) +// NULL (the corresponding matched sub-pattern is not copied) +// +// CAVEAT: An optional sub-pattern that does not exist in the matched +// string is assigned the empty string. Therefore, the following will +// return false (because the empty string is not a valid number): +// int number; +// pcrecpp::RE::FullMatch("abc", "[a-z]+(\\d+)?", &number); +// +// ----------------------------------------------------------------------- +// DO_MATCH +// +// The matching interface supports at most 16 arguments per call. +// If you need more, consider using the more general interface +// pcrecpp::RE::DoMatch(). See pcrecpp.h for the signature for DoMatch. +// +// ----------------------------------------------------------------------- +// PARTIAL MATCHES +// +// You can use the "PartialMatch" operation when you want the pattern +// to match any substring of the text. +// +// Example: simple search for a string: +// pcrecpp::RE("ell").PartialMatch("hello"); +// +// Example: find first number in a string: +// int number; +// pcrecpp::RE re("(\\d+)"); +// re.PartialMatch("x*100 + 20", &number); +// assert(number == 100); +// +// ----------------------------------------------------------------------- +// UTF-8 AND THE MATCHING INTERFACE: +// +// By default, pattern and text are plain text, one byte per character. +// The UTF8 flag, passed to the constructor, causes both pattern +// and string to be treated as UTF-8 text, still a byte stream but +// potentially multiple bytes per character. In practice, the text +// is likelier to be UTF-8 than the pattern, but the match returned +// may depend on the UTF8 flag, so always use it when matching +// UTF8 text. E.g., "." will match one byte normally but with UTF8 +// set may match up to three bytes of a multi-byte character. +// +// Example: +// pcrecpp::RE_Options options; +// options.set_utf8(); +// pcrecpp::RE re(utf8_pattern, options); +// re.FullMatch(utf8_string); +// +// Example: using the convenience function UTF8(): +// pcrecpp::RE re(utf8_pattern, pcrecpp::UTF8()); +// re.FullMatch(utf8_string); +// +// NOTE: The UTF8 option is ignored if pcre was not configured with the +// --enable-utf8 flag. +// +// ----------------------------------------------------------------------- +// PASSING MODIFIERS TO THE REGULAR EXPRESSION ENGINE +// +// PCRE defines some modifiers to change the behavior of the regular +// expression engine. +// The C++ wrapper defines an auxiliary class, RE_Options, as a vehicle +// to pass such modifiers to a RE class. +// +// Currently, the following modifiers are supported +// +// modifier description Perl corresponding +// +// PCRE_CASELESS case insensitive match /i +// PCRE_MULTILINE multiple lines match /m +// PCRE_DOTALL dot matches newlines /s +// PCRE_DOLLAR_ENDONLY $ matches only at end N/A +// PCRE_EXTRA strict escape parsing N/A +// PCRE_EXTENDED ignore whitespaces /x +// PCRE_UTF8 handles UTF8 chars built-in +// PCRE_UNGREEDY reverses * and *? N/A +// PCRE_NO_AUTO_CAPTURE disables matching parens N/A (*) +// +// (For a full account on how each modifier works, please check the +// PCRE API reference manual). +// +// (*) Both Perl and PCRE allow non matching parentheses by means of the +// "?:" modifier within the pattern itself. e.g. (?:ab|cd) does not +// capture, while (ab|cd) does. +// +// For each modifier, there are two member functions whose name is made +// out of the modifier in lowercase, without the "PCRE_" prefix. For +// instance, PCRE_CASELESS is handled by +// bool caseless(), +// which returns true if the modifier is set, and +// RE_Options & set_caseless(bool), +// which sets or unsets the modifier. +// +// Moreover, PCRE_EXTRA_MATCH_LIMIT can be accessed through the +// set_match_limit() and match_limit() member functions. +// Setting match_limit to a non-zero value will limit the executation of +// pcre to keep it from doing bad things like blowing the stack or taking +// an eternity to return a result. A value of 5000 is good enough to stop +// stack blowup in a 2MB thread stack. Setting match_limit to zero will +// disable match limiting. Alternately, you can set match_limit_recursion() +// which uses PCRE_EXTRA_MATCH_LIMIT_RECURSION to limit how much pcre +// recurses. match_limit() caps the number of matches pcre does; +// match_limit_recrusion() caps the depth of recursion. +// +// Normally, to pass one or more modifiers to a RE class, you declare +// a RE_Options object, set the appropriate options, and pass this +// object to a RE constructor. Example: +// +// RE_options opt; +// opt.set_caseless(true); +// +// if (RE("HELLO", opt).PartialMatch("hello world")) ... +// +// RE_options has two constructors. The default constructor takes no +// arguments and creates a set of flags that are off by default. +// +// The optional parameter 'option_flags' is to facilitate transfer +// of legacy code from C programs. This lets you do +// RE(pattern, RE_Options(PCRE_CASELESS|PCRE_MULTILINE)).PartialMatch(str); +// +// But new code is better off doing +// RE(pattern, +// RE_Options().set_caseless(true).set_multiline(true)).PartialMatch(str); +// (See below) +// +// If you are going to pass one of the most used modifiers, there are some +// convenience functions that return a RE_Options class with the +// appropriate modifier already set: +// CASELESS(), UTF8(), MULTILINE(), DOTALL(), EXTENDED() +// +// If you need to set several options at once, and you don't want to go +// through the pains of declaring a RE_Options object and setting several +// options, there is a parallel method that give you such ability on the +// fly. You can concatenate several set_xxxxx member functions, since each +// of them returns a reference to its class object. e.g.: to pass +// PCRE_CASELESS, PCRE_EXTENDED, and PCRE_MULTILINE to a RE with one +// statement, you may write +// +// RE(" ^ xyz \\s+ .* blah$", RE_Options() +// .set_caseless(true) +// .set_extended(true) +// .set_multiline(true)).PartialMatch(sometext); +// +// ----------------------------------------------------------------------- +// SCANNING TEXT INCREMENTALLY +// +// The "Consume" operation may be useful if you want to repeatedly +// match regular expressions at the front of a string and skip over +// them as they match. This requires use of the "StringPiece" type, +// which represents a sub-range of a real string. Like RE, StringPiece +// is defined in the pcrecpp namespace. +// +// Example: read lines of the form "var = value" from a string. +// string contents = ...; // Fill string somehow +// pcrecpp::StringPiece input(contents); // Wrap in a StringPiece +// +// string var; +// int value; +// pcrecpp::RE re("(\\w+) = (\\d+)\n"); +// while (re.Consume(&input, &var, &value)) { +// ...; +// } +// +// Each successful call to "Consume" will set "var/value", and also +// advance "input" so it points past the matched text. +// +// The "FindAndConsume" operation is similar to "Consume" but does not +// anchor your match at the beginning of the string. For example, you +// could extract all words from a string by repeatedly calling +// pcrecpp::RE("(\\w+)").FindAndConsume(&input, &word) +// +// ----------------------------------------------------------------------- +// PARSING HEX/OCTAL/C-RADIX NUMBERS +// +// By default, if you pass a pointer to a numeric value, the +// corresponding text is interpreted as a base-10 number. You can +// instead wrap the pointer with a call to one of the operators Hex(), +// Octal(), or CRadix() to interpret the text in another base. The +// CRadix operator interprets C-style "0" (base-8) and "0x" (base-16) +// prefixes, but defaults to base-10. +// +// Example: +// int a, b, c, d; +// pcrecpp::RE re("(.*) (.*) (.*) (.*)"); +// re.FullMatch("100 40 0100 0x40", +// pcrecpp::Octal(&a), pcrecpp::Hex(&b), +// pcrecpp::CRadix(&c), pcrecpp::CRadix(&d)); +// will leave 64 in a, b, c, and d. +// +// ----------------------------------------------------------------------- +// REPLACING PARTS OF STRINGS +// +// You can replace the first match of "pattern" in "str" with +// "rewrite". Within "rewrite", backslash-escaped digits (\1 to \9) +// can be used to insert text matching corresponding parenthesized +// group from the pattern. \0 in "rewrite" refers to the entire +// matching text. E.g., +// +// string s = "yabba dabba doo"; +// pcrecpp::RE("b+").Replace("d", &s); +// +// will leave "s" containing "yada dabba doo". The result is true if +// the pattern matches and a replacement occurs, or false otherwise. +// +// GlobalReplace() is like Replace(), except that it replaces all +// occurrences of the pattern in the string with the rewrite. +// Replacements are not subject to re-matching. E.g., +// +// string s = "yabba dabba doo"; +// pcrecpp::RE("b+").GlobalReplace("d", &s); +// +// will leave "s" containing "yada dada doo". It returns the number +// of replacements made. +// +// Extract() is like Replace(), except that if the pattern matches, +// "rewrite" is copied into "out" (an additional argument) with +// substitutions. The non-matching portions of "text" are ignored. +// Returns true iff a match occurred and the extraction happened +// successfully. If no match occurs, the string is left unaffected. + + +#include <string> +#include <pcre.h> +#include <pcrecpparg.h> // defines the Arg class +// This isn't technically needed here, but we include it +// anyway so folks who include pcrecpp.h don't have to. +#include <pcre_stringpiece.h> + +namespace pcrecpp { + +#define PCRE_SET_OR_CLEAR(b, o) \ + if (b) all_options_ |= (o); else all_options_ &= ~(o); \ + return *this + +#define PCRE_IS_SET(o) \ + (all_options_ & o) == o + +// We convert user-passed pointers into special Arg objects +PCRECPP_EXP_DECL Arg no_arg; + +/***** Compiling regular expressions: the RE class *****/ + +// RE_Options allow you to set options to be passed along to pcre, +// along with other options we put on top of pcre. +// Only 9 modifiers, plus match_limit and match_limit_recursion, +// are supported now. +class RE_Options { + public: + // constructor + RE_Options() : match_limit_(0), match_limit_recursion_(0), all_options_(0) {} + + // alternative constructor. + // To facilitate transfer of legacy code from C programs + // + // This lets you do + // RE(pattern, RE_Options(PCRE_CASELESS|PCRE_MULTILINE)).PartialMatch(str); + // But new code is better off doing + // RE(pattern, + // RE_Options().set_caseless(true).set_multiline(true)).PartialMatch(str); + RE_Options(int option_flags) : match_limit_(0), match_limit_recursion_(0), + all_options_(option_flags) {} + // we're fine with the default destructor, copy constructor, etc. + + // accessors and mutators + int match_limit() const { return match_limit_; }; + RE_Options &set_match_limit(int limit) { + match_limit_ = limit; + return *this; + } + + int match_limit_recursion() const { return match_limit_recursion_; }; + RE_Options &set_match_limit_recursion(int limit) { + match_limit_recursion_ = limit; + return *this; + } + + bool caseless() const { + return PCRE_IS_SET(PCRE_CASELESS); + } + RE_Options &set_caseless(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_CASELESS); + } + + bool multiline() const { + return PCRE_IS_SET(PCRE_MULTILINE); + } + RE_Options &set_multiline(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_MULTILINE); + } + + bool dotall() const { + return PCRE_IS_SET(PCRE_DOTALL); + } + RE_Options &set_dotall(bool x) { + PCRE_SET_OR_CLEAR(x,PCRE_DOTALL); + } + + bool extended() const { + return PCRE_IS_SET(PCRE_EXTENDED); + } + RE_Options &set_extended(bool x) { + PCRE_SET_OR_CLEAR(x,PCRE_EXTENDED); + } + + bool dollar_endonly() const { + return PCRE_IS_SET(PCRE_DOLLAR_ENDONLY); + } + RE_Options &set_dollar_endonly(bool x) { + PCRE_SET_OR_CLEAR(x,PCRE_DOLLAR_ENDONLY); + } + + bool extra() const { + return PCRE_IS_SET( PCRE_EXTRA); + } + RE_Options &set_extra(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_EXTRA); + } + + bool ungreedy() const { + return PCRE_IS_SET(PCRE_UNGREEDY); + } + RE_Options &set_ungreedy(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_UNGREEDY); + } + + bool utf8() const { + return PCRE_IS_SET(PCRE_UTF8); + } + RE_Options &set_utf8(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_UTF8); + } + + bool no_auto_capture() const { + return PCRE_IS_SET(PCRE_NO_AUTO_CAPTURE); + } + RE_Options &set_no_auto_capture(bool x) { + PCRE_SET_OR_CLEAR(x, PCRE_NO_AUTO_CAPTURE); + } + + RE_Options &set_all_options(int opt) { + all_options_ = opt; + return *this; + } + int all_options() const { + return all_options_ ; + } + + // TODO: add other pcre flags + + private: + int match_limit_; + int match_limit_recursion_; + int all_options_; +}; + +// These functions return some common RE_Options +static inline RE_Options UTF8() { + return RE_Options().set_utf8(true); +} + +static inline RE_Options CASELESS() { + return RE_Options().set_caseless(true); +} +static inline RE_Options MULTILINE() { + return RE_Options().set_multiline(true); +} + +static inline RE_Options DOTALL() { + return RE_Options().set_dotall(true); +} + +static inline RE_Options EXTENDED() { + return RE_Options().set_extended(true); +} + +// Interface for regular expression matching. Also corresponds to a +// pre-compiled regular expression. An "RE" object is safe for +// concurrent use by multiple threads. +class RE { + public: + // We provide implicit conversions from strings so that users can + // pass in a string or a "const char*" wherever an "RE" is expected. + RE(const string& pat) { Init(pat, NULL); } + RE(const string& pat, const RE_Options& option) { Init(pat, &option); } + RE(const char* pat) { Init(pat, NULL); } + RE(const char* pat, const RE_Options& option) { Init(pat, &option); } + RE(const unsigned char* pat) { + Init(reinterpret_cast<const char*>(pat), NULL); + } + RE(const unsigned char* pat, const RE_Options& option) { + Init(reinterpret_cast<const char*>(pat), &option); + } + + // Copy constructor & assignment - note that these are expensive + // because they recompile the expression. + RE(const RE& re) { Init(re.pattern_, &re.options_); } + const RE& operator=(const RE& re) { + if (this != &re) { + Cleanup(); + + // This is the code that originally came from Google + // Init(re.pattern_.c_str(), &re.options_); + + // This is the replacement from Ari Pollak + Init(re.pattern_, &re.options_); + } + return *this; + } + + + ~RE(); + + // The string specification for this RE. E.g. + // RE re("ab*c?d+"); + // re.pattern(); // "ab*c?d+" + const string& pattern() const { return pattern_; } + + // If RE could not be created properly, returns an error string. + // Else returns the empty string. + const string& error() const { return *error_; } + + /***** The useful part: the matching interface *****/ + + // This is provided so one can do pattern.ReplaceAll() just as + // easily as ReplaceAll(pattern-text, ....) + + bool FullMatch(const StringPiece& text, + const Arg& ptr1 = no_arg, + const Arg& ptr2 = no_arg, + const Arg& ptr3 = no_arg, + const Arg& ptr4 = no_arg, + const Arg& ptr5 = no_arg, + const Arg& ptr6 = no_arg, + const Arg& ptr7 = no_arg, + const Arg& ptr8 = no_arg, + const Arg& ptr9 = no_arg, + const Arg& ptr10 = no_arg, + const Arg& ptr11 = no_arg, + const Arg& ptr12 = no_arg, + const Arg& ptr13 = no_arg, + const Arg& ptr14 = no_arg, + const Arg& ptr15 = no_arg, + const Arg& ptr16 = no_arg) const; + + bool PartialMatch(const StringPiece& text, + const Arg& ptr1 = no_arg, + const Arg& ptr2 = no_arg, + const Arg& ptr3 = no_arg, + const Arg& ptr4 = no_arg, + const Arg& ptr5 = no_arg, + const Arg& ptr6 = no_arg, + const Arg& ptr7 = no_arg, + const Arg& ptr8 = no_arg, + const Arg& ptr9 = no_arg, + const Arg& ptr10 = no_arg, + const Arg& ptr11 = no_arg, + const Arg& ptr12 = no_arg, + const Arg& ptr13 = no_arg, + const Arg& ptr14 = no_arg, + const Arg& ptr15 = no_arg, + const Arg& ptr16 = no_arg) const; + + bool Consume(StringPiece* input, + const Arg& ptr1 = no_arg, + const Arg& ptr2 = no_arg, + const Arg& ptr3 = no_arg, + const Arg& ptr4 = no_arg, + const Arg& ptr5 = no_arg, + const Arg& ptr6 = no_arg, + const Arg& ptr7 = no_arg, + const Arg& ptr8 = no_arg, + const Arg& ptr9 = no_arg, + const Arg& ptr10 = no_arg, + const Arg& ptr11 = no_arg, + const Arg& ptr12 = no_arg, + const Arg& ptr13 = no_arg, + const Arg& ptr14 = no_arg, + const Arg& ptr15 = no_arg, + const Arg& ptr16 = no_arg) const; + + bool FindAndConsume(StringPiece* input, + const Arg& ptr1 = no_arg, + const Arg& ptr2 = no_arg, + const Arg& ptr3 = no_arg, + const Arg& ptr4 = no_arg, + const Arg& ptr5 = no_arg, + const Arg& ptr6 = no_arg, + const Arg& ptr7 = no_arg, + const Arg& ptr8 = no_arg, + const Arg& ptr9 = no_arg, + const Arg& ptr10 = no_arg, + const Arg& ptr11 = no_arg, + const Arg& ptr12 = no_arg, + const Arg& ptr13 = no_arg, + const Arg& ptr14 = no_arg, + const Arg& ptr15 = no_arg, + const Arg& ptr16 = no_arg) const; + + bool Replace(const StringPiece& rewrite, + string *str) const; + + int GlobalReplace(const StringPiece& rewrite, + string *str) const; + + bool Extract(const StringPiece &rewrite, + const StringPiece &text, + string *out) const; + + // Escapes all potentially meaningful regexp characters in + // 'unquoted'. The returned string, used as a regular expression, + // will exactly match the original string. For example, + // 1.5-2.0? + // may become: + // 1\.5\-2\.0\? + static string QuoteMeta(const StringPiece& unquoted); + + + /***** Generic matching interface *****/ + + // Type of match (TODO: Should be restructured as part of RE_Options) + enum Anchor { + UNANCHORED, // No anchoring + ANCHOR_START, // Anchor at start only + ANCHOR_BOTH // Anchor at start and end + }; + + // General matching routine. Stores the length of the match in + // "*consumed" if successful. + bool DoMatch(const StringPiece& text, + Anchor anchor, + int* consumed, + const Arg* const* args, int n) const; + + // Return the number of capturing subpatterns, or -1 if the + // regexp wasn't valid on construction. + int NumberOfCapturingGroups() const; + + private: + + void Init(const string& pattern, const RE_Options* options); + void Cleanup(); + + // Match against "text", filling in "vec" (up to "vecsize" * 2/3) with + // pairs of integers for the beginning and end positions of matched + // text. The first pair corresponds to the entire matched text; + // subsequent pairs correspond, in order, to parentheses-captured + // matches. Returns the number of pairs (one more than the number of + // the last subpattern with a match) if matching was successful + // and zero if the match failed. + // I.e. for RE("(foo)|(bar)|(baz)") it will return 2, 3, and 4 when matching + // against "foo", "bar", and "baz" respectively. + // When matching RE("(foo)|hello") against "hello", it will return 1. + // But the values for all subpattern are filled in into "vec". + int TryMatch(const StringPiece& text, + int startpos, + Anchor anchor, + int *vec, + int vecsize) const; + + // Append the "rewrite" string, with backslash subsitutions from "text" + // and "vec", to string "out". + bool Rewrite(string *out, + const StringPiece& rewrite, + const StringPiece& text, + int *vec, + int veclen) const; + + // internal implementation for DoMatch + bool DoMatchImpl(const StringPiece& text, + Anchor anchor, + int* consumed, + const Arg* const args[], + int n, + int* vec, + int vecsize) const; + + // Compile the regexp for the specified anchoring mode + pcre* Compile(Anchor anchor); + + string pattern_; + RE_Options options_; + pcre* re_full_; // For full matches + pcre* re_partial_; // For partial matches + const string* error_; // Error indicator (or points to empty string) +}; + +} // namespace pcrecpp + +#endif /* _PCRECPP_H */ diff --git a/pcre-7.4/pcrecpp_internal.h b/pcre-7.4/pcrecpp_internal.h new file mode 100644 index 0000000..0af9478 --- /dev/null +++ b/pcre-7.4/pcrecpp_internal.h @@ -0,0 +1,68 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +#ifndef PCRECPP_INTERNAL_H +#define PCRECPP_INTERNAL_H + +/* When compiling a DLL for Windows, the exported symbols have to be declared +using some MS magic. I found some useful information on this web page: +http://msdn2.microsoft.com/en-us/library/y4h7bcy6(VS.80).aspx. According to the +information there, using __declspec(dllexport) without "extern" we have a +definition; with "extern" we have a declaration. The settings here override the +setting in pcre.h. We use: + + PCRECPP_EXP_DECL for declarations + PCRECPP_EXP_DEFN for definitions of exported functions + +*/ + +#ifndef PCRECPP_EXP_DECL +# ifdef _WIN32 +# ifndef PCRECPP_STATIC +# define PCRECPP_EXP_DECL extern __declspec(dllexport) +# define PCRECPP_EXP_DEFN __declspec(dllexport) +# else +# define PCRECPP_EXP_DECL extern +# define PCRECPP_EXP_DEFN +# endif +# else +# define PCRECPP_EXP_DECL extern +# define PCRECPP_EXP_DEFN +# endif +#endif + +#endif /* PCRECPP_INTERNAL_H */ + +/* End of pcrecpp_internal.h */ diff --git a/pcre-7.4/pcrecpp_unittest.cc b/pcre-7.4/pcrecpp_unittest.cc new file mode 100644 index 0000000..463a11c --- /dev/null +++ b/pcre-7.4/pcrecpp_unittest.cc @@ -0,0 +1,1240 @@ +// -*- coding: utf-8 -*- +// +// Copyright (c) 2005 - 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat +// +// TODO: Test extractions for PartialMatch/Consume + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <cassert> +#include <vector> +#include "pcrecpp.h" + +using pcrecpp::StringPiece; +using pcrecpp::RE; +using pcrecpp::RE_Options; +using pcrecpp::Hex; +using pcrecpp::Octal; +using pcrecpp::CRadix; + +static bool VERBOSE_TEST = false; + +// CHECK dies with a fatal error if condition is not true. It is *not* +// controlled by NDEBUG, so the check will be executed regardless of +// compilation mode. Therefore, it is safe to do things like: +// CHECK_EQ(fp->Write(x), 4) +#define CHECK(condition) do { \ + if (!(condition)) { \ + fprintf(stderr, "%s:%d: Check failed: %s\n", \ + __FILE__, __LINE__, #condition); \ + exit(1); \ + } \ +} while (0) + +#define CHECK_EQ(a, b) CHECK(a == b) + +static void Timing1(int num_iters) { + // Same pattern lots of times + RE pattern("ruby:\\d+"); + StringPiece p("ruby:1234"); + for (int j = num_iters; j > 0; j--) { + CHECK(pattern.FullMatch(p)); + } +} + +static void Timing2(int num_iters) { + // Same pattern lots of times + RE pattern("ruby:(\\d+)"); + int i; + for (int j = num_iters; j > 0; j--) { + CHECK(pattern.FullMatch("ruby:1234", &i)); + CHECK_EQ(i, 1234); + } +} + +static void Timing3(int num_iters) { + string text_string; + for (int j = num_iters; j > 0; j--) { + text_string += "this is another line\n"; + } + + RE line_matcher(".*\n"); + string line; + StringPiece text(text_string); + int counter = 0; + while (line_matcher.Consume(&text)) { + counter++; + } + printf("Matched %d lines\n", counter); +} + +#if 0 // uncomment this if you have a way of defining VirtualProcessSize() + +static void LeakTest() { + // Check for memory leaks + unsigned long long initial_size = 0; + for (int i = 0; i < 100000; i++) { + if (i == 50000) { + initial_size = VirtualProcessSize(); + printf("Size after 50000: %llu\n", initial_size); + } + char buf[100]; // definitely big enough + sprintf(buf, "pat%09d", i); + RE newre(buf); + } + uint64 final_size = VirtualProcessSize(); + printf("Size after 100000: %llu\n", final_size); + const double growth = double(final_size - initial_size) / final_size; + printf("Growth: %0.2f%%", growth * 100); + CHECK(growth < 0.02); // Allow < 2% growth +} + +#endif + +static void RadixTests() { + printf("Testing hex\n"); + +#define CHECK_HEX(type, value) \ + do { \ + type v; \ + CHECK(RE("([0-9a-fA-F]+)[uUlL]*").FullMatch(#value, Hex(&v))); \ + CHECK_EQ(v, 0x ## value); \ + CHECK(RE("([0-9a-fA-FxX]+)[uUlL]*").FullMatch("0x" #value, CRadix(&v))); \ + CHECK_EQ(v, 0x ## value); \ + } while(0) + + CHECK_HEX(short, 2bad); + CHECK_HEX(unsigned short, 2badU); + CHECK_HEX(int, dead); + CHECK_HEX(unsigned int, deadU); + CHECK_HEX(long, 7eadbeefL); + CHECK_HEX(unsigned long, deadbeefUL); +#ifdef HAVE_LONG_LONG + CHECK_HEX(long long, 12345678deadbeefLL); +#endif +#ifdef HAVE_UNSIGNED_LONG_LONG + CHECK_HEX(unsigned long long, cafebabedeadbeefULL); +#endif + +#undef CHECK_HEX + + printf("Testing octal\n"); + +#define CHECK_OCTAL(type, value) \ + do { \ + type v; \ + CHECK(RE("([0-7]+)[uUlL]*").FullMatch(#value, Octal(&v))); \ + CHECK_EQ(v, 0 ## value); \ + CHECK(RE("([0-9a-fA-FxX]+)[uUlL]*").FullMatch("0" #value, CRadix(&v))); \ + CHECK_EQ(v, 0 ## value); \ + } while(0) + + CHECK_OCTAL(short, 77777); + CHECK_OCTAL(unsigned short, 177777U); + CHECK_OCTAL(int, 17777777777); + CHECK_OCTAL(unsigned int, 37777777777U); + CHECK_OCTAL(long, 17777777777L); + CHECK_OCTAL(unsigned long, 37777777777UL); +#ifdef HAVE_LONG_LONG + CHECK_OCTAL(long long, 777777777777777777777LL); +#endif +#ifdef HAVE_UNSIGNED_LONG_LONG + CHECK_OCTAL(unsigned long long, 1777777777777777777777ULL); +#endif + +#undef CHECK_OCTAL + + printf("Testing decimal\n"); + +#define CHECK_DECIMAL(type, value) \ + do { \ + type v; \ + CHECK(RE("(-?[0-9]+)[uUlL]*").FullMatch(#value, &v)); \ + CHECK_EQ(v, value); \ + CHECK(RE("(-?[0-9a-fA-FxX]+)[uUlL]*").FullMatch(#value, CRadix(&v))); \ + CHECK_EQ(v, value); \ + } while(0) + + CHECK_DECIMAL(short, -1); + CHECK_DECIMAL(unsigned short, 9999); + CHECK_DECIMAL(int, -1000); + CHECK_DECIMAL(unsigned int, 12345U); + CHECK_DECIMAL(long, -10000000L); + CHECK_DECIMAL(unsigned long, 3083324652U); +#ifdef HAVE_LONG_LONG + CHECK_DECIMAL(long long, -100000000000000LL); +#endif +#ifdef HAVE_UNSIGNED_LONG_LONG + CHECK_DECIMAL(unsigned long long, 1234567890987654321ULL); +#endif + +#undef CHECK_DECIMAL + +} + +static void TestReplace() { + printf("Testing Replace\n"); + + struct ReplaceTest { + const char *regexp; + const char *rewrite; + const char *original; + const char *single; + const char *global; + }; + static const ReplaceTest tests[] = { + { "(qu|[b-df-hj-np-tv-z]*)([a-z]+)", + "\\2\\1ay", + "the quick brown fox jumps over the lazy dogs.", + "ethay quick brown fox jumps over the lazy dogs.", + "ethay ickquay ownbray oxfay umpsjay overay ethay azylay ogsday." }, + { "\\w+", + "\\0-NOSPAM", + "paul.haahr@google.com", + "paul-NOSPAM.haahr@google.com", + "paul-NOSPAM.haahr-NOSPAM@google-NOSPAM.com-NOSPAM" }, + { "^", + "(START)", + "foo", + "(START)foo", + "(START)foo" }, + { "^", + "(START)", + "", + "(START)", + "(START)" }, + { "$", + "(END)", + "", + "(END)", + "(END)" }, + { "b", + "bb", + "ababababab", + "abbabababab", + "abbabbabbabbabb" }, + { "b", + "bb", + "bbbbbb", + "bbbbbbb", + "bbbbbbbbbbbb" }, + { "b+", + "bb", + "bbbbbb", + "bb", + "bb" }, + { "b*", + "bb", + "bbbbbb", + "bb", + "bb" }, + { "b*", + "bb", + "aaaaa", + "bbaaaaa", + "bbabbabbabbabbabb" }, + { "b*", + "bb", + "aa\naa\n", + "bbaa\naa\n", + "bbabbabb\nbbabbabb\nbb" }, + { "b*", + "bb", + "aa\raa\r", + "bbaa\raa\r", + "bbabbabb\rbbabbabb\rbb" }, + { "b*", + "bb", + "aa\r\naa\r\n", + "bbaa\r\naa\r\n", + "bbabbabb\r\nbbabbabb\r\nbb" }, +#ifdef SUPPORT_UTF8 + { "b*", + "bb", + "\xE3\x83\x9B\xE3\x83\xBC\xE3\x83\xA0\xE3\x81\xB8", // utf8 + "bb\xE3\x83\x9B\xE3\x83\xBC\xE3\x83\xA0\xE3\x81\xB8", + "bb\xE3\x83\x9B""bb""\xE3\x83\xBC""bb""\xE3\x83\xA0""bb""\xE3\x81\xB8""bb" }, + { "b*", + "bb", + "\xE3\x83\x9B\r\n\xE3\x83\xBC\r\xE3\x83\xA0\n\xE3\x81\xB8\r\n", // utf8 + "bb\xE3\x83\x9B\r\n\xE3\x83\xBC\r\xE3\x83\xA0\n\xE3\x81\xB8\r\n", + ("bb\xE3\x83\x9B""bb\r\nbb""\xE3\x83\xBC""bb\rbb""\xE3\x83\xA0" + "bb\nbb""\xE3\x81\xB8""bb\r\nbb") }, +#endif + { "", NULL, NULL, NULL, NULL } + }; + +#ifdef SUPPORT_UTF8 + const bool support_utf8 = true; +#else + const bool support_utf8 = false; +#endif + + for (const ReplaceTest *t = tests; t->original != NULL; ++t) { + RE re(t->regexp, RE_Options(PCRE_NEWLINE_CRLF).set_utf8(support_utf8)); + assert(re.error().empty()); + string one(t->original); + CHECK(re.Replace(t->rewrite, &one)); + CHECK_EQ(one, t->single); + string all(t->original); + CHECK(re.GlobalReplace(t->rewrite, &all) > 0); + CHECK_EQ(all, t->global); + } + + // One final test: test \r\n replacement when we're not in CRLF mode + { + RE re("b*", RE_Options(PCRE_NEWLINE_CR).set_utf8(support_utf8)); + assert(re.error().empty()); + string all("aa\r\naa\r\n"); + CHECK(re.GlobalReplace("bb", &all) > 0); + CHECK_EQ(all, string("bbabbabb\rbb\nbbabbabb\rbb\nbb")); + } + { + RE re("b*", RE_Options(PCRE_NEWLINE_LF).set_utf8(support_utf8)); + assert(re.error().empty()); + string all("aa\r\naa\r\n"); + CHECK(re.GlobalReplace("bb", &all) > 0); + CHECK_EQ(all, string("bbabbabb\rbb\nbbabbabb\rbb\nbb")); + } + // TODO: test what happens when no PCRE_NEWLINE_* flag is set. + // Alas, the answer depends on how pcre was compiled. +} + +static void TestExtract() { + printf("Testing Extract\n"); + + string s; + + CHECK(RE("(.*)@([^.]*)").Extract("\\2!\\1", "boris@kremvax.ru", &s)); + CHECK_EQ(s, "kremvax!boris"); + + // check the RE interface as well + CHECK(RE(".*").Extract("'\\0'", "foo", &s)); + CHECK_EQ(s, "'foo'"); + CHECK(!RE("bar").Extract("'\\0'", "baz", &s)); + CHECK_EQ(s, "'foo'"); +} + +static void TestConsume() { + printf("Testing Consume\n"); + + string word; + + string s(" aaa b!@#$@#$cccc"); + StringPiece input(s); + + RE r("\\s*(\\w+)"); // matches a word, possibly proceeded by whitespace + CHECK(r.Consume(&input, &word)); + CHECK_EQ(word, "aaa"); + CHECK(r.Consume(&input, &word)); + CHECK_EQ(word, "b"); + CHECK(! r.Consume(&input, &word)); +} + +static void TestFindAndConsume() { + printf("Testing FindAndConsume\n"); + + string word; + + string s(" aaa b!@#$@#$cccc"); + StringPiece input(s); + + RE r("(\\w+)"); // matches a word + CHECK(r.FindAndConsume(&input, &word)); + CHECK_EQ(word, "aaa"); + CHECK(r.FindAndConsume(&input, &word)); + CHECK_EQ(word, "b"); + CHECK(r.FindAndConsume(&input, &word)); + CHECK_EQ(word, "cccc"); + CHECK(! r.FindAndConsume(&input, &word)); +} + +static void TestMatchNumberPeculiarity() { + printf("Testing match-number peculiaraity\n"); + + string word1; + string word2; + string word3; + + RE r("(foo)|(bar)|(baz)"); + CHECK(r.PartialMatch("foo", &word1, &word2, &word3)); + CHECK_EQ(word1, "foo"); + CHECK_EQ(word2, ""); + CHECK_EQ(word3, ""); + CHECK(r.PartialMatch("bar", &word1, &word2, &word3)); + CHECK_EQ(word1, ""); + CHECK_EQ(word2, "bar"); + CHECK_EQ(word3, ""); + CHECK(r.PartialMatch("baz", &word1, &word2, &word3)); + CHECK_EQ(word1, ""); + CHECK_EQ(word2, ""); + CHECK_EQ(word3, "baz"); + CHECK(!r.PartialMatch("f", &word1, &word2, &word3)); + + string a; + CHECK(RE("(foo)|hello").FullMatch("hello", &a)); + CHECK_EQ(a, ""); +} + +static void TestRecursion() { + printf("Testing recursion\n"); + + // Get one string that passes (sometimes), one that never does. + string text_good("abcdefghijk"); + string text_bad("acdefghijkl"); + + // According to pcretest, matching text_good against (\w+)*b + // requires match_limit of at least 8192, and match_recursion_limit + // of at least 37. + + RE_Options options_ml; + options_ml.set_match_limit(8192); + RE re("(\\w+)*b", options_ml); + CHECK(re.PartialMatch(text_good) == true); + CHECK(re.PartialMatch(text_bad) == false); + CHECK(re.FullMatch(text_good) == false); + CHECK(re.FullMatch(text_bad) == false); + + options_ml.set_match_limit(1024); + RE re2("(\\w+)*b", options_ml); + CHECK(re2.PartialMatch(text_good) == false); // because of match_limit + CHECK(re2.PartialMatch(text_bad) == false); + CHECK(re2.FullMatch(text_good) == false); + CHECK(re2.FullMatch(text_bad) == false); + + RE_Options options_mlr; + options_mlr.set_match_limit_recursion(50); + RE re3("(\\w+)*b", options_mlr); + CHECK(re3.PartialMatch(text_good) == true); + CHECK(re3.PartialMatch(text_bad) == false); + CHECK(re3.FullMatch(text_good) == false); + CHECK(re3.FullMatch(text_bad) == false); + + options_mlr.set_match_limit_recursion(10); + RE re4("(\\w+)*b", options_mlr); + CHECK(re4.PartialMatch(text_good) == false); + CHECK(re4.PartialMatch(text_bad) == false); + CHECK(re4.FullMatch(text_good) == false); + CHECK(re4.FullMatch(text_bad) == false); +} + +// A meta-quoted string, interpreted as a pattern, should always match +// the original unquoted string. +static void TestQuoteMeta(string unquoted, RE_Options options = RE_Options()) { + string quoted = RE::QuoteMeta(unquoted); + RE re(quoted, options); + CHECK(re.FullMatch(unquoted)); +} + +// A string containing meaningful regexp characters, which is then meta- +// quoted, should not generally match a string the unquoted string does. +static void NegativeTestQuoteMeta(string unquoted, string should_not_match, + RE_Options options = RE_Options()) { + string quoted = RE::QuoteMeta(unquoted); + RE re(quoted, options); + CHECK(!re.FullMatch(should_not_match)); +} + +// Tests that quoted meta characters match their original strings, +// and that a few things that shouldn't match indeed do not. +static void TestQuotaMetaSimple() { + TestQuoteMeta("foo"); + TestQuoteMeta("foo.bar"); + TestQuoteMeta("foo\\.bar"); + TestQuoteMeta("[1-9]"); + TestQuoteMeta("1.5-2.0?"); + TestQuoteMeta("\\d"); + TestQuoteMeta("Who doesn't like ice cream?"); + TestQuoteMeta("((a|b)c?d*e+[f-h]i)"); + TestQuoteMeta("((?!)xxx).*yyy"); + TestQuoteMeta("(["); +} + +static void TestQuoteMetaSimpleNegative() { + NegativeTestQuoteMeta("foo", "bar"); + NegativeTestQuoteMeta("...", "bar"); + NegativeTestQuoteMeta("\\.", "."); + NegativeTestQuoteMeta("\\.", ".."); + NegativeTestQuoteMeta("(a)", "a"); + NegativeTestQuoteMeta("(a|b)", "a"); + NegativeTestQuoteMeta("(a|b)", "(a)"); + NegativeTestQuoteMeta("(a|b)", "a|b"); + NegativeTestQuoteMeta("[0-9]", "0"); + NegativeTestQuoteMeta("[0-9]", "0-9"); + NegativeTestQuoteMeta("[0-9]", "[9]"); + NegativeTestQuoteMeta("((?!)xxx)", "xxx"); +} + +static void TestQuoteMetaLatin1() { + TestQuoteMeta("3\xb2 = 9"); +} + +static void TestQuoteMetaUtf8() { +#ifdef SUPPORT_UTF8 + TestQuoteMeta("Pl\xc3\xa1\x63ido Domingo", pcrecpp::UTF8()); + TestQuoteMeta("xyz", pcrecpp::UTF8()); // No fancy utf8 + TestQuoteMeta("\xc2\xb0", pcrecpp::UTF8()); // 2-byte utf8 (degree symbol) + TestQuoteMeta("27\xc2\xb0 degrees", pcrecpp::UTF8()); // As a middle character + TestQuoteMeta("\xe2\x80\xb3", pcrecpp::UTF8()); // 3-byte utf8 (double prime) + TestQuoteMeta("\xf0\x9d\x85\x9f", pcrecpp::UTF8()); // 4-byte utf8 (music note) + TestQuoteMeta("27\xc2\xb0"); // Interpreted as Latin-1, but should still work + NegativeTestQuoteMeta("27\xc2\xb0", // 2-byte utf (degree symbol) + "27\\\xc2\\\xb0", + pcrecpp::UTF8()); +#endif +} + +static void TestQuoteMetaAll() { + printf("Testing QuoteMeta\n"); + TestQuotaMetaSimple(); + TestQuoteMetaSimpleNegative(); + TestQuoteMetaLatin1(); + TestQuoteMetaUtf8(); +} + +// +// Options tests contributed by +// Giuseppe Maxia, CTO, Stardata s.r.l. +// July 2005 +// +static void GetOneOptionResult( + const char *option_name, + const char *regex, + const char *str, + RE_Options options, + bool full, + string expected) { + + printf("Testing Option <%s>\n", option_name); + if(VERBOSE_TEST) + printf("/%s/ finds \"%s\" within \"%s\" \n", + regex, + expected.c_str(), + str); + string captured(""); + if (full) + RE(regex,options).FullMatch(str, &captured); + else + RE(regex,options).PartialMatch(str, &captured); + CHECK_EQ(captured, expected); +} + +static void TestOneOption( + const char *option_name, + const char *regex, + const char *str, + RE_Options options, + bool full, + bool assertive = true) { + + printf("Testing Option <%s>\n", option_name); + if (VERBOSE_TEST) + printf("'%s' %s /%s/ \n", + str, + (assertive? "matches" : "doesn't match"), + regex); + if (assertive) { + if (full) + CHECK(RE(regex,options).FullMatch(str)); + else + CHECK(RE(regex,options).PartialMatch(str)); + } else { + if (full) + CHECK(!RE(regex,options).FullMatch(str)); + else + CHECK(!RE(regex,options).PartialMatch(str)); + } +} + +static void Test_CASELESS() { + RE_Options options; + RE_Options options2; + + options.set_caseless(true); + TestOneOption("CASELESS (class)", "HELLO", "hello", options, false); + TestOneOption("CASELESS (class2)", "HELLO", "hello", options2.set_caseless(true), false); + TestOneOption("CASELESS (class)", "^[A-Z]+$", "Hello", options, false); + + TestOneOption("CASELESS (function)", "HELLO", "hello", pcrecpp::CASELESS(), false); + TestOneOption("CASELESS (function)", "^[A-Z]+$", "Hello", pcrecpp::CASELESS(), false); + options.set_caseless(false); + TestOneOption("no CASELESS", "HELLO", "hello", options, false, false); +} + +static void Test_MULTILINE() { + RE_Options options; + RE_Options options2; + const char *str = "HELLO\n" "cruel\n" "world\n"; + + options.set_multiline(true); + TestOneOption("MULTILINE (class)", "^cruel$", str, options, false); + TestOneOption("MULTILINE (class2)", "^cruel$", str, options2.set_multiline(true), false); + TestOneOption("MULTILINE (function)", "^cruel$", str, pcrecpp::MULTILINE(), false); + options.set_multiline(false); + TestOneOption("no MULTILINE", "^cruel$", str, options, false, false); +} + +static void Test_DOTALL() { + RE_Options options; + RE_Options options2; + const char *str = "HELLO\n" "cruel\n" "world"; + + options.set_dotall(true); + TestOneOption("DOTALL (class)", "HELLO.*world", str, options, true); + TestOneOption("DOTALL (class2)", "HELLO.*world", str, options2.set_dotall(true), true); + TestOneOption("DOTALL (function)", "HELLO.*world", str, pcrecpp::DOTALL(), true); + options.set_dotall(false); + TestOneOption("no DOTALL", "HELLO.*world", str, options, true, false); +} + +static void Test_DOLLAR_ENDONLY() { + RE_Options options; + RE_Options options2; + const char *str = "HELLO world\n"; + + TestOneOption("no DOLLAR_ENDONLY", "world$", str, options, false); + options.set_dollar_endonly(true); + TestOneOption("DOLLAR_ENDONLY 1", "world$", str, options, false, false); + TestOneOption("DOLLAR_ENDONLY 2", "world$", str, options2.set_dollar_endonly(true), false, false); +} + +static void Test_EXTRA() { + RE_Options options; + const char *str = "HELLO"; + + options.set_extra(true); + TestOneOption("EXTRA 1", "\\HELL\\O", str, options, true, false ); + TestOneOption("EXTRA 2", "\\HELL\\O", str, RE_Options().set_extra(true), true, false ); + options.set_extra(false); + TestOneOption("no EXTRA", "\\HELL\\O", str, options, true ); +} + +static void Test_EXTENDED() { + RE_Options options; + RE_Options options2; + const char *str = "HELLO world"; + + options.set_extended(true); + TestOneOption("EXTENDED (class)", "HELLO world", str, options, false, false); + TestOneOption("EXTENDED (class2)", "HELLO world", str, options2.set_extended(true), false, false); + TestOneOption("EXTENDED (class)", + "^ HE L{2} O " + "\\s+ " + "\\w+ $ ", + str, + options, + false); + + TestOneOption("EXTENDED (function)", "HELLO world", str, pcrecpp::EXTENDED(), false, false); + TestOneOption("EXTENDED (function)", + "^ HE L{2} O " + "\\s+ " + "\\w+ $ ", + str, + pcrecpp::EXTENDED(), + false); + + options.set_extended(false); + TestOneOption("no EXTENDED", "HELLO world", str, options, false); +} + +static void Test_NO_AUTO_CAPTURE() { + RE_Options options; + const char *str = "HELLO world"; + string captured; + + printf("Testing Option <no NO_AUTO_CAPTURE>\n"); + if (VERBOSE_TEST) + printf("parentheses capture text\n"); + RE re("(world|universe)$", options); + CHECK(re.Extract("\\1", str , &captured)); + CHECK_EQ(captured, "world"); + options.set_no_auto_capture(true); + printf("testing Option <NO_AUTO_CAPTURE>\n"); + if (VERBOSE_TEST) + printf("parentheses do not capture text\n"); + re.Extract("\\1",str, &captured ); + CHECK_EQ(captured, "world"); +} + +static void Test_UNGREEDY() { + RE_Options options; + const char *str = "HELLO, 'this' is the 'world'"; + + options.set_ungreedy(true); + GetOneOptionResult("UNGREEDY 1", "('.*')", str, options, false, "'this'" ); + GetOneOptionResult("UNGREEDY 2", "('.*')", str, RE_Options().set_ungreedy(true), false, "'this'" ); + GetOneOptionResult("UNGREEDY", "('.*?')", str, options, false, "'this' is the 'world'" ); + + options.set_ungreedy(false); + GetOneOptionResult("no UNGREEDY", "('.*')", str, options, false, "'this' is the 'world'" ); + GetOneOptionResult("no UNGREEDY", "('.*?')", str, options, false, "'this'" ); +} + +static void Test_all_options() { + const char *str = "HELLO\n" "cruel\n" "world"; + RE_Options options; + options.set_all_options(PCRE_CASELESS | PCRE_DOTALL); + + TestOneOption("all_options (CASELESS|DOTALL)", "^hello.*WORLD", str , options, false); + options.set_all_options(0); + TestOneOption("all_options (0)", "^hello.*WORLD", str , options, false, false); + options.set_all_options(PCRE_MULTILINE | PCRE_EXTENDED); + + TestOneOption("all_options (MULTILINE|EXTENDED)", " ^ c r u e l $ ", str, options, false); + TestOneOption("all_options (MULTILINE|EXTENDED) with constructor", + " ^ c r u e l $ ", + str, + RE_Options(PCRE_MULTILINE | PCRE_EXTENDED), + false); + + TestOneOption("all_options (MULTILINE|EXTENDED) with concatenation", + " ^ c r u e l $ ", + str, + RE_Options() + .set_multiline(true) + .set_extended(true), + false); + + options.set_all_options(0); + TestOneOption("all_options (0)", "^ c r u e l $", str, options, false, false); + +} + +static void TestOptions() { + printf("Testing Options\n"); + Test_CASELESS(); + Test_MULTILINE(); + Test_DOTALL(); + Test_DOLLAR_ENDONLY(); + Test_EXTENDED(); + Test_NO_AUTO_CAPTURE(); + Test_UNGREEDY(); + Test_EXTRA(); + Test_all_options(); +} + +static void TestConstructors() { + printf("Testing constructors\n"); + + RE_Options options; + options.set_dotall(true); + const char *str = "HELLO\n" "cruel\n" "world"; + + RE orig("HELLO.*world", options); + CHECK(orig.FullMatch(str)); + + RE copy1(orig); + CHECK(copy1.FullMatch(str)); + + RE copy2("not a match"); + CHECK(!copy2.FullMatch(str)); + copy2 = copy1; + CHECK(copy2.FullMatch(str)); + copy2 = orig; + CHECK(copy2.FullMatch(str)); + + // Make sure when we assign to ourselves, nothing bad happens + orig = orig; + copy1 = copy1; + copy2 = copy2; + CHECK(orig.FullMatch(str)); + CHECK(copy1.FullMatch(str)); + CHECK(copy2.FullMatch(str)); +} + +int main(int argc, char** argv) { + // Treat any flag as --help + if (argc > 1 && argv[1][0] == '-') { + printf("Usage: %s [timing1|timing2|timing3 num-iters]\n" + " If 'timingX ###' is specified, run the given timing test\n" + " with the given number of iterations, rather than running\n" + " the default corectness test.\n", argv[0]); + return 0; + } + + if (argc > 1) { + if ( argc == 2 || atoi(argv[2]) == 0) { + printf("timing mode needs a num-iters argument\n"); + return 1; + } + if (!strcmp(argv[1], "timing1")) + Timing1(atoi(argv[2])); + else if (!strcmp(argv[1], "timing2")) + Timing2(atoi(argv[2])); + else if (!strcmp(argv[1], "timing3")) + Timing3(atoi(argv[2])); + else + printf("Unknown argument '%s'\n", argv[1]); + return 0; + } + + printf("Testing FullMatch\n"); + + int i; + string s; + + /***** FullMatch with no args *****/ + + CHECK(RE("h.*o").FullMatch("hello")); + CHECK(!RE("h.*o").FullMatch("othello")); // Must be anchored at front + CHECK(!RE("h.*o").FullMatch("hello!")); // Must be anchored at end + CHECK(RE("a*").FullMatch("aaaa")); // Fullmatch with normal op + CHECK(RE("a*?").FullMatch("aaaa")); // Fullmatch with nongreedy op + CHECK(RE("a*?\\z").FullMatch("aaaa")); // Two unusual ops + + /***** FullMatch with args *****/ + + // Zero-arg + CHECK(RE("\\d+").FullMatch("1001")); + + // Single-arg + CHECK(RE("(\\d+)").FullMatch("1001", &i)); + CHECK_EQ(i, 1001); + CHECK(RE("(-?\\d+)").FullMatch("-123", &i)); + CHECK_EQ(i, -123); + CHECK(!RE("()\\d+").FullMatch("10", &i)); + CHECK(!RE("(\\d+)").FullMatch("1234567890123456789012345678901234567890", + &i)); + + // Digits surrounding integer-arg + CHECK(RE("1(\\d*)4").FullMatch("1234", &i)); + CHECK_EQ(i, 23); + CHECK(RE("(\\d)\\d+").FullMatch("1234", &i)); + CHECK_EQ(i, 1); + CHECK(RE("(-\\d)\\d+").FullMatch("-1234", &i)); + CHECK_EQ(i, -1); + CHECK(RE("(\\d)").PartialMatch("1234", &i)); + CHECK_EQ(i, 1); + CHECK(RE("(-\\d)").PartialMatch("-1234", &i)); + CHECK_EQ(i, -1); + + // String-arg + CHECK(RE("h(.*)o").FullMatch("hello", &s)); + CHECK_EQ(s, string("ell")); + + // StringPiece-arg + StringPiece sp; + CHECK(RE("(\\w+):(\\d+)").FullMatch("ruby:1234", &sp, &i)); + CHECK_EQ(sp.size(), 4); + CHECK(memcmp(sp.data(), "ruby", 4) == 0); + CHECK_EQ(i, 1234); + + // Multi-arg + CHECK(RE("(\\w+):(\\d+)").FullMatch("ruby:1234", &s, &i)); + CHECK_EQ(s, string("ruby")); + CHECK_EQ(i, 1234); + + // Ignored arg + CHECK(RE("(\\w+)(:)(\\d+)").FullMatch("ruby:1234", &s, (void*)NULL, &i)); + CHECK_EQ(s, string("ruby")); + CHECK_EQ(i, 1234); + + // Type tests + { + char c; + CHECK(RE("(H)ello").FullMatch("Hello", &c)); + CHECK_EQ(c, 'H'); + } + { + unsigned char c; + CHECK(RE("(H)ello").FullMatch("Hello", &c)); + CHECK_EQ(c, static_cast<unsigned char>('H')); + } + { + short v; + CHECK(RE("(-?\\d+)").FullMatch("100", &v)); CHECK_EQ(v, 100); + CHECK(RE("(-?\\d+)").FullMatch("-100", &v)); CHECK_EQ(v, -100); + CHECK(RE("(-?\\d+)").FullMatch("32767", &v)); CHECK_EQ(v, 32767); + CHECK(RE("(-?\\d+)").FullMatch("-32768", &v)); CHECK_EQ(v, -32768); + CHECK(!RE("(-?\\d+)").FullMatch("-32769", &v)); + CHECK(!RE("(-?\\d+)").FullMatch("32768", &v)); + } + { + unsigned short v; + CHECK(RE("(\\d+)").FullMatch("100", &v)); CHECK_EQ(v, 100); + CHECK(RE("(\\d+)").FullMatch("32767", &v)); CHECK_EQ(v, 32767); + CHECK(RE("(\\d+)").FullMatch("65535", &v)); CHECK_EQ(v, 65535); + CHECK(!RE("(\\d+)").FullMatch("65536", &v)); + } + { + int v; + static const int max_value = 0x7fffffff; + static const int min_value = -max_value - 1; + CHECK(RE("(-?\\d+)").FullMatch("100", &v)); CHECK_EQ(v, 100); + CHECK(RE("(-?\\d+)").FullMatch("-100", &v)); CHECK_EQ(v, -100); + CHECK(RE("(-?\\d+)").FullMatch("2147483647", &v)); CHECK_EQ(v, max_value); + CHECK(RE("(-?\\d+)").FullMatch("-2147483648", &v)); CHECK_EQ(v, min_value); + CHECK(!RE("(-?\\d+)").FullMatch("-2147483649", &v)); + CHECK(!RE("(-?\\d+)").FullMatch("2147483648", &v)); + } + { + unsigned int v; + static const unsigned int max_value = 0xfffffffful; + CHECK(RE("(\\d+)").FullMatch("100", &v)); CHECK_EQ(v, 100); + CHECK(RE("(\\d+)").FullMatch("4294967295", &v)); CHECK_EQ(v, max_value); + CHECK(!RE("(\\d+)").FullMatch("4294967296", &v)); + } +#ifdef HAVE_LONG_LONG +# if defined(__MINGW__) || defined(__MINGW32__) +# define LLD "%I64d" +# define LLU "%I64u" +# else +# define LLD "%lld" +# define LLU "%llu" +# endif + { + long long v; + static const long long max_value = 0x7fffffffffffffffLL; + static const long long min_value = -max_value - 1; + char buf[32]; // definitely big enough for a long long + + CHECK(RE("(-?\\d+)").FullMatch("100", &v)); CHECK_EQ(v, 100); + CHECK(RE("(-?\\d+)").FullMatch("-100",&v)); CHECK_EQ(v, -100); + + sprintf(buf, LLD, max_value); + CHECK(RE("(-?\\d+)").FullMatch(buf,&v)); CHECK_EQ(v, max_value); + + sprintf(buf, LLD, min_value); + CHECK(RE("(-?\\d+)").FullMatch(buf,&v)); CHECK_EQ(v, min_value); + + sprintf(buf, LLD, max_value); + assert(buf[strlen(buf)-1] != '9'); + buf[strlen(buf)-1]++; + CHECK(!RE("(-?\\d+)").FullMatch(buf, &v)); + + sprintf(buf, LLD, min_value); + assert(buf[strlen(buf)-1] != '9'); + buf[strlen(buf)-1]++; + CHECK(!RE("(-?\\d+)").FullMatch(buf, &v)); + } +#endif +#if defined HAVE_UNSIGNED_LONG_LONG && defined HAVE_LONG_LONG + { + unsigned long long v; + long long v2; + static const unsigned long long max_value = 0xffffffffffffffffULL; + char buf[32]; // definitely big enough for a unsigned long long + + CHECK(RE("(-?\\d+)").FullMatch("100",&v)); CHECK_EQ(v, 100); + CHECK(RE("(-?\\d+)").FullMatch("-100",&v2)); CHECK_EQ(v2, -100); + + sprintf(buf, LLU, max_value); + CHECK(RE("(-?\\d+)").FullMatch(buf,&v)); CHECK_EQ(v, max_value); + + assert(buf[strlen(buf)-1] != '9'); + buf[strlen(buf)-1]++; + CHECK(!RE("(-?\\d+)").FullMatch(buf, &v)); + } +#endif + { + float v; + CHECK(RE("(.*)").FullMatch("100", &v)); + CHECK(RE("(.*)").FullMatch("-100.", &v)); + CHECK(RE("(.*)").FullMatch("1e23", &v)); + } + { + double v; + CHECK(RE("(.*)").FullMatch("100", &v)); + CHECK(RE("(.*)").FullMatch("-100.", &v)); + CHECK(RE("(.*)").FullMatch("1e23", &v)); + } + + // Check that matching is fully anchored + CHECK(!RE("(\\d+)").FullMatch("x1001", &i)); + CHECK(!RE("(\\d+)").FullMatch("1001x", &i)); + CHECK(RE("x(\\d+)").FullMatch("x1001", &i)); CHECK_EQ(i, 1001); + CHECK(RE("(\\d+)x").FullMatch("1001x", &i)); CHECK_EQ(i, 1001); + + // Braces + CHECK(RE("[0-9a-f+.-]{5,}").FullMatch("0abcd")); + CHECK(RE("[0-9a-f+.-]{5,}").FullMatch("0abcde")); + CHECK(!RE("[0-9a-f+.-]{5,}").FullMatch("0abc")); + + // Complicated RE + CHECK(RE("foo|bar|[A-Z]").FullMatch("foo")); + CHECK(RE("foo|bar|[A-Z]").FullMatch("bar")); + CHECK(RE("foo|bar|[A-Z]").FullMatch("X")); + CHECK(!RE("foo|bar|[A-Z]").FullMatch("XY")); + + // Check full-match handling (needs '$' tacked on internally) + CHECK(RE("fo|foo").FullMatch("fo")); + CHECK(RE("fo|foo").FullMatch("foo")); + CHECK(RE("fo|foo$").FullMatch("fo")); + CHECK(RE("fo|foo$").FullMatch("foo")); + CHECK(RE("foo$").FullMatch("foo")); + CHECK(!RE("foo\\$").FullMatch("foo$bar")); + CHECK(!RE("fo|bar").FullMatch("fox")); + + // Uncomment the following if we change the handling of '$' to + // prevent it from matching a trailing newline + if (false) { + // Check that we don't get bitten by pcre's special handling of a + // '\n' at the end of the string matching '$' + CHECK(!RE("foo$").PartialMatch("foo\n")); + } + + // Number of args + int a[16]; + CHECK(RE("").FullMatch("")); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d){1}").FullMatch("1", + &a[0])); + CHECK_EQ(a[0], 1); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)").FullMatch("12", + &a[0], &a[1])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)").FullMatch("123", + &a[0], &a[1], &a[2])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)(\\d)").FullMatch("1234", + &a[0], &a[1], &a[2], &a[3])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + CHECK_EQ(a[3], 4); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)(\\d)(\\d)").FullMatch("12345", + &a[0], &a[1], &a[2], + &a[3], &a[4])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + CHECK_EQ(a[3], 4); + CHECK_EQ(a[4], 5); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)").FullMatch("123456", + &a[0], &a[1], &a[2], + &a[3], &a[4], &a[5])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + CHECK_EQ(a[3], 4); + CHECK_EQ(a[4], 5); + CHECK_EQ(a[5], 6); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)").FullMatch("1234567", + &a[0], &a[1], &a[2], &a[3], + &a[4], &a[5], &a[6])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + CHECK_EQ(a[3], 4); + CHECK_EQ(a[4], 5); + CHECK_EQ(a[5], 6); + CHECK_EQ(a[6], 7); + + memset(a, 0, sizeof(0)); + CHECK(RE("(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)" + "(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)").FullMatch( + "1234567890123456", + &a[0], &a[1], &a[2], &a[3], + &a[4], &a[5], &a[6], &a[7], + &a[8], &a[9], &a[10], &a[11], + &a[12], &a[13], &a[14], &a[15])); + CHECK_EQ(a[0], 1); + CHECK_EQ(a[1], 2); + CHECK_EQ(a[2], 3); + CHECK_EQ(a[3], 4); + CHECK_EQ(a[4], 5); + CHECK_EQ(a[5], 6); + CHECK_EQ(a[6], 7); + CHECK_EQ(a[7], 8); + CHECK_EQ(a[8], 9); + CHECK_EQ(a[9], 0); + CHECK_EQ(a[10], 1); + CHECK_EQ(a[11], 2); + CHECK_EQ(a[12], 3); + CHECK_EQ(a[13], 4); + CHECK_EQ(a[14], 5); + CHECK_EQ(a[15], 6); + + /***** PartialMatch *****/ + + printf("Testing PartialMatch\n"); + + CHECK(RE("h.*o").PartialMatch("hello")); + CHECK(RE("h.*o").PartialMatch("othello")); + CHECK(RE("h.*o").PartialMatch("hello!")); + CHECK(RE("((((((((((((((((((((x))))))))))))))))))))").PartialMatch("x")); + + /***** other tests *****/ + + RadixTests(); + TestReplace(); + TestExtract(); + TestConsume(); + TestFindAndConsume(); + TestQuoteMetaAll(); + TestMatchNumberPeculiarity(); + + // Check the pattern() accessor + { + const string kPattern = "http://([^/]+)/.*"; + const RE re(kPattern); + CHECK_EQ(kPattern, re.pattern()); + } + + // Check RE error field. + { + RE re("foo"); + CHECK(re.error().empty()); // Must have no error + } + +#ifdef SUPPORT_UTF8 + // Check UTF-8 handling + { + printf("Testing UTF-8 handling\n"); + + // Three Japanese characters (nihongo) + const unsigned char utf8_string[] = { + 0xe6, 0x97, 0xa5, // 65e5 + 0xe6, 0x9c, 0xac, // 627c + 0xe8, 0xaa, 0x9e, // 8a9e + 0 + }; + const unsigned char utf8_pattern[] = { + '.', + 0xe6, 0x9c, 0xac, // 627c + '.', + 0 + }; + + // Both should match in either mode, bytes or UTF-8 + RE re_test1("........."); + CHECK(re_test1.FullMatch(utf8_string)); + RE re_test2("...", pcrecpp::UTF8()); + CHECK(re_test2.FullMatch(utf8_string)); + + // Check that '.' matches one byte or UTF-8 character + // according to the mode. + string ss; + RE re_test3("(.)"); + CHECK(re_test3.PartialMatch(utf8_string, &ss)); + CHECK_EQ(ss, string("\xe6")); + RE re_test4("(.)", pcrecpp::UTF8()); + CHECK(re_test4.PartialMatch(utf8_string, &ss)); + CHECK_EQ(ss, string("\xe6\x97\xa5")); + + // Check that string matches itself in either mode + RE re_test5(utf8_string); + CHECK(re_test5.FullMatch(utf8_string)); + RE re_test6(utf8_string, pcrecpp::UTF8()); + CHECK(re_test6.FullMatch(utf8_string)); + + // Check that pattern matches string only in UTF8 mode + RE re_test7(utf8_pattern); + CHECK(!re_test7.FullMatch(utf8_string)); + RE re_test8(utf8_pattern, pcrecpp::UTF8()); + CHECK(re_test8.FullMatch(utf8_string)); + } + + // Check that ungreedy, UTF8 regular expressions don't match when they + // oughtn't -- see bug 82246. + { + // This code always worked. + const char* pattern = "\\w+X"; + const string target = "a aX"; + RE match_sentence(pattern); + RE match_sentence_re(pattern, pcrecpp::UTF8()); + + CHECK(!match_sentence.FullMatch(target)); + CHECK(!match_sentence_re.FullMatch(target)); + } + + { + const char* pattern = "(?U)\\w+X"; + const string target = "a aX"; + RE match_sentence(pattern); + RE match_sentence_re(pattern, pcrecpp::UTF8()); + + CHECK(!match_sentence.FullMatch(target)); + CHECK(!match_sentence_re.FullMatch(target)); + } +#endif /* def SUPPORT_UTF8 */ + + printf("Testing error reporting\n"); + + { RE re("a\\1"); CHECK(!re.error().empty()); } + { + RE re("a[x"); + CHECK(!re.error().empty()); + } + { + RE re("a[z-a]"); + CHECK(!re.error().empty()); + } + { + RE re("a[[:foobar:]]"); + CHECK(!re.error().empty()); + } + { + RE re("a(b"); + CHECK(!re.error().empty()); + } + { + RE re("a\\"); + CHECK(!re.error().empty()); + } + + // Test that recursion is stopped + TestRecursion(); + + // Test Options + if (getenv("VERBOSE_TEST") != NULL) + VERBOSE_TEST = true; + TestOptions(); + + // Test the constructors + TestConstructors(); + + // Done + printf("OK\n"); + + return 0; +} diff --git a/pcre-7.4/pcrecpparg.h b/pcre-7.4/pcrecpparg.h new file mode 100644 index 0000000..c5bfae0 --- /dev/null +++ b/pcre-7.4/pcrecpparg.h @@ -0,0 +1,173 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat + +#ifndef _PCRECPPARG_H +#define _PCRECPPARG_H + +#include <stdlib.h> // for NULL +#include <string> + +#include <pcre.h> + +namespace pcrecpp { + +class StringPiece; + +// Hex/Octal/Binary? + +// Special class for parsing into objects that define a ParseFrom() method +template <class T> +class _RE_MatchObject { + public: + static inline bool Parse(const char* str, int n, void* dest) { + T* object = reinterpret_cast<T*>(dest); + return object->ParseFrom(str, n); + } +}; + +class PCRECPP_EXP_DEFN Arg { + public: + // Empty constructor so we can declare arrays of Arg + Arg(); + + // Constructor specially designed for NULL arguments + Arg(void*); + + typedef bool (*Parser)(const char* str, int n, void* dest); + +// Type-specific parsers +#define PCRE_MAKE_PARSER(type,name) \ + Arg(type* p) : arg_(p), parser_(name) { } \ + Arg(type* p, Parser parser) : arg_(p), parser_(parser) { } + + + PCRE_MAKE_PARSER(char, parse_char); + PCRE_MAKE_PARSER(unsigned char, parse_uchar); + PCRE_MAKE_PARSER(short, parse_short); + PCRE_MAKE_PARSER(unsigned short, parse_ushort); + PCRE_MAKE_PARSER(int, parse_int); + PCRE_MAKE_PARSER(unsigned int, parse_uint); + PCRE_MAKE_PARSER(long, parse_long); + PCRE_MAKE_PARSER(unsigned long, parse_ulong); +#if 1 + PCRE_MAKE_PARSER(long long, parse_longlong); +#endif +#if 1 + PCRE_MAKE_PARSER(unsigned long long, parse_ulonglong); +#endif + PCRE_MAKE_PARSER(float, parse_float); + PCRE_MAKE_PARSER(double, parse_double); + PCRE_MAKE_PARSER(std::string, parse_string); + PCRE_MAKE_PARSER(StringPiece, parse_stringpiece); + +#undef PCRE_MAKE_PARSER + + // Generic constructor + template <class T> Arg(T*, Parser parser); + // Generic constructor template + template <class T> Arg(T* p) + : arg_(p), parser_(_RE_MatchObject<T>::Parse) { + } + + // Parse the data + bool Parse(const char* str, int n) const; + + private: + void* arg_; + Parser parser_; + + static bool parse_null (const char* str, int n, void* dest); + static bool parse_char (const char* str, int n, void* dest); + static bool parse_uchar (const char* str, int n, void* dest); + static bool parse_float (const char* str, int n, void* dest); + static bool parse_double (const char* str, int n, void* dest); + static bool parse_string (const char* str, int n, void* dest); + static bool parse_stringpiece (const char* str, int n, void* dest); + +#define PCRE_DECLARE_INTEGER_PARSER(name) \ + private: \ + static bool parse_ ## name(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _radix( \ + const char* str, int n, void* dest, int radix); \ + public: \ + static bool parse_ ## name ## _hex(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _octal(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _cradix(const char* str, int n, void* dest) + + PCRE_DECLARE_INTEGER_PARSER(short); + PCRE_DECLARE_INTEGER_PARSER(ushort); + PCRE_DECLARE_INTEGER_PARSER(int); + PCRE_DECLARE_INTEGER_PARSER(uint); + PCRE_DECLARE_INTEGER_PARSER(long); + PCRE_DECLARE_INTEGER_PARSER(ulong); + PCRE_DECLARE_INTEGER_PARSER(longlong); + PCRE_DECLARE_INTEGER_PARSER(ulonglong); + +#undef PCRE_DECLARE_INTEGER_PARSER +}; + +inline Arg::Arg() : arg_(NULL), parser_(parse_null) { } +inline Arg::Arg(void* p) : arg_(p), parser_(parse_null) { } + +inline bool Arg::Parse(const char* str, int n) const { + return (*parser_)(str, n, arg_); +} + +// This part of the parser, appropriate only for ints, deals with bases +#define MAKE_INTEGER_PARSER(type, name) \ + inline Arg Hex(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _hex); } \ + inline Arg Octal(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _octal); } \ + inline Arg CRadix(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _cradix); } + +MAKE_INTEGER_PARSER(short, short) /* */ +MAKE_INTEGER_PARSER(unsigned short, ushort) /* */ +MAKE_INTEGER_PARSER(int, int) /* Don't use semicolons */ +MAKE_INTEGER_PARSER(unsigned int, uint) /* after these statement */ +MAKE_INTEGER_PARSER(long, long) /* because they can cause */ +MAKE_INTEGER_PARSER(unsigned long, ulong) /* compiler warnings if */ +#if 1 /* the checking level is */ +MAKE_INTEGER_PARSER(long long, longlong) /* turned up high enough. */ +#endif /* */ +#if 1 /* */ +MAKE_INTEGER_PARSER(unsigned long long, ulonglong) /* */ +#endif + +#undef PCRE_IS_SET +#undef PCRE_SET_OR_CLEAR +#undef MAKE_INTEGER_PARSER + +} // namespace pcrecpp + + +#endif /* _PCRECPPARG_H */ diff --git a/pcre-7.4/pcrecpparg.h.in b/pcre-7.4/pcrecpparg.h.in new file mode 100644 index 0000000..83cc44b --- /dev/null +++ b/pcre-7.4/pcrecpparg.h.in @@ -0,0 +1,173 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: Sanjay Ghemawat + +#ifndef _PCRECPPARG_H +#define _PCRECPPARG_H + +#include <stdlib.h> // for NULL +#include <string> + +#include <pcre.h> + +namespace pcrecpp { + +class StringPiece; + +// Hex/Octal/Binary? + +// Special class for parsing into objects that define a ParseFrom() method +template <class T> +class _RE_MatchObject { + public: + static inline bool Parse(const char* str, int n, void* dest) { + T* object = reinterpret_cast<T*>(dest); + return object->ParseFrom(str, n); + } +}; + +class PCRECPP_EXP_DEFN Arg { + public: + // Empty constructor so we can declare arrays of Arg + Arg(); + + // Constructor specially designed for NULL arguments + Arg(void*); + + typedef bool (*Parser)(const char* str, int n, void* dest); + +// Type-specific parsers +#define PCRE_MAKE_PARSER(type,name) \ + Arg(type* p) : arg_(p), parser_(name) { } \ + Arg(type* p, Parser parser) : arg_(p), parser_(parser) { } + + + PCRE_MAKE_PARSER(char, parse_char); + PCRE_MAKE_PARSER(unsigned char, parse_uchar); + PCRE_MAKE_PARSER(short, parse_short); + PCRE_MAKE_PARSER(unsigned short, parse_ushort); + PCRE_MAKE_PARSER(int, parse_int); + PCRE_MAKE_PARSER(unsigned int, parse_uint); + PCRE_MAKE_PARSER(long, parse_long); + PCRE_MAKE_PARSER(unsigned long, parse_ulong); +#if @pcre_have_long_long@ + PCRE_MAKE_PARSER(long long, parse_longlong); +#endif +#if @pcre_have_ulong_long@ + PCRE_MAKE_PARSER(unsigned long long, parse_ulonglong); +#endif + PCRE_MAKE_PARSER(float, parse_float); + PCRE_MAKE_PARSER(double, parse_double); + PCRE_MAKE_PARSER(std::string, parse_string); + PCRE_MAKE_PARSER(StringPiece, parse_stringpiece); + +#undef PCRE_MAKE_PARSER + + // Generic constructor + template <class T> Arg(T*, Parser parser); + // Generic constructor template + template <class T> Arg(T* p) + : arg_(p), parser_(_RE_MatchObject<T>::Parse) { + } + + // Parse the data + bool Parse(const char* str, int n) const; + + private: + void* arg_; + Parser parser_; + + static bool parse_null (const char* str, int n, void* dest); + static bool parse_char (const char* str, int n, void* dest); + static bool parse_uchar (const char* str, int n, void* dest); + static bool parse_float (const char* str, int n, void* dest); + static bool parse_double (const char* str, int n, void* dest); + static bool parse_string (const char* str, int n, void* dest); + static bool parse_stringpiece (const char* str, int n, void* dest); + +#define PCRE_DECLARE_INTEGER_PARSER(name) \ + private: \ + static bool parse_ ## name(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _radix( \ + const char* str, int n, void* dest, int radix); \ + public: \ + static bool parse_ ## name ## _hex(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _octal(const char* str, int n, void* dest); \ + static bool parse_ ## name ## _cradix(const char* str, int n, void* dest) + + PCRE_DECLARE_INTEGER_PARSER(short); + PCRE_DECLARE_INTEGER_PARSER(ushort); + PCRE_DECLARE_INTEGER_PARSER(int); + PCRE_DECLARE_INTEGER_PARSER(uint); + PCRE_DECLARE_INTEGER_PARSER(long); + PCRE_DECLARE_INTEGER_PARSER(ulong); + PCRE_DECLARE_INTEGER_PARSER(longlong); + PCRE_DECLARE_INTEGER_PARSER(ulonglong); + +#undef PCRE_DECLARE_INTEGER_PARSER +}; + +inline Arg::Arg() : arg_(NULL), parser_(parse_null) { } +inline Arg::Arg(void* p) : arg_(p), parser_(parse_null) { } + +inline bool Arg::Parse(const char* str, int n) const { + return (*parser_)(str, n, arg_); +} + +// This part of the parser, appropriate only for ints, deals with bases +#define MAKE_INTEGER_PARSER(type, name) \ + inline Arg Hex(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _hex); } \ + inline Arg Octal(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _octal); } \ + inline Arg CRadix(type* ptr) { \ + return Arg(ptr, Arg::parse_ ## name ## _cradix); } + +MAKE_INTEGER_PARSER(short, short) /* */ +MAKE_INTEGER_PARSER(unsigned short, ushort) /* */ +MAKE_INTEGER_PARSER(int, int) /* Don't use semicolons */ +MAKE_INTEGER_PARSER(unsigned int, uint) /* after these statement */ +MAKE_INTEGER_PARSER(long, long) /* because they can cause */ +MAKE_INTEGER_PARSER(unsigned long, ulong) /* compiler warnings if */ +#if @pcre_have_long_long@ /* the checking level is */ +MAKE_INTEGER_PARSER(long long, longlong) /* turned up high enough. */ +#endif /* */ +#if @pcre_have_ulong_long@ /* */ +MAKE_INTEGER_PARSER(unsigned long long, ulonglong) /* */ +#endif + +#undef PCRE_IS_SET +#undef PCRE_SET_OR_CLEAR +#undef MAKE_INTEGER_PARSER + +} // namespace pcrecpp + + +#endif /* _PCRECPPARG_H */ diff --git a/pcre-7.4/pcredemo.c b/pcre-7.4/pcredemo.c new file mode 100644 index 0000000..4068e3e --- /dev/null +++ b/pcre-7.4/pcredemo.c @@ -0,0 +1,325 @@ +/************************************************* +* PCRE DEMONSTRATION PROGRAM * +*************************************************/ + +/* This is a demonstration program to illustrate the most straightforward ways +of calling the PCRE regular expression library from a C program. See the +pcresample documentation for a short discussion. + +Compile thuswise: + gcc -Wall pcredemo.c -I/usr/local/include -L/usr/local/lib \ + -R/usr/local/lib -lpcre + +Replace "/usr/local/include" and "/usr/local/lib" with wherever the include and +library files for PCRE are installed on your system. You don't need -I and -L +if PCRE is installed in the standard system libraries. Only some operating +systems (e.g. Solaris) use the -R option. +*/ + + +#include <stdio.h> +#include <string.h> +#include <pcre.h> + +#define OVECCOUNT 30 /* should be a multiple of 3 */ + + +int main(int argc, char **argv) +{ +pcre *re; +const char *error; +char *pattern; +char *subject; +unsigned char *name_table; +int erroffset; +int find_all; +int namecount; +int name_entry_size; +int ovector[OVECCOUNT]; +int subject_length; +int rc, i; + + +/************************************************************************** +* First, sort out the command line. There is only one possible option at * +* the moment, "-g" to request repeated matching to find all occurrences, * +* like Perl's /g option. We set the variable find_all to a non-zero value * +* if the -g option is present. Apart from that, there must be exactly two * +* arguments. * +**************************************************************************/ + +find_all = 0; +for (i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-g") == 0) find_all = 1; + else break; + } + +/* After the options, we require exactly two arguments, which are the pattern, +and the subject string. */ + +if (argc - i != 2) + { + printf("Two arguments required: a regex and a subject string\n"); + return 1; + } + +pattern = argv[i]; +subject = argv[i+1]; +subject_length = (int)strlen(subject); + + +/************************************************************************* +* Now we are going to compile the regular expression pattern, and handle * +* and errors that are detected. * +*************************************************************************/ + +re = pcre_compile( + pattern, /* the pattern */ + 0, /* default options */ + &error, /* for error message */ + &erroffset, /* for error offset */ + NULL); /* use default character tables */ + +/* Compilation failed: print the error message and exit */ + +if (re == NULL) + { + printf("PCRE compilation failed at offset %d: %s\n", erroffset, error); + return 1; + } + + +/************************************************************************* +* If the compilation succeeded, we call PCRE again, in order to do a * +* pattern match against the subject string. This does just ONE match. If * +* further matching is needed, it will be done below. * +*************************************************************************/ + +rc = pcre_exec( + re, /* the compiled pattern */ + NULL, /* no extra data - we didn't study the pattern */ + subject, /* the subject string */ + subject_length, /* the length of the subject */ + 0, /* start at offset 0 in the subject */ + 0, /* default options */ + ovector, /* output vector for substring information */ + OVECCOUNT); /* number of elements in the output vector */ + +/* Matching failed: handle error cases */ + +if (rc < 0) + { + switch(rc) + { + case PCRE_ERROR_NOMATCH: printf("No match\n"); break; + /* + Handle other special cases if you like + */ + default: printf("Matching error %d\n", rc); break; + } + pcre_free(re); /* Release memory used for the compiled pattern */ + return 1; + } + +/* Match succeded */ + +printf("\nMatch succeeded at offset %d\n", ovector[0]); + + +/************************************************************************* +* We have found the first match within the subject string. If the output * +* vector wasn't big enough, set its size to the maximum. Then output any * +* substrings that were captured. * +*************************************************************************/ + +/* The output vector wasn't big enough */ + +if (rc == 0) + { + rc = OVECCOUNT/3; + printf("ovector only has room for %d captured substrings\n", rc - 1); + } + +/* Show substrings stored in the output vector by number. Obviously, in a real +application you might want to do things other than print them. */ + +for (i = 0; i < rc; i++) + { + char *substring_start = subject + ovector[2*i]; + int substring_length = ovector[2*i+1] - ovector[2*i]; + printf("%2d: %.*s\n", i, substring_length, substring_start); + } + + +/************************************************************************** +* That concludes the basic part of this demonstration program. We have * +* compiled a pattern, and performed a single match. The code that follows * +* first shows how to access named substrings, and then how to code for * +* repeated matches on the same subject. * +**************************************************************************/ + +/* See if there are any named substrings, and if so, show them by name. First +we have to extract the count of named parentheses from the pattern. */ + +(void)pcre_fullinfo( + re, /* the compiled pattern */ + NULL, /* no extra data - we didn't study the pattern */ + PCRE_INFO_NAMECOUNT, /* number of named substrings */ + &namecount); /* where to put the answer */ + +if (namecount <= 0) printf("No named substrings\n"); else + { + unsigned char *tabptr; + printf("Named substrings\n"); + + /* Before we can access the substrings, we must extract the table for + translating names to numbers, and the size of each entry in the table. */ + + (void)pcre_fullinfo( + re, /* the compiled pattern */ + NULL, /* no extra data - we didn't study the pattern */ + PCRE_INFO_NAMETABLE, /* address of the table */ + &name_table); /* where to put the answer */ + + (void)pcre_fullinfo( + re, /* the compiled pattern */ + NULL, /* no extra data - we didn't study the pattern */ + PCRE_INFO_NAMEENTRYSIZE, /* size of each entry in the table */ + &name_entry_size); /* where to put the answer */ + + /* Now we can scan the table and, for each entry, print the number, the name, + and the substring itself. */ + + tabptr = name_table; + for (i = 0; i < namecount; i++) + { + int n = (tabptr[0] << 8) | tabptr[1]; + printf("(%d) %*s: %.*s\n", n, name_entry_size - 3, tabptr + 2, + ovector[2*n+1] - ovector[2*n], subject + ovector[2*n]); + tabptr += name_entry_size; + } + } + + +/************************************************************************* +* If the "-g" option was given on the command line, we want to continue * +* to search for additional matches in the subject string, in a similar * +* way to the /g option in Perl. This turns out to be trickier than you * +* might think because of the possibility of matching an empty string. * +* What happens is as follows: * +* * +* If the previous match was NOT for an empty string, we can just start * +* the next match at the end of the previous one. * +* * +* If the previous match WAS for an empty string, we can't do that, as it * +* would lead to an infinite loop. Instead, a special call of pcre_exec() * +* is made with the PCRE_NOTEMPTY and PCRE_ANCHORED flags set. The first * +* of these tells PCRE that an empty string is not a valid match; other * +* possibilities must be tried. The second flag restricts PCRE to one * +* match attempt at the initial string position. If this match succeeds, * +* an alternative to the empty string match has been found, and we can * +* proceed round the loop. * +*************************************************************************/ + +if (!find_all) + { + pcre_free(re); /* Release the memory used for the compiled pattern */ + return 0; /* Finish unless -g was given */ + } + +/* Loop for second and subsequent matches */ + +for (;;) + { + int options = 0; /* Normally no options */ + int start_offset = ovector[1]; /* Start at end of previous match */ + + /* If the previous match was for an empty string, we are finished if we are + at the end of the subject. Otherwise, arrange to run another match at the + same point to see if a non-empty match can be found. */ + + if (ovector[0] == ovector[1]) + { + if (ovector[0] == subject_length) break; + options = PCRE_NOTEMPTY | PCRE_ANCHORED; + } + + /* Run the next matching operation */ + + rc = pcre_exec( + re, /* the compiled pattern */ + NULL, /* no extra data - we didn't study the pattern */ + subject, /* the subject string */ + subject_length, /* the length of the subject */ + start_offset, /* starting offset in the subject */ + options, /* options */ + ovector, /* output vector for substring information */ + OVECCOUNT); /* number of elements in the output vector */ + + /* This time, a result of NOMATCH isn't an error. If the value in "options" + is zero, it just means we have found all possible matches, so the loop ends. + Otherwise, it means we have failed to find a non-empty-string match at a + point where there was a previous empty-string match. In this case, we do what + Perl does: advance the matching position by one, and continue. We do this by + setting the "end of previous match" offset, because that is picked up at the + top of the loop as the point at which to start again. */ + + if (rc == PCRE_ERROR_NOMATCH) + { + if (options == 0) break; + ovector[1] = start_offset + 1; + continue; /* Go round the loop again */ + } + + /* Other matching errors are not recoverable. */ + + if (rc < 0) + { + printf("Matching error %d\n", rc); + pcre_free(re); /* Release memory used for the compiled pattern */ + return 1; + } + + /* Match succeded */ + + printf("\nMatch succeeded again at offset %d\n", ovector[0]); + + /* The match succeeded, but the output vector wasn't big enough. */ + + if (rc == 0) + { + rc = OVECCOUNT/3; + printf("ovector only has room for %d captured substrings\n", rc - 1); + } + + /* As before, show substrings stored in the output vector by number, and then + also any named substrings. */ + + for (i = 0; i < rc; i++) + { + char *substring_start = subject + ovector[2*i]; + int substring_length = ovector[2*i+1] - ovector[2*i]; + printf("%2d: %.*s\n", i, substring_length, substring_start); + } + + if (namecount <= 0) printf("No named substrings\n"); else + { + unsigned char *tabptr = name_table; + printf("Named substrings\n"); + for (i = 0; i < namecount; i++) + { + int n = (tabptr[0] << 8) | tabptr[1]; + printf("(%d) %*s: %.*s\n", n, name_entry_size - 3, tabptr + 2, + ovector[2*n+1] - ovector[2*n], subject + ovector[2*n]); + tabptr += name_entry_size; + } + } + } /* End of loop to find second and subsequent matches */ + +printf("\n"); +pcre_free(re); /* Release memory used for the compiled pattern */ +return 0; +} + +/* End of pcredemo.c */ diff --git a/pcre-7.4/pcregrep.c b/pcre-7.4/pcregrep.c new file mode 100644 index 0000000..b44574e --- /dev/null +++ b/pcre-7.4/pcregrep.c @@ -0,0 +1,2106 @@ +/************************************************* +* pcregrep program * +*************************************************/ + +/* This is a grep program that uses the PCRE regular expression library to do +its pattern matching. On a Unix or Win32 system it can recurse into +directories. + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ctype.h> +#include <locale.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "pcre.h" + +#define FALSE 0 +#define TRUE 1 + +typedef int BOOL; + +#define MAX_PATTERN_COUNT 100 + +#if BUFSIZ > 8192 +#define MBUFTHIRD BUFSIZ +#else +#define MBUFTHIRD 8192 +#endif + +/* Values for the "filenames" variable, which specifies options for file name +output. The order is important; it is assumed that a file name is wanted for +all values greater than FN_DEFAULT. */ + +enum { FN_NONE, FN_DEFAULT, FN_ONLY, FN_NOMATCH_ONLY, FN_FORCE }; + +/* Actions for the -d and -D options */ + +enum { dee_READ, dee_SKIP, dee_RECURSE }; +enum { DEE_READ, DEE_SKIP }; + +/* Actions for special processing options (flag bits) */ + +#define PO_WORD_MATCH 0x0001 +#define PO_LINE_MATCH 0x0002 +#define PO_FIXED_STRINGS 0x0004 + +/* Line ending types */ + +enum { EL_LF, EL_CR, EL_CRLF, EL_ANY, EL_ANYCRLF }; + + + +/************************************************* +* Global variables * +*************************************************/ + +/* Jeffrey Friedl has some debugging requirements that are not part of the +regular code. */ + +#ifdef JFRIEDL_DEBUG +static int S_arg = -1; +static unsigned int jfriedl_XR = 0; /* repeat regex attempt this many times */ +static unsigned int jfriedl_XT = 0; /* replicate text this many times */ +static const char *jfriedl_prefix = ""; +static const char *jfriedl_postfix = ""; +#endif + +static int endlinetype; + +static char *colour_string = (char *)"1;31"; +static char *colour_option = NULL; +static char *dee_option = NULL; +static char *DEE_option = NULL; +static char *newline = NULL; +static char *pattern_filename = NULL; +static char *stdin_name = (char *)"(standard input)"; +static char *locale = NULL; + +static const unsigned char *pcretables = NULL; + +static int pattern_count = 0; +static pcre **pattern_list = NULL; +static pcre_extra **hints_list = NULL; + +static char *include_pattern = NULL; +static char *exclude_pattern = NULL; + +static pcre *include_compiled = NULL; +static pcre *exclude_compiled = NULL; + +static int after_context = 0; +static int before_context = 0; +static int both_context = 0; +static int dee_action = dee_READ; +static int DEE_action = DEE_READ; +static int error_count = 0; +static int filenames = FN_DEFAULT; +static int process_options = 0; + +static BOOL count_only = FALSE; +static BOOL do_colour = FALSE; +static BOOL hyphenpending = FALSE; +static BOOL invert = FALSE; +static BOOL multiline = FALSE; +static BOOL number = FALSE; +static BOOL only_matching = FALSE; +static BOOL quiet = FALSE; +static BOOL silent = FALSE; +static BOOL utf8 = FALSE; + +/* Structure for options and list of them */ + +enum { OP_NODATA, OP_STRING, OP_OP_STRING, OP_NUMBER, OP_OP_NUMBER, + OP_PATLIST }; + +typedef struct option_item { + int type; + int one_char; + void *dataptr; + const char *long_name; + const char *help_text; +} option_item; + +/* Options without a single-letter equivalent get a negative value. This can be +used to identify them. */ + +#define N_COLOUR (-1) +#define N_EXCLUDE (-2) +#define N_HELP (-3) +#define N_INCLUDE (-4) +#define N_LABEL (-5) +#define N_LOCALE (-6) +#define N_NULL (-7) + +static option_item optionlist[] = { + { OP_NODATA, N_NULL, NULL, "", " terminate options" }, + { OP_NODATA, N_HELP, NULL, "help", "display this help and exit" }, + { OP_NUMBER, 'A', &after_context, "after-context=number", "set number of following context lines" }, + { OP_NUMBER, 'B', &before_context, "before-context=number", "set number of prior context lines" }, + { OP_OP_STRING, N_COLOUR, &colour_option, "color=option", "matched text color option" }, + { OP_NUMBER, 'C', &both_context, "context=number", "set number of context lines, before & after" }, + { OP_NODATA, 'c', NULL, "count", "print only a count of matching lines per FILE" }, + { OP_OP_STRING, N_COLOUR, &colour_option, "colour=option", "matched text colour option" }, + { OP_STRING, 'D', &DEE_option, "devices=action","how to handle devices, FIFOs, and sockets" }, + { OP_STRING, 'd', &dee_option, "directories=action", "how to handle directories" }, + { OP_PATLIST, 'e', NULL, "regex(p)", "specify pattern (may be used more than once)" }, + { OP_NODATA, 'F', NULL, "fixed_strings", "patterns are sets of newline-separated strings" }, + { OP_STRING, 'f', &pattern_filename, "file=path", "read patterns from file" }, + { OP_NODATA, 'H', NULL, "with-filename", "force the prefixing filename on output" }, + { OP_NODATA, 'h', NULL, "no-filename", "suppress the prefixing filename on output" }, + { OP_NODATA, 'i', NULL, "ignore-case", "ignore case distinctions" }, + { OP_NODATA, 'l', NULL, "files-with-matches", "print only FILE names containing matches" }, + { OP_NODATA, 'L', NULL, "files-without-match","print only FILE names not containing matches" }, + { OP_STRING, N_LABEL, &stdin_name, "label=name", "set name for standard input" }, + { OP_STRING, N_LOCALE, &locale, "locale=locale", "use the named locale" }, + { OP_NODATA, 'M', NULL, "multiline", "run in multiline mode" }, + { OP_STRING, 'N', &newline, "newline=type", "specify newline type (CR, LF, CRLF, ANYCRLF or ANY)" }, + { OP_NODATA, 'n', NULL, "line-number", "print line number with output lines" }, + { OP_NODATA, 'o', NULL, "only-matching", "show only the part of the line that matched" }, + { OP_NODATA, 'q', NULL, "quiet", "suppress output, just set return code" }, + { OP_NODATA, 'r', NULL, "recursive", "recursively scan sub-directories" }, + { OP_STRING, N_EXCLUDE,&exclude_pattern, "exclude=pattern","exclude matching files when recursing" }, + { OP_STRING, N_INCLUDE,&include_pattern, "include=pattern","include matching files when recursing" }, +#ifdef JFRIEDL_DEBUG + { OP_OP_NUMBER, 'S', &S_arg, "jeffS", "replace matched (sub)string with X" }, +#endif + { OP_NODATA, 's', NULL, "no-messages", "suppress error messages" }, + { OP_NODATA, 'u', NULL, "utf-8", "use UTF-8 mode" }, + { OP_NODATA, 'V', NULL, "version", "print version information and exit" }, + { OP_NODATA, 'v', NULL, "invert-match", "select non-matching lines" }, + { OP_NODATA, 'w', NULL, "word-regex(p)", "force patterns to match only as words" }, + { OP_NODATA, 'x', NULL, "line-regex(p)", "force patterns to match only whole lines" }, + { OP_NODATA, 0, NULL, NULL, NULL } +}; + +/* Tables for prefixing and suffixing patterns, according to the -w, -x, and -F +options. These set the 1, 2, and 4 bits in process_options, respectively. Note +that the combination of -w and -x has the same effect as -x on its own, so we +can treat them as the same. */ + +static const char *prefix[] = { + "", "\\b", "^(?:", "^(?:", "\\Q", "\\b\\Q", "^(?:\\Q", "^(?:\\Q" }; + +static const char *suffix[] = { + "", "\\b", ")$", ")$", "\\E", "\\E\\b", "\\E)$", "\\E)$" }; + +/* UTF-8 tables - used only when the newline setting is "any". */ + +const int utf8_table3[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01}; + +const char utf8_table4[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; + + + +/************************************************* +* OS-specific functions * +*************************************************/ + +/* These functions are defined so that they can be made system specific, +although at present the only ones are for Unix, Win32, and for "no support". */ + + +/************* Directory scanning in Unix ***********/ + +#if defined HAVE_SYS_STAT_H && defined HAVE_DIRENT_H && defined HAVE_SYS_TYPES_H +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +typedef DIR directory_type; + +static int +isdirectory(char *filename) +{ +struct stat statbuf; +if (stat(filename, &statbuf) < 0) + return 0; /* In the expectation that opening as a file will fail */ +return ((statbuf.st_mode & S_IFMT) == S_IFDIR)? '/' : 0; +} + +static directory_type * +opendirectory(char *filename) +{ +return opendir(filename); +} + +static char * +readdirectory(directory_type *dir) +{ +for (;;) + { + struct dirent *dent = readdir(dir); + if (dent == NULL) return NULL; + if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) + return dent->d_name; + } +/* Control never reaches here */ +} + +static void +closedirectory(directory_type *dir) +{ +closedir(dir); +} + + +/************* Test for regular file in Unix **********/ + +static int +isregfile(char *filename) +{ +struct stat statbuf; +if (stat(filename, &statbuf) < 0) + return 1; /* In the expectation that opening as a file will fail */ +return (statbuf.st_mode & S_IFMT) == S_IFREG; +} + + +/************* Test stdout for being a terminal in Unix **********/ + +static BOOL +is_stdout_tty(void) +{ +return isatty(fileno(stdout)); +} + + +/************* Directory scanning in Win32 ***********/ + +/* I (Philip Hazel) have no means of testing this code. It was contributed by +Lionel Fourquaux. David Burgess added a patch to define INVALID_FILE_ATTRIBUTES +when it did not exist. */ + + +#elif HAVE_WINDOWS_H + +#ifndef STRICT +# define STRICT +#endif +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF +#endif + +#include <windows.h> + +typedef struct directory_type +{ +HANDLE handle; +BOOL first; +WIN32_FIND_DATA data; +} directory_type; + +int +isdirectory(char *filename) +{ +DWORD attr = GetFileAttributes(filename); +if (attr == INVALID_FILE_ATTRIBUTES) + return 0; +return ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) ? '/' : 0; +} + +directory_type * +opendirectory(char *filename) +{ +size_t len; +char *pattern; +directory_type *dir; +DWORD err; +len = strlen(filename); +pattern = (char *) malloc(len + 3); +dir = (directory_type *) malloc(sizeof(*dir)); +if ((pattern == NULL) || (dir == NULL)) + { + fprintf(stderr, "pcregrep: malloc failed\n"); + exit(2); + } +memcpy(pattern, filename, len); +memcpy(&(pattern[len]), "\\*", 3); +dir->handle = FindFirstFile(pattern, &(dir->data)); +if (dir->handle != INVALID_HANDLE_VALUE) + { + free(pattern); + dir->first = TRUE; + return dir; + } +err = GetLastError(); +free(pattern); +free(dir); +errno = (err == ERROR_ACCESS_DENIED) ? EACCES : ENOENT; +return NULL; +} + +char * +readdirectory(directory_type *dir) +{ +for (;;) + { + if (!dir->first) + { + if (!FindNextFile(dir->handle, &(dir->data))) + return NULL; + } + else + { + dir->first = FALSE; + } + if (strcmp(dir->data.cFileName, ".") != 0 && strcmp(dir->data.cFileName, "..") != 0) + return dir->data.cFileName; + } +#ifndef _MSC_VER +return NULL; /* Keep compiler happy; never executed */ +#endif +} + +void +closedirectory(directory_type *dir) +{ +FindClose(dir->handle); +free(dir); +} + + +/************* Test for regular file in Win32 **********/ + +/* I don't know how to do this, or if it can be done; assume all paths are +regular if they are not directories. */ + +int isregfile(char *filename) +{ +return !isdirectory(filename) +} + + +/************* Test stdout for being a terminal in Win32 **********/ + +/* I don't know how to do this; assume never */ + +static BOOL +is_stdout_tty(void) +{ +FALSE; +} + + +/************* Directory scanning when we can't do it ***********/ + +/* The type is void, and apart from isdirectory(), the functions do nothing. */ + +#else + +typedef void directory_type; + +int isdirectory(char *filename) { return 0; } +directory_type * opendirectory(char *filename) { return (directory_type*)0;} +char *readdirectory(directory_type *dir) { return (char*)0;} +void closedirectory(directory_type *dir) {} + + +/************* Test for regular when we can't do it **********/ + +/* Assume all files are regular. */ + +int isregfile(char *filename) { return 1; } + + +/************* Test stdout for being a terminal when we can't do it **********/ + +static BOOL +is_stdout_tty(void) +{ +return FALSE; +} + + +#endif + + + +#ifndef HAVE_STRERROR +/************************************************* +* Provide strerror() for non-ANSI libraries * +*************************************************/ + +/* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror() +in their libraries, but can provide the same facility by this simple +alternative function. */ + +extern int sys_nerr; +extern char *sys_errlist[]; + +char * +strerror(int n) +{ +if (n < 0 || n >= sys_nerr) return "unknown error number"; +return sys_errlist[n]; +} +#endif /* HAVE_STRERROR */ + + + +/************************************************* +* Find end of line * +*************************************************/ + +/* The length of the endline sequence that is found is set via lenptr. This may +be zero at the very end of the file if there is no line-ending sequence there. + +Arguments: + p current position in line + endptr end of available data + lenptr where to put the length of the eol sequence + +Returns: pointer to the last byte of the line +*/ + +static char * +end_of_line(char *p, char *endptr, int *lenptr) +{ +switch(endlinetype) + { + default: /* Just in case */ + case EL_LF: + while (p < endptr && *p != '\n') p++; + if (p < endptr) + { + *lenptr = 1; + return p + 1; + } + *lenptr = 0; + return endptr; + + case EL_CR: + while (p < endptr && *p != '\r') p++; + if (p < endptr) + { + *lenptr = 1; + return p + 1; + } + *lenptr = 0; + return endptr; + + case EL_CRLF: + for (;;) + { + while (p < endptr && *p != '\r') p++; + if (++p >= endptr) + { + *lenptr = 0; + return endptr; + } + if (*p == '\n') + { + *lenptr = 2; + return p + 1; + } + } + break; + + case EL_ANYCRLF: + while (p < endptr) + { + int extra = 0; + register int c = *((unsigned char *)p); + + if (utf8 && c >= 0xc0) + { + int gcii, gcss; + extra = utf8_table4[c & 0x3f]; /* Number of additional bytes */ + gcss = 6*extra; + c = (c & utf8_table3[extra]) << gcss; + for (gcii = 1; gcii <= extra; gcii++) + { + gcss -= 6; + c |= (p[gcii] & 0x3f) << gcss; + } + } + + p += 1 + extra; + + switch (c) + { + case 0x0a: /* LF */ + *lenptr = 1; + return p; + + case 0x0d: /* CR */ + if (p < endptr && *p == 0x0a) + { + *lenptr = 2; + p++; + } + else *lenptr = 1; + return p; + + default: + break; + } + } /* End of loop for ANYCRLF case */ + + *lenptr = 0; /* Must have hit the end */ + return endptr; + + case EL_ANY: + while (p < endptr) + { + int extra = 0; + register int c = *((unsigned char *)p); + + if (utf8 && c >= 0xc0) + { + int gcii, gcss; + extra = utf8_table4[c & 0x3f]; /* Number of additional bytes */ + gcss = 6*extra; + c = (c & utf8_table3[extra]) << gcss; + for (gcii = 1; gcii <= extra; gcii++) + { + gcss -= 6; + c |= (p[gcii] & 0x3f) << gcss; + } + } + + p += 1 + extra; + + switch (c) + { + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + *lenptr = 1; + return p; + + case 0x0d: /* CR */ + if (p < endptr && *p == 0x0a) + { + *lenptr = 2; + p++; + } + else *lenptr = 1; + return p; + + case 0x85: /* NEL */ + *lenptr = utf8? 2 : 1; + return p; + + case 0x2028: /* LS */ + case 0x2029: /* PS */ + *lenptr = 3; + return p; + + default: + break; + } + } /* End of loop for ANY case */ + + *lenptr = 0; /* Must have hit the end */ + return endptr; + } /* End of overall switch */ +} + + + +/************************************************* +* Find start of previous line * +*************************************************/ + +/* This is called when looking back for before lines to print. + +Arguments: + p start of the subsequent line + startptr start of available data + +Returns: pointer to the start of the previous line +*/ + +static char * +previous_line(char *p, char *startptr) +{ +switch(endlinetype) + { + default: /* Just in case */ + case EL_LF: + p--; + while (p > startptr && p[-1] != '\n') p--; + return p; + + case EL_CR: + p--; + while (p > startptr && p[-1] != '\n') p--; + return p; + + case EL_CRLF: + for (;;) + { + p -= 2; + while (p > startptr && p[-1] != '\n') p--; + if (p <= startptr + 1 || p[-2] == '\r') return p; + } + return p; /* But control should never get here */ + + case EL_ANY: + case EL_ANYCRLF: + if (*(--p) == '\n' && p > startptr && p[-1] == '\r') p--; + if (utf8) while ((*p & 0xc0) == 0x80) p--; + + while (p > startptr) + { + register int c; + char *pp = p - 1; + + if (utf8) + { + int extra = 0; + while ((*pp & 0xc0) == 0x80) pp--; + c = *((unsigned char *)pp); + if (c >= 0xc0) + { + int gcii, gcss; + extra = utf8_table4[c & 0x3f]; /* Number of additional bytes */ + gcss = 6*extra; + c = (c & utf8_table3[extra]) << gcss; + for (gcii = 1; gcii <= extra; gcii++) + { + gcss -= 6; + c |= (pp[gcii] & 0x3f) << gcss; + } + } + } + else c = *((unsigned char *)pp); + + if (endlinetype == EL_ANYCRLF) switch (c) + { + case 0x0a: /* LF */ + case 0x0d: /* CR */ + return p; + + default: + break; + } + + else switch (c) + { + case 0x0a: /* LF */ + case 0x0b: /* VT */ + case 0x0c: /* FF */ + case 0x0d: /* CR */ + case 0x85: /* NEL */ + case 0x2028: /* LS */ + case 0x2029: /* PS */ + return p; + + default: + break; + } + + p = pp; /* Back one character */ + } /* End of loop for ANY case */ + + return startptr; /* Hit start of data */ + } /* End of overall switch */ +} + + + + + +/************************************************* +* Print the previous "after" lines * +*************************************************/ + +/* This is called if we are about to lose said lines because of buffer filling, +and at the end of the file. The data in the line is written using fwrite() so +that a binary zero does not terminate it. + +Arguments: + lastmatchnumber the number of the last matching line, plus one + lastmatchrestart where we restarted after the last match + endptr end of available data + printname filename for printing + +Returns: nothing +*/ + +static void do_after_lines(int lastmatchnumber, char *lastmatchrestart, + char *endptr, char *printname) +{ +if (after_context > 0 && lastmatchnumber > 0) + { + int count = 0; + while (lastmatchrestart < endptr && count++ < after_context) + { + int ellength; + char *pp = lastmatchrestart; + if (printname != NULL) fprintf(stdout, "%s-", printname); + if (number) fprintf(stdout, "%d-", lastmatchnumber++); + pp = end_of_line(pp, endptr, &ellength); + fwrite(lastmatchrestart, 1, pp - lastmatchrestart, stdout); + lastmatchrestart = pp; + } + hyphenpending = TRUE; + } +} + + + +/************************************************* +* Grep an individual file * +*************************************************/ + +/* This is called from grep_or_recurse() below. It uses a buffer that is three +times the value of MBUFTHIRD. The matching point is never allowed to stray into +the top third of the buffer, thus keeping more of the file available for +context printing or for multiline scanning. For large files, the pointer will +be in the middle third most of the time, so the bottom third is available for +"before" context printing. + +Arguments: + in the fopened FILE stream + printname the file name if it is to be printed for each match + or NULL if the file name is not to be printed + it cannot be NULL if filenames[_nomatch]_only is set + +Returns: 0 if there was at least one match + 1 otherwise (no matches) +*/ + +static int +pcregrep(FILE *in, char *printname) +{ +int rc = 1; +int linenumber = 1; +int lastmatchnumber = 0; +int count = 0; +int offsets[99]; +char *lastmatchrestart = NULL; +char buffer[3*MBUFTHIRD]; +char *ptr = buffer; +char *endptr; +size_t bufflength; +BOOL endhyphenpending = FALSE; + +/* Do the first read into the start of the buffer and set up the pointer to +end of what we have. */ + +bufflength = fread(buffer, 1, 3*MBUFTHIRD, in); +endptr = buffer + bufflength; + +/* Loop while the current pointer is not at the end of the file. For large +files, endptr will be at the end of the buffer when we are in the middle of the +file, but ptr will never get there, because as soon as it gets over 2/3 of the +way, the buffer is shifted left and re-filled. */ + +while (ptr < endptr) + { + int i, endlinelength; + int mrc = 0; + BOOL match = FALSE; + char *t = ptr; + size_t length, linelength; + + /* At this point, ptr is at the start of a line. We need to find the length + of the subject string to pass to pcre_exec(). In multiline mode, it is the + length remainder of the data in the buffer. Otherwise, it is the length of + the next line. After matching, we always advance by the length of the next + line. In multiline mode the PCRE_FIRSTLINE option is used for compiling, so + that any match is constrained to be in the first line. */ + + t = end_of_line(t, endptr, &endlinelength); + linelength = t - ptr - endlinelength; + length = multiline? (size_t)(endptr - ptr) : linelength; + + /* Extra processing for Jeffrey Friedl's debugging. */ + +#ifdef JFRIEDL_DEBUG + if (jfriedl_XT || jfriedl_XR) + { + #include <sys/time.h> + #include <time.h> + struct timeval start_time, end_time; + struct timezone dummy; + + if (jfriedl_XT) + { + unsigned long newlen = length * jfriedl_XT + strlen(jfriedl_prefix) + strlen(jfriedl_postfix); + const char *orig = ptr; + ptr = malloc(newlen + 1); + if (!ptr) { + printf("out of memory"); + exit(2); + } + endptr = ptr; + strcpy(endptr, jfriedl_prefix); endptr += strlen(jfriedl_prefix); + for (i = 0; i < jfriedl_XT; i++) { + strncpy(endptr, orig, length); + endptr += length; + } + strcpy(endptr, jfriedl_postfix); endptr += strlen(jfriedl_postfix); + length = newlen; + } + + if (gettimeofday(&start_time, &dummy) != 0) + perror("bad gettimeofday"); + + + for (i = 0; i < jfriedl_XR; i++) + match = (pcre_exec(pattern_list[0], hints_list[0], ptr, length, 0, 0, offsets, 99) >= 0); + + if (gettimeofday(&end_time, &dummy) != 0) + perror("bad gettimeofday"); + + double delta = ((end_time.tv_sec + (end_time.tv_usec / 1000000.0)) + - + (start_time.tv_sec + (start_time.tv_usec / 1000000.0))); + + printf("%s TIMER[%.4f]\n", match ? "MATCH" : "FAIL", delta); + return 0; + } +#endif + + + /* Run through all the patterns until one matches. Note that we don't include + the final newline in the subject string. */ + + for (i = 0; i < pattern_count; i++) + { + mrc = pcre_exec(pattern_list[i], hints_list[i], ptr, length, 0, 0, + offsets, 99); + if (mrc >= 0) { match = TRUE; break; } + if (mrc != PCRE_ERROR_NOMATCH) + { + fprintf(stderr, "pcregrep: pcre_exec() error %d while matching ", mrc); + if (pattern_count > 1) fprintf(stderr, "pattern number %d to ", i+1); + fprintf(stderr, "this line:\n"); + fwrite(ptr, 1, linelength, stderr); /* In case binary zero included */ + fprintf(stderr, "\n"); + if (error_count == 0 && + (mrc == PCRE_ERROR_MATCHLIMIT || mrc == PCRE_ERROR_RECURSIONLIMIT)) + { + fprintf(stderr, "pcregrep: error %d means that a resource limit " + "was exceeded\n", mrc); + fprintf(stderr, "pcregrep: check your regex for nested unlimited loops\n"); + } + if (error_count++ > 20) + { + fprintf(stderr, "pcregrep: too many errors - abandoned\n"); + exit(2); + } + match = invert; /* No more matching; don't show the line again */ + break; + } + } + + /* If it's a match or a not-match (as required), do what's wanted. */ + + if (match != invert) + { + BOOL hyphenprinted = FALSE; + + /* We've failed if we want a file that doesn't have any matches. */ + + if (filenames == FN_NOMATCH_ONLY) return 1; + + /* Just count if just counting is wanted. */ + + if (count_only) count++; + + /* If all we want is a file name, there is no need to scan any more lines + in the file. */ + + else if (filenames == FN_ONLY) + { + fprintf(stdout, "%s\n", printname); + return 0; + } + + /* Likewise, if all we want is a yes/no answer. */ + + else if (quiet) return 0; + + /* The --only-matching option prints just the substring that matched, and + does not pring any context. */ + + else if (only_matching) + { + if (printname != NULL) fprintf(stdout, "%s:", printname); + if (number) fprintf(stdout, "%d:", linenumber); + fwrite(ptr + offsets[0], 1, offsets[1] - offsets[0], stdout); + fprintf(stdout, "\n"); + } + + /* This is the default case when none of the above options is set. We print + the matching lines(s), possibly preceded and/or followed by other lines of + context. */ + + else + { + /* See if there is a requirement to print some "after" lines from a + previous match. We never print any overlaps. */ + + if (after_context > 0 && lastmatchnumber > 0) + { + int ellength; + int linecount = 0; + char *p = lastmatchrestart; + + while (p < ptr && linecount < after_context) + { + p = end_of_line(p, ptr, &ellength); + linecount++; + } + + /* It is important to advance lastmatchrestart during this printing so + that it interacts correctly with any "before" printing below. Print + each line's data using fwrite() in case there are binary zeroes. */ + + while (lastmatchrestart < p) + { + char *pp = lastmatchrestart; + if (printname != NULL) fprintf(stdout, "%s-", printname); + if (number) fprintf(stdout, "%d-", lastmatchnumber++); + pp = end_of_line(pp, endptr, &ellength); + fwrite(lastmatchrestart, 1, pp - lastmatchrestart, stdout); + lastmatchrestart = pp; + } + if (lastmatchrestart != ptr) hyphenpending = TRUE; + } + + /* If there were non-contiguous lines printed above, insert hyphens. */ + + if (hyphenpending) + { + fprintf(stdout, "--\n"); + hyphenpending = FALSE; + hyphenprinted = TRUE; + } + + /* See if there is a requirement to print some "before" lines for this + match. Again, don't print overlaps. */ + + if (before_context > 0) + { + int linecount = 0; + char *p = ptr; + + while (p > buffer && (lastmatchnumber == 0 || p > lastmatchrestart) && + linecount < before_context) + { + linecount++; + p = previous_line(p, buffer); + } + + if (lastmatchnumber > 0 && p > lastmatchrestart && !hyphenprinted) + fprintf(stdout, "--\n"); + + while (p < ptr) + { + int ellength; + char *pp = p; + if (printname != NULL) fprintf(stdout, "%s-", printname); + if (number) fprintf(stdout, "%d-", linenumber - linecount--); + pp = end_of_line(pp, endptr, &ellength); + fwrite(p, 1, pp - p, stdout); + p = pp; + } + } + + /* Now print the matching line(s); ensure we set hyphenpending at the end + of the file if any context lines are being output. */ + + if (after_context > 0 || before_context > 0) + endhyphenpending = TRUE; + + if (printname != NULL) fprintf(stdout, "%s:", printname); + if (number) fprintf(stdout, "%d:", linenumber); + + /* In multiline mode, we want to print to the end of the line in which + the end of the matched string is found, so we adjust linelength and the + line number appropriately, but only when there actually was a match + (invert not set). Because the PCRE_FIRSTLINE option is set, the start of + the match will always be before the first newline sequence. */ + + if (multiline) + { + int ellength; + char *endmatch = ptr; + if (!invert) + { + endmatch += offsets[1]; + t = ptr; + while (t < endmatch) + { + t = end_of_line(t, endptr, &ellength); + if (t <= endmatch) linenumber++; else break; + } + } + endmatch = end_of_line(endmatch, endptr, &ellength); + linelength = endmatch - ptr - ellength; + } + + /*** NOTE: Use only fwrite() to output the data line, so that binary + zeroes are treated as just another data character. */ + + /* This extra option, for Jeffrey Friedl's debugging requirements, + replaces the matched string, or a specific captured string if it exists, + with X. When this happens, colouring is ignored. */ + +#ifdef JFRIEDL_DEBUG + if (S_arg >= 0 && S_arg < mrc) + { + int first = S_arg * 2; + int last = first + 1; + fwrite(ptr, 1, offsets[first], stdout); + fprintf(stdout, "X"); + fwrite(ptr + offsets[last], 1, linelength - offsets[last], stdout); + } + else +#endif + + /* We have to split the line(s) up if colouring. */ + + if (do_colour) + { + fwrite(ptr, 1, offsets[0], stdout); + fprintf(stdout, "%c[%sm", 0x1b, colour_string); + fwrite(ptr + offsets[0], 1, offsets[1] - offsets[0], stdout); + fprintf(stdout, "%c[00m", 0x1b); + fwrite(ptr + offsets[1], 1, (linelength + endlinelength) - offsets[1], + stdout); + } + else fwrite(ptr, 1, linelength + endlinelength, stdout); + } + + /* End of doing what has to be done for a match */ + + rc = 0; /* Had some success */ + + /* Remember where the last match happened for after_context. We remember + where we are about to restart, and that line's number. */ + + lastmatchrestart = ptr + linelength + endlinelength; + lastmatchnumber = linenumber + 1; + } + + /* For a match in multiline inverted mode (which of course did not cause + anything to be printed), we have to move on to the end of the match before + proceeding. */ + + if (multiline && invert && match) + { + int ellength; + char *endmatch = ptr + offsets[1]; + t = ptr; + while (t < endmatch) + { + t = end_of_line(t, endptr, &ellength); + if (t <= endmatch) linenumber++; else break; + } + endmatch = end_of_line(endmatch, endptr, &ellength); + linelength = endmatch - ptr - ellength; + } + + /* Advance to after the newline and increment the line number. */ + + ptr += linelength + endlinelength; + linenumber++; + + /* If we haven't yet reached the end of the file (the buffer is full), and + the current point is in the top 1/3 of the buffer, slide the buffer down by + 1/3 and refill it. Before we do this, if some unprinted "after" lines are + about to be lost, print them. */ + + if (bufflength >= sizeof(buffer) && ptr > buffer + 2*MBUFTHIRD) + { + if (after_context > 0 && + lastmatchnumber > 0 && + lastmatchrestart < buffer + MBUFTHIRD) + { + do_after_lines(lastmatchnumber, lastmatchrestart, endptr, printname); + lastmatchnumber = 0; + } + + /* Now do the shuffle */ + + memmove(buffer, buffer + MBUFTHIRD, 2*MBUFTHIRD); + ptr -= MBUFTHIRD; + bufflength = 2*MBUFTHIRD + fread(buffer + 2*MBUFTHIRD, 1, MBUFTHIRD, in); + endptr = buffer + bufflength; + + /* Adjust any last match point */ + + if (lastmatchnumber > 0) lastmatchrestart -= MBUFTHIRD; + } + } /* Loop through the whole file */ + +/* End of file; print final "after" lines if wanted; do_after_lines sets +hyphenpending if it prints something. */ + +if (!only_matching && !count_only) + { + do_after_lines(lastmatchnumber, lastmatchrestart, endptr, printname); + hyphenpending |= endhyphenpending; + } + +/* Print the file name if we are looking for those without matches and there +were none. If we found a match, we won't have got this far. */ + +if (filenames == FN_NOMATCH_ONLY) + { + fprintf(stdout, "%s\n", printname); + return 0; + } + +/* Print the match count if wanted */ + +if (count_only) + { + if (printname != NULL) fprintf(stdout, "%s:", printname); + fprintf(stdout, "%d\n", count); + } + +return rc; +} + + + +/************************************************* +* Grep a file or recurse into a directory * +*************************************************/ + +/* Given a path name, if it's a directory, scan all the files if we are +recursing; if it's a file, grep it. + +Arguments: + pathname the path to investigate + dir_recurse TRUE if recursing is wanted (-r or -drecurse) + only_one_at_top TRUE if the path is the only one at toplevel + +Returns: 0 if there was at least one match + 1 if there were no matches + 2 there was some kind of error + +However, file opening failures are suppressed if "silent" is set. +*/ + +static int +grep_or_recurse(char *pathname, BOOL dir_recurse, BOOL only_one_at_top) +{ +int rc = 1; +int sep; +FILE *in; + +/* If the file name is "-" we scan stdin */ + +if (strcmp(pathname, "-") == 0) + { + return pcregrep(stdin, + (filenames > FN_DEFAULT || (filenames == FN_DEFAULT && !only_one_at_top))? + stdin_name : NULL); + } + + +/* If the file is a directory, skip if skipping or if we are recursing, scan +each file within it, subject to any include or exclude patterns that were set. +The scanning code is localized so it can be made system-specific. */ + +if ((sep = isdirectory(pathname)) != 0) + { + if (dee_action == dee_SKIP) return 1; + if (dee_action == dee_RECURSE) + { + char buffer[1024]; + char *nextfile; + directory_type *dir = opendirectory(pathname); + + if (dir == NULL) + { + if (!silent) + fprintf(stderr, "pcregrep: Failed to open directory %s: %s\n", pathname, + strerror(errno)); + return 2; + } + + while ((nextfile = readdirectory(dir)) != NULL) + { + int frc, blen; + sprintf(buffer, "%.512s%c%.128s", pathname, sep, nextfile); + blen = strlen(buffer); + + if (exclude_compiled != NULL && + pcre_exec(exclude_compiled, NULL, buffer, blen, 0, 0, NULL, 0) >= 0) + continue; + + if (include_compiled != NULL && + pcre_exec(include_compiled, NULL, buffer, blen, 0, 0, NULL, 0) < 0) + continue; + + frc = grep_or_recurse(buffer, dir_recurse, FALSE); + if (frc > 1) rc = frc; + else if (frc == 0 && rc == 1) rc = 0; + } + + closedirectory(dir); + return rc; + } + } + +/* If the file is not a directory and not a regular file, skip it if that's +been requested. */ + +else if (!isregfile(pathname) && DEE_action == DEE_SKIP) return 1; + +/* Control reaches here if we have a regular file, or if we have a directory +and recursion or skipping was not requested, or if we have anything else and +skipping was not requested. The scan proceeds. If this is the first and only +argument at top level, we don't show the file name, unless we are only showing +the file name, or the filename was forced (-H). */ + +in = fopen(pathname, "r"); +if (in == NULL) + { + if (!silent) + fprintf(stderr, "pcregrep: Failed to open %s: %s\n", pathname, + strerror(errno)); + return 2; + } + +rc = pcregrep(in, (filenames > FN_DEFAULT || + (filenames == FN_DEFAULT && !only_one_at_top))? pathname : NULL); + +fclose(in); +return rc; +} + + + + +/************************************************* +* Usage function * +*************************************************/ + +static int +usage(int rc) +{ +option_item *op; +fprintf(stderr, "Usage: pcregrep [-"); +for (op = optionlist; op->one_char != 0; op++) + { + if (op->one_char > 0) fprintf(stderr, "%c", op->one_char); + } +fprintf(stderr, "] [long options] [pattern] [files]\n"); +fprintf(stderr, "Type `pcregrep --help' for more information.\n"); +return rc; +} + + + + +/************************************************* +* Help function * +*************************************************/ + +static void +help(void) +{ +option_item *op; + +printf("Usage: pcregrep [OPTION]... [PATTERN] [FILE1 FILE2 ...]\n"); +printf("Search for PATTERN in each FILE or standard input.\n"); +printf("PATTERN must be present if neither -e nor -f is used.\n"); +printf("\"-\" can be used as a file name to mean STDIN.\n\n"); +printf("Example: pcregrep -i 'hello.*world' menu.h main.c\n\n"); + +printf("Options:\n"); + +for (op = optionlist; op->one_char != 0; op++) + { + int n; + char s[4]; + if (op->one_char > 0) sprintf(s, "-%c,", op->one_char); else strcpy(s, " "); + printf(" %s --%s%n", s, op->long_name, &n); + n = 30 - n; + if (n < 1) n = 1; + printf("%.*s%s\n", n, " ", op->help_text); + } + +printf("\nWhen reading patterns from a file instead of using a command line option,\n"); +printf("trailing white space is removed and blank lines are ignored.\n"); +printf("There is a maximum of %d patterns.\n", MAX_PATTERN_COUNT); + +printf("\nWith no FILEs, read standard input. If fewer than two FILEs given, assume -h.\n"); +printf("Exit status is 0 if any matches, 1 if no matches, and 2 if trouble.\n"); +} + + + + +/************************************************* +* Handle a single-letter, no data option * +*************************************************/ + +static int +handle_option(int letter, int options) +{ +switch(letter) + { + case N_HELP: help(); exit(0); + case 'c': count_only = TRUE; break; + case 'F': process_options |= PO_FIXED_STRINGS; break; + case 'H': filenames = FN_FORCE; break; + case 'h': filenames = FN_NONE; break; + case 'i': options |= PCRE_CASELESS; break; + case 'l': filenames = FN_ONLY; break; + case 'L': filenames = FN_NOMATCH_ONLY; break; + case 'M': multiline = TRUE; options |= PCRE_MULTILINE|PCRE_FIRSTLINE; break; + case 'n': number = TRUE; break; + case 'o': only_matching = TRUE; break; + case 'q': quiet = TRUE; break; + case 'r': dee_action = dee_RECURSE; break; + case 's': silent = TRUE; break; + case 'u': options |= PCRE_UTF8; utf8 = TRUE; break; + case 'v': invert = TRUE; break; + case 'w': process_options |= PO_WORD_MATCH; break; + case 'x': process_options |= PO_LINE_MATCH; break; + + case 'V': + fprintf(stderr, "pcregrep version %s\n", pcre_version()); + exit(0); + break; + + default: + fprintf(stderr, "pcregrep: Unknown option -%c\n", letter); + exit(usage(2)); + } + +return options; +} + + + + +/************************************************* +* Construct printed ordinal * +*************************************************/ + +/* This turns a number into "1st", "3rd", etc. */ + +static char * +ordin(int n) +{ +static char buffer[8]; +char *p = buffer; +sprintf(p, "%d", n); +while (*p != 0) p++; +switch (n%10) + { + case 1: strcpy(p, "st"); break; + case 2: strcpy(p, "nd"); break; + case 3: strcpy(p, "rd"); break; + default: strcpy(p, "th"); break; + } +return buffer; +} + + + +/************************************************* +* Compile a single pattern * +*************************************************/ + +/* When the -F option has been used, this is called for each substring. +Otherwise it's called for each supplied pattern. + +Arguments: + pattern the pattern string + options the PCRE options + filename the file name, or NULL for a command-line pattern + count 0 if this is the only command line pattern, or + number of the command line pattern, or + linenumber for a pattern from a file + +Returns: TRUE on success, FALSE after an error +*/ + +static BOOL +compile_single_pattern(char *pattern, int options, char *filename, int count) +{ +char buffer[MBUFTHIRD + 16]; +const char *error; +int errptr; + +if (pattern_count >= MAX_PATTERN_COUNT) + { + fprintf(stderr, "pcregrep: Too many %spatterns (max %d)\n", + (filename == NULL)? "command-line " : "", MAX_PATTERN_COUNT); + return FALSE; + } + +sprintf(buffer, "%s%.*s%s", prefix[process_options], MBUFTHIRD, pattern, + suffix[process_options]); +pattern_list[pattern_count] = + pcre_compile(buffer, options, &error, &errptr, pcretables); +if (pattern_list[pattern_count] != NULL) + { + pattern_count++; + return TRUE; + } + +/* Handle compile errors */ + +errptr -= (int)strlen(prefix[process_options]); +if (errptr > (int)strlen(pattern)) errptr = (int)strlen(pattern); + +if (filename == NULL) + { + if (count == 0) + fprintf(stderr, "pcregrep: Error in command-line regex " + "at offset %d: %s\n", errptr, error); + else + fprintf(stderr, "pcregrep: Error in %s command-line regex " + "at offset %d: %s\n", ordin(count), errptr, error); + } +else + { + fprintf(stderr, "pcregrep: Error in regex in line %d of %s " + "at offset %d: %s\n", count, filename, errptr, error); + } + +return FALSE; +} + + + +/************************************************* +* Compile one supplied pattern * +*************************************************/ + +/* When the -F option has been used, each string may be a list of strings, +separated by line breaks. They will be matched literally. + +Arguments: + pattern the pattern string + options the PCRE options + filename the file name, or NULL for a command-line pattern + count 0 if this is the only command line pattern, or + number of the command line pattern, or + linenumber for a pattern from a file + +Returns: TRUE on success, FALSE after an error +*/ + +static BOOL +compile_pattern(char *pattern, int options, char *filename, int count) +{ +if ((process_options & PO_FIXED_STRINGS) != 0) + { + char *eop = pattern + strlen(pattern); + char buffer[MBUFTHIRD]; + for(;;) + { + int ellength; + char *p = end_of_line(pattern, eop, &ellength); + if (ellength == 0) + return compile_single_pattern(pattern, options, filename, count); + sprintf(buffer, "%.*s", (int)(p - pattern - ellength), pattern); + pattern = p; + if (!compile_single_pattern(buffer, options, filename, count)) + return FALSE; + } + } +else return compile_single_pattern(pattern, options, filename, count); +} + + + +/************************************************* +* Main program * +*************************************************/ + +/* Returns 0 if something matched, 1 if nothing matched, 2 after an error. */ + +int +main(int argc, char **argv) +{ +int i, j; +int rc = 1; +int pcre_options = 0; +int cmd_pattern_count = 0; +int hint_count = 0; +int errptr; +BOOL only_one_at_top; +char *patterns[MAX_PATTERN_COUNT]; +const char *locale_from = "--locale"; +const char *error; + +/* Set the default line ending value from the default in the PCRE library; +"lf", "cr", "crlf", and "any" are supported. Anything else is treated as "lf". +*/ + +(void)pcre_config(PCRE_CONFIG_NEWLINE, &i); +switch(i) + { + default: newline = (char *)"lf"; break; + case '\r': newline = (char *)"cr"; break; + case ('\r' << 8) | '\n': newline = (char *)"crlf"; break; + case -1: newline = (char *)"any"; break; + case -2: newline = (char *)"anycrlf"; break; + } + +/* Process the options */ + +for (i = 1; i < argc; i++) + { + option_item *op = NULL; + char *option_data = (char *)""; /* default to keep compiler happy */ + BOOL longop; + BOOL longopwasequals = FALSE; + + if (argv[i][0] != '-') break; + + /* If we hit an argument that is just "-", it may be a reference to STDIN, + but only if we have previously had -e or -f to define the patterns. */ + + if (argv[i][1] == 0) + { + if (pattern_filename != NULL || pattern_count > 0) break; + else exit(usage(2)); + } + + /* Handle a long name option, or -- to terminate the options */ + + if (argv[i][1] == '-') + { + char *arg = argv[i] + 2; + char *argequals = strchr(arg, '='); + + if (*arg == 0) /* -- terminates options */ + { + i++; + break; /* out of the options-handling loop */ + } + + longop = TRUE; + + /* Some long options have data that follows after =, for example file=name. + Some options have variations in the long name spelling: specifically, we + allow "regexp" because GNU grep allows it, though I personally go along + with Jeffrey Friedl and Larry Wall in preferring "regex" without the "p". + These options are entered in the table as "regex(p)". No option is in both + these categories, fortunately. */ + + for (op = optionlist; op->one_char != 0; op++) + { + char *opbra = strchr(op->long_name, '('); + char *equals = strchr(op->long_name, '='); + if (opbra == NULL) /* Not a (p) case */ + { + if (equals == NULL) /* Not thing=data case */ + { + if (strcmp(arg, op->long_name) == 0) break; + } + else /* Special case xxx=data */ + { + int oplen = equals - op->long_name; + int arglen = (argequals == NULL)? (int)strlen(arg) : argequals - arg; + if (oplen == arglen && strncmp(arg, op->long_name, oplen) == 0) + { + option_data = arg + arglen; + if (*option_data == '=') + { + option_data++; + longopwasequals = TRUE; + } + break; + } + } + } + else /* Special case xxxx(p) */ + { + char buff1[24]; + char buff2[24]; + int baselen = opbra - op->long_name; + sprintf(buff1, "%.*s", baselen, op->long_name); + sprintf(buff2, "%s%.*s", buff1, + (int)strlen(op->long_name) - baselen - 2, opbra + 1); + if (strcmp(arg, buff1) == 0 || strcmp(arg, buff2) == 0) + break; + } + } + + if (op->one_char == 0) + { + fprintf(stderr, "pcregrep: Unknown option %s\n", argv[i]); + exit(usage(2)); + } + } + + + /* Jeffrey Friedl's debugging harness uses these additional options which + are not in the right form for putting in the option table because they use + only one hyphen, yet are more than one character long. By putting them + separately here, they will not get displayed as part of the help() output, + but I don't think Jeffrey will care about that. */ + +#ifdef JFRIEDL_DEBUG + else if (strcmp(argv[i], "-pre") == 0) { + jfriedl_prefix = argv[++i]; + continue; + } else if (strcmp(argv[i], "-post") == 0) { + jfriedl_postfix = argv[++i]; + continue; + } else if (strcmp(argv[i], "-XT") == 0) { + sscanf(argv[++i], "%d", &jfriedl_XT); + continue; + } else if (strcmp(argv[i], "-XR") == 0) { + sscanf(argv[++i], "%d", &jfriedl_XR); + continue; + } +#endif + + + /* One-char options; many that have no data may be in a single argument; we + continue till we hit the last one or one that needs data. */ + + else + { + char *s = argv[i] + 1; + longop = FALSE; + while (*s != 0) + { + for (op = optionlist; op->one_char != 0; op++) + { if (*s == op->one_char) break; } + if (op->one_char == 0) + { + fprintf(stderr, "pcregrep: Unknown option letter '%c' in \"%s\"\n", + *s, argv[i]); + exit(usage(2)); + } + if (op->type != OP_NODATA || s[1] == 0) + { + option_data = s+1; + break; + } + pcre_options = handle_option(*s++, pcre_options); + } + } + + /* At this point we should have op pointing to a matched option. If the type + is NO_DATA, it means that there is no data, and the option might set + something in the PCRE options. */ + + if (op->type == OP_NODATA) + { + pcre_options = handle_option(op->one_char, pcre_options); + continue; + } + + /* If the option type is OP_OP_STRING or OP_OP_NUMBER, it's an option that + either has a value or defaults to something. It cannot have data in a + separate item. At the moment, the only such options are "colo(u)r" and + Jeffrey Friedl's special -S debugging option. */ + + if (*option_data == 0 && + (op->type == OP_OP_STRING || op->type == OP_OP_NUMBER)) + { + switch (op->one_char) + { + case N_COLOUR: + colour_option = (char *)"auto"; + break; +#ifdef JFRIEDL_DEBUG + case 'S': + S_arg = 0; + break; +#endif + } + continue; + } + + /* Otherwise, find the data string for the option. */ + + if (*option_data == 0) + { + if (i >= argc - 1 || longopwasequals) + { + fprintf(stderr, "pcregrep: Data missing after %s\n", argv[i]); + exit(usage(2)); + } + option_data = argv[++i]; + } + + /* If the option type is OP_PATLIST, it's the -e option, which can be called + multiple times to create a list of patterns. */ + + if (op->type == OP_PATLIST) + { + if (cmd_pattern_count >= MAX_PATTERN_COUNT) + { + fprintf(stderr, "pcregrep: Too many command-line patterns (max %d)\n", + MAX_PATTERN_COUNT); + return 2; + } + patterns[cmd_pattern_count++] = option_data; + } + + /* Otherwise, deal with single string or numeric data values. */ + + else if (op->type != OP_NUMBER && op->type != OP_OP_NUMBER) + { + *((char **)op->dataptr) = option_data; + } + else + { + char *endptr; + int n = strtoul(option_data, &endptr, 10); + if (*endptr != 0) + { + if (longop) + { + char *equals = strchr(op->long_name, '='); + int nlen = (equals == NULL)? (int)strlen(op->long_name) : + equals - op->long_name; + fprintf(stderr, "pcregrep: Malformed number \"%s\" after --%.*s\n", + option_data, nlen, op->long_name); + } + else + fprintf(stderr, "pcregrep: Malformed number \"%s\" after -%c\n", + option_data, op->one_char); + exit(usage(2)); + } + *((int *)op->dataptr) = n; + } + } + +/* Options have been decoded. If -C was used, its value is used as a default +for -A and -B. */ + +if (both_context > 0) + { + if (after_context == 0) after_context = both_context; + if (before_context == 0) before_context = both_context; + } + +/* If a locale has not been provided as an option, see if the LC_CTYPE or +LC_ALL environment variable is set, and if so, use it. */ + +if (locale == NULL) + { + locale = getenv("LC_ALL"); + locale_from = "LCC_ALL"; + } + +if (locale == NULL) + { + locale = getenv("LC_CTYPE"); + locale_from = "LC_CTYPE"; + } + +/* If a locale has been provided, set it, and generate the tables the PCRE +needs. Otherwise, pcretables==NULL, which causes the use of default tables. */ + +if (locale != NULL) + { + if (setlocale(LC_CTYPE, locale) == NULL) + { + fprintf(stderr, "pcregrep: Failed to set locale %s (obtained from %s)\n", + locale, locale_from); + return 2; + } + pcretables = pcre_maketables(); + } + +/* Sort out colouring */ + +if (colour_option != NULL && strcmp(colour_option, "never") != 0) + { + if (strcmp(colour_option, "always") == 0) do_colour = TRUE; + else if (strcmp(colour_option, "auto") == 0) do_colour = is_stdout_tty(); + else + { + fprintf(stderr, "pcregrep: Unknown colour setting \"%s\"\n", + colour_option); + return 2; + } + if (do_colour) + { + char *cs = getenv("PCREGREP_COLOUR"); + if (cs == NULL) cs = getenv("PCREGREP_COLOR"); + if (cs != NULL) colour_string = cs; + } + } + +/* Interpret the newline type; the default settings are Unix-like. */ + +if (strcmp(newline, "cr") == 0 || strcmp(newline, "CR") == 0) + { + pcre_options |= PCRE_NEWLINE_CR; + endlinetype = EL_CR; + } +else if (strcmp(newline, "lf") == 0 || strcmp(newline, "LF") == 0) + { + pcre_options |= PCRE_NEWLINE_LF; + endlinetype = EL_LF; + } +else if (strcmp(newline, "crlf") == 0 || strcmp(newline, "CRLF") == 0) + { + pcre_options |= PCRE_NEWLINE_CRLF; + endlinetype = EL_CRLF; + } +else if (strcmp(newline, "any") == 0 || strcmp(newline, "ANY") == 0) + { + pcre_options |= PCRE_NEWLINE_ANY; + endlinetype = EL_ANY; + } +else if (strcmp(newline, "anycrlf") == 0 || strcmp(newline, "ANYCRLF") == 0) + { + pcre_options |= PCRE_NEWLINE_ANYCRLF; + endlinetype = EL_ANYCRLF; + } +else + { + fprintf(stderr, "pcregrep: Invalid newline specifier \"%s\"\n", newline); + return 2; + } + +/* Interpret the text values for -d and -D */ + +if (dee_option != NULL) + { + if (strcmp(dee_option, "read") == 0) dee_action = dee_READ; + else if (strcmp(dee_option, "recurse") == 0) dee_action = dee_RECURSE; + else if (strcmp(dee_option, "skip") == 0) dee_action = dee_SKIP; + else + { + fprintf(stderr, "pcregrep: Invalid value \"%s\" for -d\n", dee_option); + return 2; + } + } + +if (DEE_option != NULL) + { + if (strcmp(DEE_option, "read") == 0) DEE_action = DEE_READ; + else if (strcmp(DEE_option, "skip") == 0) DEE_action = DEE_SKIP; + else + { + fprintf(stderr, "pcregrep: Invalid value \"%s\" for -D\n", DEE_option); + return 2; + } + } + +/* Check the values for Jeffrey Friedl's debugging options. */ + +#ifdef JFRIEDL_DEBUG +if (S_arg > 9) + { + fprintf(stderr, "pcregrep: bad value for -S option\n"); + return 2; + } +if (jfriedl_XT != 0 || jfriedl_XR != 0) + { + if (jfriedl_XT == 0) jfriedl_XT = 1; + if (jfriedl_XR == 0) jfriedl_XR = 1; + } +#endif + +/* Get memory to store the pattern and hints lists. */ + +pattern_list = (pcre **)malloc(MAX_PATTERN_COUNT * sizeof(pcre *)); +hints_list = (pcre_extra **)malloc(MAX_PATTERN_COUNT * sizeof(pcre_extra *)); + +if (pattern_list == NULL || hints_list == NULL) + { + fprintf(stderr, "pcregrep: malloc failed\n"); + goto EXIT2; + } + +/* If no patterns were provided by -e, and there is no file provided by -f, +the first argument is the one and only pattern, and it must exist. */ + +if (cmd_pattern_count == 0 && pattern_filename == NULL) + { + if (i >= argc) return usage(2); + patterns[cmd_pattern_count++] = argv[i++]; + } + +/* Compile the patterns that were provided on the command line, either by +multiple uses of -e or as a single unkeyed pattern. */ + +for (j = 0; j < cmd_pattern_count; j++) + { + if (!compile_pattern(patterns[j], pcre_options, NULL, + (j == 0 && cmd_pattern_count == 1)? 0 : j + 1)) + goto EXIT2; + } + +/* Compile the regular expressions that are provided in a file. */ + +if (pattern_filename != NULL) + { + int linenumber = 0; + FILE *f; + char *filename; + char buffer[MBUFTHIRD]; + + if (strcmp(pattern_filename, "-") == 0) + { + f = stdin; + filename = stdin_name; + } + else + { + f = fopen(pattern_filename, "r"); + if (f == NULL) + { + fprintf(stderr, "pcregrep: Failed to open %s: %s\n", pattern_filename, + strerror(errno)); + goto EXIT2; + } + filename = pattern_filename; + } + + while (fgets(buffer, MBUFTHIRD, f) != NULL) + { + char *s = buffer + (int)strlen(buffer); + while (s > buffer && isspace((unsigned char)(s[-1]))) s--; + *s = 0; + linenumber++; + if (buffer[0] == 0) continue; /* Skip blank lines */ + if (!compile_pattern(buffer, pcre_options, filename, linenumber)) + goto EXIT2; + } + + if (f != stdin) fclose(f); + } + +/* Study the regular expressions, as we will be running them many times */ + +for (j = 0; j < pattern_count; j++) + { + hints_list[j] = pcre_study(pattern_list[j], 0, &error); + if (error != NULL) + { + char s[16]; + if (pattern_count == 1) s[0] = 0; else sprintf(s, " number %d", j); + fprintf(stderr, "pcregrep: Error while studying regex%s: %s\n", s, error); + goto EXIT2; + } + hint_count++; + } + +/* If there are include or exclude patterns, compile them. */ + +if (exclude_pattern != NULL) + { + exclude_compiled = pcre_compile(exclude_pattern, 0, &error, &errptr, + pcretables); + if (exclude_compiled == NULL) + { + fprintf(stderr, "pcregrep: Error in 'exclude' regex at offset %d: %s\n", + errptr, error); + goto EXIT2; + } + } + +if (include_pattern != NULL) + { + include_compiled = pcre_compile(include_pattern, 0, &error, &errptr, + pcretables); + if (include_compiled == NULL) + { + fprintf(stderr, "pcregrep: Error in 'include' regex at offset %d: %s\n", + errptr, error); + goto EXIT2; + } + } + +/* If there are no further arguments, do the business on stdin and exit. */ + +if (i >= argc) + { + rc = pcregrep(stdin, (filenames > FN_DEFAULT)? stdin_name : NULL); + goto EXIT; + } + +/* Otherwise, work through the remaining arguments as files or directories. +Pass in the fact that there is only one argument at top level - this suppresses +the file name if the argument is not a directory and filenames are not +otherwise forced. */ + +only_one_at_top = i == argc - 1; /* Catch initial value of i */ + +for (; i < argc; i++) + { + int frc = grep_or_recurse(argv[i], dee_action == dee_RECURSE, + only_one_at_top); + if (frc > 1) rc = frc; + else if (frc == 0 && rc == 1) rc = 0; + } + +EXIT: +if (pattern_list != NULL) + { + for (i = 0; i < pattern_count; i++) free(pattern_list[i]); + free(pattern_list); + } +if (hints_list != NULL) + { + for (i = 0; i < hint_count; i++) free(hints_list[i]); + free(hints_list); + } +return rc; + +EXIT2: +rc = 2; +goto EXIT; +} + +/* End of pcregrep */ diff --git a/pcre-7.4/pcreposix.c b/pcre-7.4/pcreposix.c new file mode 100644 index 0000000..24f2109 --- /dev/null +++ b/pcre-7.4/pcreposix.c @@ -0,0 +1,338 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are Permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module is a wrapper that provides a POSIX API to the underlying PCRE +functions. */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +/* Ensure that the PCREPOSIX_EXP_xxx macros are set appropriately for +compiling these functions. This must come before including pcreposix.h, where +they are set for an application (using these functions) if they have not +previously been set. */ + +/*#if defined(_WIN32) && !defined(PCRE_STATIC) +#error why are we here? +# define PCREPOSIX_EXP_DECL extern __declspec(dllexport) +# define PCREPOSIX_EXP_DEFN __declspec(dllexport) +#endif +*/ +#include "pcre.h" +#include "pcre_internal.h" +#include "pcreposix.h" + + +/* Table to translate PCRE compile time error codes into POSIX error codes. */ + +static const int eint[] = { + 0, /* no error */ + REG_EESCAPE, /* \ at end of pattern */ + REG_EESCAPE, /* \c at end of pattern */ + REG_EESCAPE, /* unrecognized character follows \ */ + REG_BADBR, /* numbers out of order in {} quantifier */ + REG_BADBR, /* number too big in {} quantifier */ + REG_EBRACK, /* missing terminating ] for character class */ + REG_ECTYPE, /* invalid escape sequence in character class */ + REG_ERANGE, /* range out of order in character class */ + REG_BADRPT, /* nothing to repeat */ + REG_BADRPT, /* operand of unlimited repeat could match the empty string */ + REG_ASSERT, /* internal error: unexpected repeat */ + REG_BADPAT, /* unrecognized character after (? */ + REG_BADPAT, /* POSIX named classes are supported only within a class */ + REG_EPAREN, /* missing ) */ + REG_ESUBREG, /* reference to non-existent subpattern */ + REG_INVARG, /* erroffset passed as NULL */ + REG_INVARG, /* unknown option bit(s) set */ + REG_EPAREN, /* missing ) after comment */ + REG_ESIZE, /* parentheses nested too deeply */ + REG_ESIZE, /* regular expression too large */ + REG_ESPACE, /* failed to get memory */ + REG_EPAREN, /* unmatched brackets */ + REG_ASSERT, /* internal error: code overflow */ + REG_BADPAT, /* unrecognized character after (?< */ + REG_BADPAT, /* lookbehind assertion is not fixed length */ + REG_BADPAT, /* malformed number or name after (?( */ + REG_BADPAT, /* conditional group contains more than two branches */ + REG_BADPAT, /* assertion expected after (?( */ + REG_BADPAT, /* (?R or (?[+-]digits must be followed by ) */ + REG_ECTYPE, /* unknown POSIX class name */ + REG_BADPAT, /* POSIX collating elements are not supported */ + REG_INVARG, /* this version of PCRE is not compiled with PCRE_UTF8 support */ + REG_BADPAT, /* spare error */ + REG_BADPAT, /* character value in \x{...} sequence is too large */ + REG_BADPAT, /* invalid condition (?(0) */ + REG_BADPAT, /* \C not allowed in lookbehind assertion */ + REG_EESCAPE, /* PCRE does not support \L, \l, \N, \U, or \u */ + REG_BADPAT, /* number after (?C is > 255 */ + REG_BADPAT, /* closing ) for (?C expected */ + REG_BADPAT, /* recursive call could loop indefinitely */ + REG_BADPAT, /* unrecognized character after (?P */ + REG_BADPAT, /* syntax error in subpattern name (missing terminator) */ + REG_BADPAT, /* two named subpatterns have the same name */ + REG_BADPAT, /* invalid UTF-8 string */ + REG_BADPAT, /* support for \P, \p, and \X has not been compiled */ + REG_BADPAT, /* malformed \P or \p sequence */ + REG_BADPAT, /* unknown property name after \P or \p */ + REG_BADPAT, /* subpattern name is too long (maximum 32 characters) */ + REG_BADPAT, /* too many named subpatterns (maximum 10,000) */ + REG_BADPAT, /* repeated subpattern is too long */ + REG_BADPAT, /* octal value is greater than \377 (not in UTF-8 mode) */ + REG_BADPAT, /* internal error: overran compiling workspace */ + REG_BADPAT, /* internal error: previously-checked referenced subpattern not found */ + REG_BADPAT, /* DEFINE group contains more than one branch */ + REG_BADPAT, /* repeating a DEFINE group is not allowed */ + REG_INVARG, /* inconsistent NEWLINE options */ + REG_BADPAT, /* \g is not followed followed by an (optionally braced) non-zero number */ + REG_BADPAT, /* (?+ or (?- must be followed by a non-zero number */ + REG_BADPAT /* number is too big */ +}; + +/* Table of texts corresponding to POSIX error codes */ + +static const char *const pstring[] = { + "", /* Dummy for value 0 */ + "internal error", /* REG_ASSERT */ + "invalid repeat counts in {}", /* BADBR */ + "pattern error", /* BADPAT */ + "? * + invalid", /* BADRPT */ + "unbalanced {}", /* EBRACE */ + "unbalanced []", /* EBRACK */ + "collation error - not relevant", /* ECOLLATE */ + "bad class", /* ECTYPE */ + "bad escape sequence", /* EESCAPE */ + "empty expression", /* EMPTY */ + "unbalanced ()", /* EPAREN */ + "bad range inside []", /* ERANGE */ + "expression too big", /* ESIZE */ + "failed to get memory", /* ESPACE */ + "bad back reference", /* ESUBREG */ + "bad argument", /* INVARG */ + "match failed" /* NOMATCH */ +}; + + + + +/************************************************* +* Translate error code to string * +*************************************************/ + +PCREPOSIX_EXP_DEFN size_t +regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) +{ +const char *message, *addmessage; +size_t length, addlength; + +message = (errcode >= (int)(sizeof(pstring)/sizeof(char *)))? + "unknown error code" : pstring[errcode]; +length = strlen(message) + 1; + +addmessage = " at offset "; +addlength = (preg != NULL && (int)preg->re_erroffset != -1)? + strlen(addmessage) + 6 : 0; + +if (errbuf_size > 0) + { + if (addlength > 0 && errbuf_size >= length + addlength) + sprintf(errbuf, "%s%s%-6d", message, addmessage, (int)preg->re_erroffset); + else + { + strncpy(errbuf, message, errbuf_size - 1); + errbuf[errbuf_size-1] = 0; + } + } + +return length + addlength; +} + + + + +/************************************************* +* Free store held by a regex * +*************************************************/ + +PCREPOSIX_EXP_DEFN void +regfree(regex_t *preg) +{ +(pcre_free)(preg->re_pcre); +} + + + + +/************************************************* +* Compile a regular expression * +*************************************************/ + +/* +Arguments: + preg points to a structure for recording the compiled expression + pattern the pattern to compile + cflags compilation flags + +Returns: 0 on success + various non-zero codes on failure +*/ + +PCREPOSIX_EXP_DEFN int +regcomp(regex_t *preg, const char *pattern, int cflags) +{ +const char *errorptr; +int erroffset; +int errorcode; +int options = 0; + +if ((cflags & REG_ICASE) != 0) options |= PCRE_CASELESS; +if ((cflags & REG_NEWLINE) != 0) options |= PCRE_MULTILINE; +if ((cflags & REG_DOTALL) != 0) options |= PCRE_DOTALL; +if ((cflags & REG_NOSUB) != 0) options |= PCRE_NO_AUTO_CAPTURE; +if ((cflags & REG_UTF8) != 0) options |= PCRE_UTF8; + +preg->re_pcre = pcre_compile2(pattern, options, &errorcode, &errorptr, + &erroffset, NULL); +preg->re_erroffset = erroffset; + +if (preg->re_pcre == NULL) return eint[errorcode]; + +preg->re_nsub = pcre_info((const pcre *)preg->re_pcre, NULL, NULL); +return 0; +} + + + + +/************************************************* +* Match a regular expression * +*************************************************/ + +/* Unfortunately, PCRE requires 3 ints of working space for each captured +substring, so we have to get and release working store instead of just using +the POSIX structures as was done in earlier releases when PCRE needed only 2 +ints. However, if the number of possible capturing brackets is small, use a +block of store on the stack, to reduce the use of malloc/free. The threshold is +in a macro that can be changed at configure time. + +If REG_NOSUB was specified at compile time, the PCRE_NO_AUTO_CAPTURE flag will +be set. When this is the case, the nmatch and pmatch arguments are ignored, and +the only result is yes/no/error. */ + +PCREPOSIX_EXP_DEFN int +regexec(const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags) +{ +int rc; +int options = 0; +int *ovector = NULL; +int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; +BOOL allocated_ovector = FALSE; +BOOL nosub = + (((const pcre *)preg->re_pcre)->options & PCRE_NO_AUTO_CAPTURE) != 0; + +if ((eflags & REG_NOTBOL) != 0) options |= PCRE_NOTBOL; +if ((eflags & REG_NOTEOL) != 0) options |= PCRE_NOTEOL; + +((regex_t *)preg)->re_erroffset = (size_t)(-1); /* Only has meaning after compile */ + +/* When no string data is being returned, ensure that nmatch is zero. +Otherwise, ensure the vector for holding the return data is large enough. */ + +if (nosub) nmatch = 0; + +else if (nmatch > 0) + { + if (nmatch <= POSIX_MALLOC_THRESHOLD) + { + ovector = &(small_ovector[0]); + } + else + { + if (nmatch > INT_MAX/(sizeof(int) * 3)) return REG_ESPACE; + ovector = (int *)malloc(sizeof(int) * nmatch * 3); + if (ovector == NULL) return REG_ESPACE; + allocated_ovector = TRUE; + } + } + +rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string, (int)strlen(string), + 0, options, ovector, nmatch * 3); + +if (rc == 0) rc = nmatch; /* All captured slots were filled in */ + +if (rc >= 0) + { + size_t i; + if (!nosub) + { + for (i = 0; i < (size_t)rc; i++) + { + pmatch[i].rm_so = ovector[i*2]; + pmatch[i].rm_eo = ovector[i*2+1]; + } + if (allocated_ovector) free(ovector); + for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; + } + return 0; + } + +else + { + if (allocated_ovector) free(ovector); + switch(rc) + { + case PCRE_ERROR_NOMATCH: return REG_NOMATCH; + case PCRE_ERROR_NULL: return REG_INVARG; + case PCRE_ERROR_BADOPTION: return REG_INVARG; + case PCRE_ERROR_BADMAGIC: return REG_INVARG; + case PCRE_ERROR_UNKNOWN_NODE: return REG_ASSERT; + case PCRE_ERROR_NOMEMORY: return REG_ESPACE; + case PCRE_ERROR_MATCHLIMIT: return REG_ESPACE; + case PCRE_ERROR_BADUTF8: return REG_INVARG; + case PCRE_ERROR_BADUTF8_OFFSET: return REG_INVARG; + default: return REG_ASSERT; + } + } +} + +/* End of pcreposix.c */ diff --git a/pcre-7.4/pcreposix.h b/pcre-7.4/pcreposix.h new file mode 100644 index 0000000..875e1ff --- /dev/null +++ b/pcre-7.4/pcreposix.h @@ -0,0 +1,142 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +#ifndef _PCREPOSIX_H +#define _PCREPOSIX_H + +/* This is the header for the POSIX wrapper interface to the PCRE Perl- +Compatible Regular Expression library. It defines the things POSIX says should +be there. I hope. + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* Have to include stdlib.h in order to ensure that size_t is defined. */ + +#include <stdlib.h> + +/* Allow for C++ users */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Options, mostly defined by POSIX, but with a couple of extras. */ + +#define REG_ICASE 0x0001 +#define REG_NEWLINE 0x0002 +#define REG_NOTBOL 0x0004 +#define REG_NOTEOL 0x0008 +#define REG_DOTALL 0x0010 /* NOT defined by POSIX. */ +#define REG_NOSUB 0x0020 +#define REG_UTF8 0x0040 /* NOT defined by POSIX. */ + +/* This is not used by PCRE, but by defining it we make it easier +to slot PCRE into existing programs that make POSIX calls. */ + +#define REG_EXTENDED 0 + +/* Error values. Not all these are relevant or used by the wrapper. */ + +enum { + REG_ASSERT = 1, /* internal error ? */ + REG_BADBR, /* invalid repeat counts in {} */ + REG_BADPAT, /* pattern error */ + REG_BADRPT, /* ? * + invalid */ + REG_EBRACE, /* unbalanced {} */ + REG_EBRACK, /* unbalanced [] */ + REG_ECOLLATE, /* collation error - not relevant */ + REG_ECTYPE, /* bad class */ + REG_EESCAPE, /* bad escape sequence */ + REG_EMPTY, /* empty expression */ + REG_EPAREN, /* unbalanced () */ + REG_ERANGE, /* bad range inside [] */ + REG_ESIZE, /* expression too big */ + REG_ESPACE, /* failed to get memory */ + REG_ESUBREG, /* bad back reference */ + REG_INVARG, /* bad argument */ + REG_NOMATCH /* match failed */ +}; + + +/* The structure representing a compiled regular expression. */ + +typedef struct { + void *re_pcre; + size_t re_nsub; + size_t re_erroffset; +} regex_t; + +/* The structure in which a captured offset is returned. */ + +typedef int regoff_t; + +typedef struct { + regoff_t rm_so; + regoff_t rm_eo; +} regmatch_t; + +/* When an application links to a PCRE DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE, the appropriate +export settings are needed, and are set in pcreposix.c before including this +file. */ + +#if defined(_WIN32) && !defined(PCRE_STATIC) && !defined(PCREPOSIX_EXP_DECL) +# define PCREPOSIX_EXP_DECL extern __declspec(dllimport) +# define PCREPOSIX_EXP_DEFN __declspec(dllimport) +#endif + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCREPOSIX_EXP_DECL +# ifdef __cplusplus +# define PCREPOSIX_EXP_DECL extern "C" +# define PCREPOSIX_EXP_DEFN extern "C" +# else +# define PCREPOSIX_EXP_DECL extern +# define PCREPOSIX_EXP_DEFN extern +# endif +#endif + +/* The functions */ + +PCREPOSIX_EXP_DECL int regcomp(regex_t *, const char *, int); +PCREPOSIX_EXP_DECL int regexec(const regex_t *, const char *, size_t, + regmatch_t *, int); +PCREPOSIX_EXP_DECL size_t regerror(int, const regex_t *, char *, size_t); +PCREPOSIX_EXP_DECL void regfree(regex_t *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* End of pcreposix.h */ diff --git a/pcre-7.4/pcretest.c b/pcre-7.4/pcretest.c new file mode 100644 index 0000000..a222146 --- /dev/null +++ b/pcre-7.4/pcretest.c @@ -0,0 +1,2396 @@ +/************************************************* +* PCRE testing program * +*************************************************/ + +/* This program was hacked up as a tester for PCRE. I really should have +written it more tidily in the first place. Will I ever learn? It has grown and +been extended and consequently is now rather, er, *very* untidy in places. + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <locale.h> +#include <errno.h> + + +/* A number of things vary for Windows builds. Originally, pcretest opened its +input and output without "b"; then I was told that "b" was needed in some +environments, so it was added for release 5.0 to both the input and output. (It +makes no difference on Unix-like systems.) Later I was told that it is wrong +for the input on Windows. I've now abstracted the modes into two macros that +are set here, to make it easier to fiddle with them, and removed "b" from the +input mode under Windows. */ + +#if defined(_WIN32) || defined(WIN32) +#include <io.h> /* For _setmode() */ +#include <fcntl.h> /* For _O_BINARY */ +#define INPUT_MODE "r" +#define OUTPUT_MODE "wb" + +#else +#include <sys/time.h> /* These two includes are needed */ +#include <sys/resource.h> /* for setrlimit(). */ +#define INPUT_MODE "rb" +#define OUTPUT_MODE "wb" +#endif + + +/* We have to include pcre_internal.h because we need the internal info for +displaying the results of pcre_study() and we also need to know about the +internal macros, structures, and other internal data values; pcretest has +"inside information" compared to a program that strictly follows the PCRE API. + +Although pcre_internal.h does itself include pcre.h, we explicitly include it +here before pcre_internal.h so that the PCRE_EXP_xxx macros get set +appropriately for an application, not for building PCRE. */ + +#include "pcre.h" +#include "pcre_internal.h" + +/* We need access to the data tables that PCRE uses. So as not to have to keep +two copies, we include the source file here, changing the names of the external +symbols to prevent clashes. */ + +#define _pcre_utf8_table1 utf8_table1 +#define _pcre_utf8_table1_size utf8_table1_size +#define _pcre_utf8_table2 utf8_table2 +#define _pcre_utf8_table3 utf8_table3 +#define _pcre_utf8_table4 utf8_table4 +#define _pcre_utt utt +#define _pcre_utt_size utt_size +#define _pcre_utt_names utt_names +#define _pcre_OP_lengths OP_lengths + +#include "pcre_tables.c" + +/* We also need the pcre_printint() function for printing out compiled +patterns. This function is in a separate file so that it can be included in +pcre_compile.c when that module is compiled with debugging enabled. + +The definition of the macro PRINTABLE, which determines whether to print an +output character as-is or as a hex value when showing compiled patterns, is +contained in this file. We uses it here also, in cases when the locale has not +been explicitly changed, so as to get consistent output from systems that +differ in their output from isprint() even in the "C" locale. */ + +#include "pcre_printint.src" + +#define PRINTHEX(c) (locale_set? isprint(c) : PRINTABLE(c)) + + +/* It is possible to compile this test program without including support for +testing the POSIX interface, though this is not available via the standard +Makefile. */ + +#if !defined NOPOSIX +#include "pcreposix.h" +#endif + +/* It is also possible, for the benefit of the version currently imported into +Exim, to build pcretest without support for UTF8 (define NOUTF8), without the +interface to the DFA matcher (NODFA), and without the doublecheck of the old +"info" function (define NOINFOCHECK). In fact, we automatically cut out the +UTF8 support if PCRE is built without it. */ + +#ifndef SUPPORT_UTF8 +#ifndef NOUTF8 +#define NOUTF8 +#endif +#endif + + +/* Other parameters */ + +#ifndef CLOCKS_PER_SEC +#ifdef CLK_TCK +#define CLOCKS_PER_SEC CLK_TCK +#else +#define CLOCKS_PER_SEC 100 +#endif +#endif + +/* This is the default loop count for timing. */ + +#define LOOPREPEAT 500000 + +/* Static variables */ + +static FILE *outfile; +static int log_store = 0; +static int callout_count; +static int callout_extra; +static int callout_fail_count; +static int callout_fail_id; +static int debug_lengths; +static int first_callout; +static int locale_set = 0; +static int show_malloc; +static int use_utf8; +static size_t gotten_store; + +/* The buffers grow automatically if very long input lines are encountered. */ + +static int buffer_size = 50000; +static uschar *buffer = NULL; +static uschar *dbuffer = NULL; +static uschar *pbuffer = NULL; + + + +/************************************************* +* Read or extend an input line * +*************************************************/ + +/* Input lines are read into buffer, but both patterns and data lines can be +continued over multiple input lines. In addition, if the buffer fills up, we +want to automatically expand it so as to be able to handle extremely large +lines that are needed for certain stress tests. When the input buffer is +expanded, the other two buffers must also be expanded likewise, and the +contents of pbuffer, which are a copy of the input for callouts, must be +preserved (for when expansion happens for a data line). This is not the most +optimal way of handling this, but hey, this is just a test program! + +Arguments: + f the file to read + start where in buffer to start (this *must* be within buffer) + +Returns: pointer to the start of new data + could be a copy of start, or could be moved + NULL if no data read and EOF reached +*/ + +static uschar * +extend_inputline(FILE *f, uschar *start) +{ +uschar *here = start; + +for (;;) + { + int rlen = buffer_size - (here - buffer); + + if (rlen > 1000) + { + int dlen; + if (fgets((char *)here, rlen, f) == NULL) + return (here == start)? NULL : start; + dlen = (int)strlen((char *)here); + if (dlen > 0 && here[dlen - 1] == '\n') return start; + here += dlen; + } + + else + { + int new_buffer_size = 2*buffer_size; + uschar *new_buffer = (unsigned char *)malloc(new_buffer_size); + uschar *new_dbuffer = (unsigned char *)malloc(new_buffer_size); + uschar *new_pbuffer = (unsigned char *)malloc(new_buffer_size); + + if (new_buffer == NULL || new_dbuffer == NULL || new_pbuffer == NULL) + { + fprintf(stderr, "pcretest: malloc(%d) failed\n", new_buffer_size); + exit(1); + } + + memcpy(new_buffer, buffer, buffer_size); + memcpy(new_pbuffer, pbuffer, buffer_size); + + buffer_size = new_buffer_size; + + start = new_buffer + (start - buffer); + here = new_buffer + (here - buffer); + + free(buffer); + free(dbuffer); + free(pbuffer); + + buffer = new_buffer; + dbuffer = new_dbuffer; + pbuffer = new_pbuffer; + } + } + +return NULL; /* Control never gets here */ +} + + + + + + + +/************************************************* +* Read number from string * +*************************************************/ + +/* We don't use strtoul() because SunOS4 doesn't have it. Rather than mess +around with conditional compilation, just do the job by hand. It is only used +for unpicking arguments, so just keep it simple. + +Arguments: + str string to be converted + endptr where to put the end pointer + +Returns: the unsigned long +*/ + +static int +get_value(unsigned char *str, unsigned char **endptr) +{ +int result = 0; +while(*str != 0 && isspace(*str)) str++; +while (isdigit(*str)) result = result * 10 + (int)(*str++ - '0'); +*endptr = str; +return(result); +} + + + + +/************************************************* +* Convert UTF-8 string to value * +*************************************************/ + +/* This function takes one or more bytes that represents a UTF-8 character, +and returns the value of the character. + +Argument: + utf8bytes a pointer to the byte vector + vptr a pointer to an int to receive the value + +Returns: > 0 => the number of bytes consumed + -6 to 0 => malformed UTF-8 character at offset = (-return) +*/ + +#if !defined NOUTF8 + +static int +utf82ord(unsigned char *utf8bytes, int *vptr) +{ +int c = *utf8bytes++; +int d = c; +int i, j, s; + +for (i = -1; i < 6; i++) /* i is number of additional bytes */ + { + if ((d & 0x80) == 0) break; + d <<= 1; + } + +if (i == -1) { *vptr = c; return 1; } /* ascii character */ +if (i == 0 || i == 6) return 0; /* invalid UTF-8 */ + +/* i now has a value in the range 1-5 */ + +s = 6*i; +d = (c & utf8_table3[i]) << s; + +for (j = 0; j < i; j++) + { + c = *utf8bytes++; + if ((c & 0xc0) != 0x80) return -(j+1); + s -= 6; + d |= (c & 0x3f) << s; + } + +/* Check that encoding was the correct unique one */ + +for (j = 0; j < utf8_table1_size; j++) + if (d <= utf8_table1[j]) break; +if (j != i) return -(i+1); + +/* Valid value */ + +*vptr = d; +return i+1; +} + +#endif + + + +/************************************************* +* Convert character value to UTF-8 * +*************************************************/ + +/* This function takes an integer value in the range 0 - 0x7fffffff +and encodes it as a UTF-8 character in 0 to 6 bytes. + +Arguments: + cvalue the character value + utf8bytes pointer to buffer for result - at least 6 bytes long + +Returns: number of characters placed in the buffer +*/ + +#if !defined NOUTF8 + +static int +ord2utf8(int cvalue, uschar *utf8bytes) +{ +register int i, j; +for (i = 0; i < utf8_table1_size; i++) + if (cvalue <= utf8_table1[i]) break; +utf8bytes += i; +for (j = i; j > 0; j--) + { + *utf8bytes-- = 0x80 | (cvalue & 0x3f); + cvalue >>= 6; + } +*utf8bytes = utf8_table2[i] | cvalue; +return i + 1; +} + +#endif + + + +/************************************************* +* Print character string * +*************************************************/ + +/* Character string printing function. Must handle UTF-8 strings in utf8 +mode. Yields number of characters printed. If handed a NULL file, just counts +chars without printing. */ + +static int pchars(unsigned char *p, int length, FILE *f) +{ +int c = 0; +int yield = 0; + +while (length-- > 0) + { +#if !defined NOUTF8 + if (use_utf8) + { + int rc = utf82ord(p, &c); + + if (rc > 0 && rc <= length + 1) /* Mustn't run over the end */ + { + length -= rc - 1; + p += rc; + if (PRINTHEX(c)) + { + if (f != NULL) fprintf(f, "%c", c); + yield++; + } + else + { + int n = 4; + if (f != NULL) fprintf(f, "\\x{%02x}", c); + yield += (n <= 0x000000ff)? 2 : + (n <= 0x00000fff)? 3 : + (n <= 0x0000ffff)? 4 : + (n <= 0x000fffff)? 5 : 6; + } + continue; + } + } +#endif + + /* Not UTF-8, or malformed UTF-8 */ + + c = *p++; + if (PRINTHEX(c)) + { + if (f != NULL) fprintf(f, "%c", c); + yield++; + } + else + { + if (f != NULL) fprintf(f, "\\x%02x", c); + yield += 4; + } + } + +return yield; +} + + + +/************************************************* +* Callout function * +*************************************************/ + +/* Called from PCRE as a result of the (?C) item. We print out where we are in +the match. Yield zero unless more callouts than the fail count, or the callout +data is not zero. */ + +static int callout(pcre_callout_block *cb) +{ +FILE *f = (first_callout | callout_extra)? outfile : NULL; +int i, pre_start, post_start, subject_length; + +if (callout_extra) + { + fprintf(f, "Callout %d: last capture = %d\n", + cb->callout_number, cb->capture_last); + + for (i = 0; i < cb->capture_top * 2; i += 2) + { + if (cb->offset_vector[i] < 0) + fprintf(f, "%2d: <unset>\n", i/2); + else + { + fprintf(f, "%2d: ", i/2); + (void)pchars((unsigned char *)cb->subject + cb->offset_vector[i], + cb->offset_vector[i+1] - cb->offset_vector[i], f); + fprintf(f, "\n"); + } + } + } + +/* Re-print the subject in canonical form, the first time or if giving full +datails. On subsequent calls in the same match, we use pchars just to find the +printed lengths of the substrings. */ + +if (f != NULL) fprintf(f, "--->"); + +pre_start = pchars((unsigned char *)cb->subject, cb->start_match, f); +post_start = pchars((unsigned char *)(cb->subject + cb->start_match), + cb->current_position - cb->start_match, f); + +subject_length = pchars((unsigned char *)cb->subject, cb->subject_length, NULL); + +(void)pchars((unsigned char *)(cb->subject + cb->current_position), + cb->subject_length - cb->current_position, f); + +if (f != NULL) fprintf(f, "\n"); + +/* Always print appropriate indicators, with callout number if not already +shown. For automatic callouts, show the pattern offset. */ + +if (cb->callout_number == 255) + { + fprintf(outfile, "%+3d ", cb->pattern_position); + if (cb->pattern_position > 99) fprintf(outfile, "\n "); + } +else + { + if (callout_extra) fprintf(outfile, " "); + else fprintf(outfile, "%3d ", cb->callout_number); + } + +for (i = 0; i < pre_start; i++) fprintf(outfile, " "); +fprintf(outfile, "^"); + +if (post_start > 0) + { + for (i = 0; i < post_start - 1; i++) fprintf(outfile, " "); + fprintf(outfile, "^"); + } + +for (i = 0; i < subject_length - pre_start - post_start + 4; i++) + fprintf(outfile, " "); + +fprintf(outfile, "%.*s", (cb->next_item_length == 0)? 1 : cb->next_item_length, + pbuffer + cb->pattern_position); + +fprintf(outfile, "\n"); +first_callout = 0; + +if (cb->callout_data != NULL) + { + int callout_data = *((int *)(cb->callout_data)); + if (callout_data != 0) + { + fprintf(outfile, "Callout data = %d\n", callout_data); + return callout_data; + } + } + +return (cb->callout_number != callout_fail_id)? 0 : + (++callout_count >= callout_fail_count)? 1 : 0; +} + + +/************************************************* +* Local malloc functions * +*************************************************/ + +/* Alternative malloc function, to test functionality and show the size of the +compiled re. */ + +static void *new_malloc(size_t size) +{ +void *block = malloc(size); +gotten_store = size; +if (show_malloc) + fprintf(outfile, "malloc %3d %p\n", (int)size, block); +return block; +} + +static void new_free(void *block) +{ +if (show_malloc) + fprintf(outfile, "free %p\n", block); +free(block); +} + + +/* For recursion malloc/free, to test stacking calls */ + +static void *stack_malloc(size_t size) +{ +void *block = malloc(size); +if (show_malloc) + fprintf(outfile, "stack_malloc %3d %p\n", (int)size, block); +return block; +} + +static void stack_free(void *block) +{ +if (show_malloc) + fprintf(outfile, "stack_free %p\n", block); +free(block); +} + + +/************************************************* +* Call pcre_fullinfo() * +*************************************************/ + +/* Get one piece of information from the pcre_fullinfo() function */ + +static void new_info(pcre *re, pcre_extra *study, int option, void *ptr) +{ +int rc; +if ((rc = pcre_fullinfo(re, study, option, ptr)) < 0) + fprintf(outfile, "Error %d from pcre_fullinfo(%d)\n", rc, option); +} + + + +/************************************************* +* Byte flipping function * +*************************************************/ + +static unsigned long int +byteflip(unsigned long int value, int n) +{ +if (n == 2) return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8); +return ((value & 0x000000ff) << 24) | + ((value & 0x0000ff00) << 8) | + ((value & 0x00ff0000) >> 8) | + ((value & 0xff000000) >> 24); +} + + + + +/************************************************* +* Check match or recursion limit * +*************************************************/ + +static int +check_match_limit(pcre *re, pcre_extra *extra, uschar *bptr, int len, + int start_offset, int options, int *use_offsets, int use_size_offsets, + int flag, unsigned long int *limit, int errnumber, const char *msg) +{ +int count; +int min = 0; +int mid = 64; +int max = -1; + +extra->flags |= flag; + +for (;;) + { + *limit = mid; + + count = pcre_exec(re, extra, (char *)bptr, len, start_offset, options, + use_offsets, use_size_offsets); + + if (count == errnumber) + { + /* fprintf(outfile, "Testing %s limit = %d\n", msg, mid); */ + min = mid; + mid = (mid == max - 1)? max : (max > 0)? (min + max)/2 : mid*2; + } + + else if (count >= 0 || count == PCRE_ERROR_NOMATCH || + count == PCRE_ERROR_PARTIAL) + { + if (mid == min + 1) + { + fprintf(outfile, "Minimum %s limit = %d\n", msg, mid); + break; + } + /* fprintf(outfile, "Testing %s limit = %d\n", msg, mid); */ + max = mid; + mid = (min + mid)/2; + } + else break; /* Some other error */ + } + +extra->flags &= ~flag; +return count; +} + + + +/************************************************* +* Case-independent strncmp() function * +*************************************************/ + +/* +Arguments: + s first string + t second string + n number of characters to compare + +Returns: < 0, = 0, or > 0, according to the comparison +*/ + +static int +strncmpic(uschar *s, uschar *t, int n) +{ +while (n--) + { + int c = tolower(*s++) - tolower(*t++); + if (c) return c; + } +return 0; +} + + + +/************************************************* +* Check newline indicator * +*************************************************/ + +/* This is used both at compile and run-time to check for <xxx> escapes, where +xxx is LF, CR, CRLF, ANYCRLF, or ANY. Print a message and return 0 if there is +no match. + +Arguments: + p points after the leading '<' + f file for error message + +Returns: appropriate PCRE_NEWLINE_xxx flags, or 0 +*/ + +static int +check_newline(uschar *p, FILE *f) +{ +if (strncmpic(p, (uschar *)"cr>", 3) == 0) return PCRE_NEWLINE_CR; +if (strncmpic(p, (uschar *)"lf>", 3) == 0) return PCRE_NEWLINE_LF; +if (strncmpic(p, (uschar *)"crlf>", 5) == 0) return PCRE_NEWLINE_CRLF; +if (strncmpic(p, (uschar *)"anycrlf>", 8) == 0) return PCRE_NEWLINE_ANYCRLF; +if (strncmpic(p, (uschar *)"any>", 4) == 0) return PCRE_NEWLINE_ANY; +if (strncmpic(p, (uschar *)"bsr_anycrlf>", 12) == 0) return PCRE_BSR_ANYCRLF; +if (strncmpic(p, (uschar *)"bsr_unicode>", 12) == 0) return PCRE_BSR_UNICODE; +fprintf(f, "Unknown newline type at: <%s\n", p); +return 0; +} + + + +/************************************************* +* Usage function * +*************************************************/ + +static void +usage(void) +{ +printf("Usage: pcretest [options] [<input> [<output>]]\n"); +printf(" -b show compiled code (bytecode)\n"); +printf(" -C show PCRE compile-time options and exit\n"); +printf(" -d debug: show compiled code and information (-b and -i)\n"); +#if !defined NODFA +printf(" -dfa force DFA matching for all subjects\n"); +#endif +printf(" -help show usage information\n"); +printf(" -i show information about compiled patterns\n" + " -m output memory used information\n" + " -o <n> set size of offsets vector to <n>\n"); +#if !defined NOPOSIX +printf(" -p use POSIX interface\n"); +#endif +printf(" -q quiet: do not output PCRE version number at start\n"); +printf(" -S <n> set stack size to <n> megabytes\n"); +printf(" -s output store (memory) used information\n" + " -t time compilation and execution\n"); +printf(" -t <n> time compilation and execution, repeating <n> times\n"); +printf(" -tm time execution (matching) only\n"); +printf(" -tm <n> time execution (matching) only, repeating <n> times\n"); +} + + + +/************************************************* +* Main Program * +*************************************************/ + +/* Read lines from named file or stdin and write to named file or stdout; lines +consist of a regular expression, in delimiters and optionally followed by +options, followed by a set of test data, terminated by an empty line. */ + +int main(int argc, char **argv) +{ +FILE *infile = stdin; +int options = 0; +int study_options = 0; +int op = 1; +int timeit = 0; +int timeitm = 0; +int showinfo = 0; +int showstore = 0; +int quiet = 0; +int size_offsets = 45; +int size_offsets_max; +int *offsets = NULL; +#if !defined NOPOSIX +int posix = 0; +#endif +int debug = 0; +int done = 0; +int all_use_dfa = 0; +int yield = 0; +int stack_size; + +/* These vectors store, end-to-end, a list of captured substring names. Assume +that 1024 is plenty long enough for the few names we'll be testing. */ + +uschar copynames[1024]; +uschar getnames[1024]; + +uschar *copynamesptr; +uschar *getnamesptr; + +/* Get buffers from malloc() so that Electric Fence will check their misuse +when I am debugging. They grow automatically when very long lines are read. */ + +buffer = (unsigned char *)malloc(buffer_size); +dbuffer = (unsigned char *)malloc(buffer_size); +pbuffer = (unsigned char *)malloc(buffer_size); + +/* The outfile variable is static so that new_malloc can use it. */ + +outfile = stdout; + +/* The following _setmode() stuff is some Windows magic that tells its runtime +library to translate CRLF into a single LF character. At least, that's what +I've been told: never having used Windows I take this all on trust. Originally +it set 0x8000, but then I was advised that _O_BINARY was better. */ + +#if defined(_WIN32) || defined(WIN32) +_setmode( _fileno( stdout ), _O_BINARY ); +#endif + +/* Scan options */ + +while (argc > 1 && argv[op][0] == '-') + { + unsigned char *endptr; + + if (strcmp(argv[op], "-s") == 0 || strcmp(argv[op], "-m") == 0) + showstore = 1; + else if (strcmp(argv[op], "-q") == 0) quiet = 1; + else if (strcmp(argv[op], "-b") == 0) debug = 1; + else if (strcmp(argv[op], "-i") == 0) showinfo = 1; + else if (strcmp(argv[op], "-d") == 0) showinfo = debug = 1; +#if !defined NODFA + else if (strcmp(argv[op], "-dfa") == 0) all_use_dfa = 1; +#endif + else if (strcmp(argv[op], "-o") == 0 && argc > 2 && + ((size_offsets = get_value((unsigned char *)argv[op+1], &endptr)), + *endptr == 0)) + { + op++; + argc--; + } + else if (strcmp(argv[op], "-t") == 0 || strcmp(argv[op], "-tm") == 0) + { + int both = argv[op][2] == 0; + int temp; + if (argc > 2 && (temp = get_value((unsigned char *)argv[op+1], &endptr), + *endptr == 0)) + { + timeitm = temp; + op++; + argc--; + } + else timeitm = LOOPREPEAT; + if (both) timeit = timeitm; + } + else if (strcmp(argv[op], "-S") == 0 && argc > 2 && + ((stack_size = get_value((unsigned char *)argv[op+1], &endptr)), + *endptr == 0)) + { +#if defined(_WIN32) || defined(WIN32) + printf("PCRE: -S not supported on this OS\n"); + exit(1); +#else + int rc; + struct rlimit rlim; + getrlimit(RLIMIT_STACK, &rlim); + rlim.rlim_cur = stack_size * 1024 * 1024; + rc = setrlimit(RLIMIT_STACK, &rlim); + if (rc != 0) + { + printf("PCRE: setrlimit() failed with error %d\n", rc); + exit(1); + } + op++; + argc--; +#endif + } +#if !defined NOPOSIX + else if (strcmp(argv[op], "-p") == 0) posix = 1; +#endif + else if (strcmp(argv[op], "-C") == 0) + { + int rc; + printf("PCRE version %s\n", pcre_version()); + printf("Compiled with\n"); + (void)pcre_config(PCRE_CONFIG_UTF8, &rc); + printf(" %sUTF-8 support\n", rc? "" : "No "); + (void)pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &rc); + printf(" %sUnicode properties support\n", rc? "" : "No "); + (void)pcre_config(PCRE_CONFIG_NEWLINE, &rc); + printf(" Newline sequence is %s\n", (rc == '\r')? "CR" : + (rc == '\n')? "LF" : (rc == ('\r'<<8 | '\n'))? "CRLF" : + (rc == -2)? "ANYCRLF" : + (rc == -1)? "ANY" : "???"); + (void)pcre_config(PCRE_CONFIG_BSR, &rc); + printf(" \\R matches %s\n", rc? "CR, LF, or CRLF only" : + "all Unicode newlines"); + (void)pcre_config(PCRE_CONFIG_LINK_SIZE, &rc); + printf(" Internal link size = %d\n", rc); + (void)pcre_config(PCRE_CONFIG_POSIX_MALLOC_THRESHOLD, &rc); + printf(" POSIX malloc threshold = %d\n", rc); + (void)pcre_config(PCRE_CONFIG_MATCH_LIMIT, &rc); + printf(" Default match limit = %d\n", rc); + (void)pcre_config(PCRE_CONFIG_MATCH_LIMIT_RECURSION, &rc); + printf(" Default recursion depth limit = %d\n", rc); + (void)pcre_config(PCRE_CONFIG_STACKRECURSE, &rc); + printf(" Match recursion uses %s\n", rc? "stack" : "heap"); + goto EXIT; + } + else if (strcmp(argv[op], "-help") == 0 || + strcmp(argv[op], "--help") == 0) + { + usage(); + goto EXIT; + } + else + { + printf("** Unknown or malformed option %s\n", argv[op]); + usage(); + yield = 1; + goto EXIT; + } + op++; + argc--; + } + +/* Get the store for the offsets vector, and remember what it was */ + +size_offsets_max = size_offsets; +offsets = (int *)malloc(size_offsets_max * sizeof(int)); +if (offsets == NULL) + { + printf("** Failed to get %d bytes of memory for offsets vector\n", + (int)(size_offsets_max * sizeof(int))); + yield = 1; + goto EXIT; + } + +/* Sort out the input and output files */ + +if (argc > 1) + { + infile = fopen(argv[op], INPUT_MODE); + if (infile == NULL) + { + printf("** Failed to open %s\n", argv[op]); + yield = 1; + goto EXIT; + } + } + +if (argc > 2) + { + outfile = fopen(argv[op+1], OUTPUT_MODE); + if (outfile == NULL) + { + printf("** Failed to open %s\n", argv[op+1]); + yield = 1; + goto EXIT; + } + } + +/* Set alternative malloc function */ + +pcre_malloc = new_malloc; +pcre_free = new_free; +pcre_stack_malloc = stack_malloc; +pcre_stack_free = stack_free; + +/* Heading line unless quiet, then prompt for first regex if stdin */ + +if (!quiet) fprintf(outfile, "PCRE version %s\n\n", pcre_version()); + +/* Main loop */ + +while (!done) + { + pcre *re = NULL; + pcre_extra *extra = NULL; + +#if !defined NOPOSIX /* There are still compilers that require no indent */ + regex_t preg; + int do_posix = 0; +#endif + + const char *error; + unsigned char *p, *pp, *ppp; + unsigned char *to_file = NULL; + const unsigned char *tables = NULL; + unsigned long int true_size, true_study_size = 0; + size_t size, regex_gotten_store; + int do_study = 0; + int do_debug = debug; + int do_G = 0; + int do_g = 0; + int do_showinfo = showinfo; + int do_showrest = 0; + int do_flip = 0; + int erroroffset, len, delimiter, poffset; + + use_utf8 = 0; + debug_lengths = 1; + + if (infile == stdin) printf(" re> "); + if (extend_inputline(infile, buffer) == NULL) break; + if (infile != stdin) fprintf(outfile, "%s", (char *)buffer); + fflush(outfile); + + p = buffer; + while (isspace(*p)) p++; + if (*p == 0) continue; + + /* See if the pattern is to be loaded pre-compiled from a file. */ + + if (*p == '<' && strchr((char *)(p+1), '<') == NULL) + { + unsigned long int magic, get_options; + uschar sbuf[8]; + FILE *f; + + p++; + pp = p + (int)strlen((char *)p); + while (isspace(pp[-1])) pp--; + *pp = 0; + + f = fopen((char *)p, "rb"); + if (f == NULL) + { + fprintf(outfile, "Failed to open %s: %s\n", p, strerror(errno)); + continue; + } + + if (fread(sbuf, 1, 8, f) != 8) goto FAIL_READ; + + true_size = + (sbuf[0] << 24) | (sbuf[1] << 16) | (sbuf[2] << 8) | sbuf[3]; + true_study_size = + (sbuf[4] << 24) | (sbuf[5] << 16) | (sbuf[6] << 8) | sbuf[7]; + + re = (real_pcre *)new_malloc(true_size); + regex_gotten_store = gotten_store; + + if (fread(re, 1, true_size, f) != true_size) goto FAIL_READ; + + magic = ((real_pcre *)re)->magic_number; + if (magic != MAGIC_NUMBER) + { + if (byteflip(magic, sizeof(magic)) == MAGIC_NUMBER) + { + do_flip = 1; + } + else + { + fprintf(outfile, "Data in %s is not a compiled PCRE regex\n", p); + fclose(f); + continue; + } + } + + fprintf(outfile, "Compiled regex%s loaded from %s\n", + do_flip? " (byte-inverted)" : "", p); + + /* Need to know if UTF-8 for printing data strings */ + + new_info(re, NULL, PCRE_INFO_OPTIONS, &get_options); + use_utf8 = (get_options & PCRE_UTF8) != 0; + + /* Now see if there is any following study data */ + + if (true_study_size != 0) + { + pcre_study_data *psd; + + extra = (pcre_extra *)new_malloc(sizeof(pcre_extra) + true_study_size); + extra->flags = PCRE_EXTRA_STUDY_DATA; + + psd = (pcre_study_data *)(((char *)extra) + sizeof(pcre_extra)); + extra->study_data = psd; + + if (fread(psd, 1, true_study_size, f) != true_study_size) + { + FAIL_READ: + fprintf(outfile, "Failed to read data from %s\n", p); + if (extra != NULL) new_free(extra); + if (re != NULL) new_free(re); + fclose(f); + continue; + } + fprintf(outfile, "Study data loaded from %s\n", p); + do_study = 1; /* To get the data output if requested */ + } + else fprintf(outfile, "No study data\n"); + + fclose(f); + goto SHOW_INFO; + } + + /* In-line pattern (the usual case). Get the delimiter and seek the end of + the pattern; if is isn't complete, read more. */ + + delimiter = *p++; + + if (isalnum(delimiter) || delimiter == '\\') + { + fprintf(outfile, "** Delimiter must not be alphameric or \\\n"); + goto SKIP_DATA; + } + + pp = p; + poffset = p - buffer; + + for(;;) + { + while (*pp != 0) + { + if (*pp == '\\' && pp[1] != 0) pp++; + else if (*pp == delimiter) break; + pp++; + } + if (*pp != 0) break; + if (infile == stdin) printf(" > "); + if ((pp = extend_inputline(infile, pp)) == NULL) + { + fprintf(outfile, "** Unexpected EOF\n"); + done = 1; + goto CONTINUE; + } + if (infile != stdin) fprintf(outfile, "%s", (char *)pp); + } + + /* The buffer may have moved while being extended; reset the start of data + pointer to the correct relative point in the buffer. */ + + p = buffer + poffset; + + /* If the first character after the delimiter is backslash, make + the pattern end with backslash. This is purely to provide a way + of testing for the error message when a pattern ends with backslash. */ + + if (pp[1] == '\\') *pp++ = '\\'; + + /* Terminate the pattern at the delimiter, and save a copy of the pattern + for callouts. */ + + *pp++ = 0; + strcpy((char *)pbuffer, (char *)p); + + /* Look for options after final delimiter */ + + options = 0; + study_options = 0; + log_store = showstore; /* default from command line */ + + while (*pp != 0) + { + switch (*pp++) + { + case 'f': options |= PCRE_FIRSTLINE; break; + case 'g': do_g = 1; break; + case 'i': options |= PCRE_CASELESS; break; + case 'm': options |= PCRE_MULTILINE; break; + case 's': options |= PCRE_DOTALL; break; + case 'x': options |= PCRE_EXTENDED; break; + + case '+': do_showrest = 1; break; + case 'A': options |= PCRE_ANCHORED; break; + case 'B': do_debug = 1; break; + case 'C': options |= PCRE_AUTO_CALLOUT; break; + case 'D': do_debug = do_showinfo = 1; break; + case 'E': options |= PCRE_DOLLAR_ENDONLY; break; + case 'F': do_flip = 1; break; + case 'G': do_G = 1; break; + case 'I': do_showinfo = 1; break; + case 'J': options |= PCRE_DUPNAMES; break; + case 'M': log_store = 1; break; + case 'N': options |= PCRE_NO_AUTO_CAPTURE; break; + +#if !defined NOPOSIX + case 'P': do_posix = 1; break; +#endif + + case 'S': do_study = 1; break; + case 'U': options |= PCRE_UNGREEDY; break; + case 'X': options |= PCRE_EXTRA; break; + case 'Z': debug_lengths = 0; break; + case '8': options |= PCRE_UTF8; use_utf8 = 1; break; + case '?': options |= PCRE_NO_UTF8_CHECK; break; + + case 'L': + ppp = pp; + /* The '\r' test here is so that it works on Windows. */ + /* The '0' test is just in case this is an unterminated line. */ + while (*ppp != 0 && *ppp != '\n' && *ppp != '\r' && *ppp != ' ') ppp++; + *ppp = 0; + if (setlocale(LC_CTYPE, (const char *)pp) == NULL) + { + fprintf(outfile, "** Failed to set locale \"%s\"\n", pp); + goto SKIP_DATA; + } + locale_set = 1; + tables = pcre_maketables(); + pp = ppp; + break; + + case '>': + to_file = pp; + while (*pp != 0) pp++; + while (isspace(pp[-1])) pp--; + *pp = 0; + break; + + case '<': + { + int x = check_newline(pp, outfile); + if (x == 0) goto SKIP_DATA; + options |= x; + while (*pp++ != '>'); + } + break; + + case '\r': /* So that it works in Windows */ + case '\n': + case ' ': + break; + + default: + fprintf(outfile, "** Unknown option '%c'\n", pp[-1]); + goto SKIP_DATA; + } + } + + /* Handle compiling via the POSIX interface, which doesn't support the + timing, showing, or debugging options, nor the ability to pass over + local character tables. */ + +#if !defined NOPOSIX + if (posix || do_posix) + { + int rc; + int cflags = 0; + + if ((options & PCRE_CASELESS) != 0) cflags |= REG_ICASE; + if ((options & PCRE_MULTILINE) != 0) cflags |= REG_NEWLINE; + if ((options & PCRE_DOTALL) != 0) cflags |= REG_DOTALL; + if ((options & PCRE_NO_AUTO_CAPTURE) != 0) cflags |= REG_NOSUB; + if ((options & PCRE_UTF8) != 0) cflags |= REG_UTF8; + + rc = regcomp(&preg, (char *)p, cflags); + + /* Compilation failed; go back for another re, skipping to blank line + if non-interactive. */ + + if (rc != 0) + { + (void)regerror(rc, &preg, (char *)buffer, buffer_size); + fprintf(outfile, "Failed: POSIX code %d: %s\n", rc, buffer); + goto SKIP_DATA; + } + } + + /* Handle compiling via the native interface */ + + else +#endif /* !defined NOPOSIX */ + + { + if (timeit > 0) + { + register int i; + clock_t time_taken; + clock_t start_time = clock(); + for (i = 0; i < timeit; i++) + { + re = pcre_compile((char *)p, options, &error, &erroroffset, tables); + if (re != NULL) free(re); + } + time_taken = clock() - start_time; + fprintf(outfile, "Compile time %.4f milliseconds\n", + (((double)time_taken * 1000.0) / (double)timeit) / + (double)CLOCKS_PER_SEC); + } + + re = pcre_compile((char *)p, options, &error, &erroroffset, tables); + + /* Compilation failed; go back for another re, skipping to blank line + if non-interactive. */ + + if (re == NULL) + { + fprintf(outfile, "Failed: %s at offset %d\n", error, erroroffset); + SKIP_DATA: + if (infile != stdin) + { + for (;;) + { + if (extend_inputline(infile, buffer) == NULL) + { + done = 1; + goto CONTINUE; + } + len = (int)strlen((char *)buffer); + while (len > 0 && isspace(buffer[len-1])) len--; + if (len == 0) break; + } + fprintf(outfile, "\n"); + } + goto CONTINUE; + } + + /* Compilation succeeded; print data if required. There are now two + info-returning functions. The old one has a limited interface and + returns only limited data. Check that it agrees with the newer one. */ + + if (log_store) + fprintf(outfile, "Memory allocation (code space): %d\n", + (int)(gotten_store - + sizeof(real_pcre) - + ((real_pcre *)re)->name_count * ((real_pcre *)re)->name_entry_size)); + + /* Extract the size for possible writing before possibly flipping it, + and remember the store that was got. */ + + true_size = ((real_pcre *)re)->size; + regex_gotten_store = gotten_store; + + /* If /S was present, study the regexp to generate additional info to + help with the matching. */ + + if (do_study) + { + if (timeit > 0) + { + register int i; + clock_t time_taken; + clock_t start_time = clock(); + for (i = 0; i < timeit; i++) + extra = pcre_study(re, study_options, &error); + time_taken = clock() - start_time; + if (extra != NULL) free(extra); + fprintf(outfile, " Study time %.4f milliseconds\n", + (((double)time_taken * 1000.0) / (double)timeit) / + (double)CLOCKS_PER_SEC); + } + extra = pcre_study(re, study_options, &error); + if (error != NULL) + fprintf(outfile, "Failed to study: %s\n", error); + else if (extra != NULL) + true_study_size = ((pcre_study_data *)(extra->study_data))->size; + } + + /* If the 'F' option was present, we flip the bytes of all the integer + fields in the regex data block and the study block. This is to make it + possible to test PCRE's handling of byte-flipped patterns, e.g. those + compiled on a different architecture. */ + + if (do_flip) + { + real_pcre *rre = (real_pcre *)re; + rre->magic_number = + byteflip(rre->magic_number, sizeof(rre->magic_number)); + rre->size = byteflip(rre->size, sizeof(rre->size)); + rre->options = byteflip(rre->options, sizeof(rre->options)); + rre->flags = (pcre_uint16)byteflip(rre->flags, sizeof(rre->flags)); + rre->top_bracket = + (pcre_uint16)byteflip(rre->top_bracket, sizeof(rre->top_bracket)); + rre->top_backref = + (pcre_uint16)byteflip(rre->top_backref, sizeof(rre->top_backref)); + rre->first_byte = + (pcre_uint16)byteflip(rre->first_byte, sizeof(rre->first_byte)); + rre->req_byte = + (pcre_uint16)byteflip(rre->req_byte, sizeof(rre->req_byte)); + rre->name_table_offset = (pcre_uint16)byteflip(rre->name_table_offset, + sizeof(rre->name_table_offset)); + rre->name_entry_size = (pcre_uint16)byteflip(rre->name_entry_size, + sizeof(rre->name_entry_size)); + rre->name_count = (pcre_uint16)byteflip(rre->name_count, + sizeof(rre->name_count)); + + if (extra != NULL) + { + pcre_study_data *rsd = (pcre_study_data *)(extra->study_data); + rsd->size = byteflip(rsd->size, sizeof(rsd->size)); + rsd->options = byteflip(rsd->options, sizeof(rsd->options)); + } + } + + /* Extract information from the compiled data if required */ + + SHOW_INFO: + + if (do_debug) + { + fprintf(outfile, "------------------------------------------------------------------\n"); + pcre_printint(re, outfile, debug_lengths); + } + + if (do_showinfo) + { + unsigned long int get_options, all_options; +#if !defined NOINFOCHECK + int old_first_char, old_options, old_count; +#endif + int count, backrefmax, first_char, need_char, okpartial, jchanged, + hascrorlf; + int nameentrysize, namecount; + const uschar *nametable; + + new_info(re, NULL, PCRE_INFO_OPTIONS, &get_options); + new_info(re, NULL, PCRE_INFO_SIZE, &size); + new_info(re, NULL, PCRE_INFO_CAPTURECOUNT, &count); + new_info(re, NULL, PCRE_INFO_BACKREFMAX, &backrefmax); + new_info(re, NULL, PCRE_INFO_FIRSTBYTE, &first_char); + new_info(re, NULL, PCRE_INFO_LASTLITERAL, &need_char); + new_info(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); + new_info(re, NULL, PCRE_INFO_NAMECOUNT, &namecount); + new_info(re, NULL, PCRE_INFO_NAMETABLE, (void *)&nametable); + new_info(re, NULL, PCRE_INFO_OKPARTIAL, &okpartial); + new_info(re, NULL, PCRE_INFO_JCHANGED, &jchanged); + new_info(re, NULL, PCRE_INFO_HASCRORLF, &hascrorlf); + +#if !defined NOINFOCHECK + old_count = pcre_info(re, &old_options, &old_first_char); + if (count < 0) fprintf(outfile, + "Error %d from pcre_info()\n", count); + else + { + if (old_count != count) fprintf(outfile, + "Count disagreement: pcre_fullinfo=%d pcre_info=%d\n", count, + old_count); + + if (old_first_char != first_char) fprintf(outfile, + "First char disagreement: pcre_fullinfo=%d pcre_info=%d\n", + first_char, old_first_char); + + if (old_options != (int)get_options) fprintf(outfile, + "Options disagreement: pcre_fullinfo=%ld pcre_info=%d\n", + get_options, old_options); + } +#endif + + if (size != regex_gotten_store) fprintf(outfile, + "Size disagreement: pcre_fullinfo=%d call to malloc for %d\n", + (int)size, (int)regex_gotten_store); + + fprintf(outfile, "Capturing subpattern count = %d\n", count); + if (backrefmax > 0) + fprintf(outfile, "Max back reference = %d\n", backrefmax); + + if (namecount > 0) + { + fprintf(outfile, "Named capturing subpatterns:\n"); + while (namecount-- > 0) + { + fprintf(outfile, " %s %*s%3d\n", nametable + 2, + nameentrysize - 3 - (int)strlen((char *)nametable + 2), "", + GET2(nametable, 0)); + nametable += nameentrysize; + } + } + + if (!okpartial) fprintf(outfile, "Partial matching not supported\n"); + if (hascrorlf) fprintf(outfile, "Contains explicit CR or LF match\n"); + + all_options = ((real_pcre *)re)->options; + if (do_flip) all_options = byteflip(all_options, sizeof(all_options)); + + if (get_options == 0) fprintf(outfile, "No options\n"); + else fprintf(outfile, "Options:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + ((get_options & PCRE_ANCHORED) != 0)? " anchored" : "", + ((get_options & PCRE_CASELESS) != 0)? " caseless" : "", + ((get_options & PCRE_EXTENDED) != 0)? " extended" : "", + ((get_options & PCRE_MULTILINE) != 0)? " multiline" : "", + ((get_options & PCRE_FIRSTLINE) != 0)? " firstline" : "", + ((get_options & PCRE_DOTALL) != 0)? " dotall" : "", + ((get_options & PCRE_BSR_ANYCRLF) != 0)? " bsr_anycrlf" : "", + ((get_options & PCRE_BSR_UNICODE) != 0)? " bsr_unicode" : "", + ((get_options & PCRE_DOLLAR_ENDONLY) != 0)? " dollar_endonly" : "", + ((get_options & PCRE_EXTRA) != 0)? " extra" : "", + ((get_options & PCRE_UNGREEDY) != 0)? " ungreedy" : "", + ((get_options & PCRE_NO_AUTO_CAPTURE) != 0)? " no_auto_capture" : "", + ((get_options & PCRE_UTF8) != 0)? " utf8" : "", + ((get_options & PCRE_NO_UTF8_CHECK) != 0)? " no_utf8_check" : "", + ((get_options & PCRE_DUPNAMES) != 0)? " dupnames" : ""); + + if (jchanged) fprintf(outfile, "Duplicate name status changes\n"); + + switch (get_options & PCRE_NEWLINE_BITS) + { + case PCRE_NEWLINE_CR: + fprintf(outfile, "Forced newline sequence: CR\n"); + break; + + case PCRE_NEWLINE_LF: + fprintf(outfile, "Forced newline sequence: LF\n"); + break; + + case PCRE_NEWLINE_CRLF: + fprintf(outfile, "Forced newline sequence: CRLF\n"); + break; + + case PCRE_NEWLINE_ANYCRLF: + fprintf(outfile, "Forced newline sequence: ANYCRLF\n"); + break; + + case PCRE_NEWLINE_ANY: + fprintf(outfile, "Forced newline sequence: ANY\n"); + break; + + default: + break; + } + + if (first_char == -1) + { + fprintf(outfile, "First char at start or follows newline\n"); + } + else if (first_char < 0) + { + fprintf(outfile, "No first char\n"); + } + else + { + int ch = first_char & 255; + const char *caseless = ((first_char & REQ_CASELESS) == 0)? + "" : " (caseless)"; + if (PRINTHEX(ch)) + fprintf(outfile, "First char = \'%c\'%s\n", ch, caseless); + else + fprintf(outfile, "First char = %d%s\n", ch, caseless); + } + + if (need_char < 0) + { + fprintf(outfile, "No need char\n"); + } + else + { + int ch = need_char & 255; + const char *caseless = ((need_char & REQ_CASELESS) == 0)? + "" : " (caseless)"; + if (PRINTHEX(ch)) + fprintf(outfile, "Need char = \'%c\'%s\n", ch, caseless); + else + fprintf(outfile, "Need char = %d%s\n", ch, caseless); + } + + /* Don't output study size; at present it is in any case a fixed + value, but it varies, depending on the computer architecture, and + so messes up the test suite. (And with the /F option, it might be + flipped.) */ + + if (do_study) + { + if (extra == NULL) + fprintf(outfile, "Study returned NULL\n"); + else + { + uschar *start_bits = NULL; + new_info(re, extra, PCRE_INFO_FIRSTTABLE, &start_bits); + + if (start_bits == NULL) + fprintf(outfile, "No starting byte set\n"); + else + { + int i; + int c = 24; + fprintf(outfile, "Starting byte set: "); + for (i = 0; i < 256; i++) + { + if ((start_bits[i/8] & (1<<(i&7))) != 0) + { + if (c > 75) + { + fprintf(outfile, "\n "); + c = 2; + } + if (PRINTHEX(i) && i != ' ') + { + fprintf(outfile, "%c ", i); + c += 2; + } + else + { + fprintf(outfile, "\\x%02x ", i); + c += 5; + } + } + } + fprintf(outfile, "\n"); + } + } + } + } + + /* If the '>' option was present, we write out the regex to a file, and + that is all. The first 8 bytes of the file are the regex length and then + the study length, in big-endian order. */ + + if (to_file != NULL) + { + FILE *f = fopen((char *)to_file, "wb"); + if (f == NULL) + { + fprintf(outfile, "Unable to open %s: %s\n", to_file, strerror(errno)); + } + else + { + uschar sbuf[8]; + sbuf[0] = (uschar)((true_size >> 24) & 255); + sbuf[1] = (uschar)((true_size >> 16) & 255); + sbuf[2] = (uschar)((true_size >> 8) & 255); + sbuf[3] = (uschar)((true_size) & 255); + + sbuf[4] = (uschar)((true_study_size >> 24) & 255); + sbuf[5] = (uschar)((true_study_size >> 16) & 255); + sbuf[6] = (uschar)((true_study_size >> 8) & 255); + sbuf[7] = (uschar)((true_study_size) & 255); + + if (fwrite(sbuf, 1, 8, f) < 8 || + fwrite(re, 1, true_size, f) < true_size) + { + fprintf(outfile, "Write error on %s: %s\n", to_file, strerror(errno)); + } + else + { + fprintf(outfile, "Compiled regex written to %s\n", to_file); + if (extra != NULL) + { + if (fwrite(extra->study_data, 1, true_study_size, f) < + true_study_size) + { + fprintf(outfile, "Write error on %s: %s\n", to_file, + strerror(errno)); + } + else fprintf(outfile, "Study data written to %s\n", to_file); + + } + } + fclose(f); + } + + new_free(re); + if (extra != NULL) new_free(extra); + if (tables != NULL) new_free((void *)tables); + continue; /* With next regex */ + } + } /* End of non-POSIX compile */ + + /* Read data lines and test them */ + + for (;;) + { + uschar *q; + uschar *bptr; + int *use_offsets = offsets; + int use_size_offsets = size_offsets; + int callout_data = 0; + int callout_data_set = 0; + int count, c; + int copystrings = 0; + int find_match_limit = 0; + int getstrings = 0; + int getlist = 0; + int gmatched = 0; + int start_offset = 0; + int g_notempty = 0; + int use_dfa = 0; + + options = 0; + + *copynames = 0; + *getnames = 0; + + copynamesptr = copynames; + getnamesptr = getnames; + + pcre_callout = callout; + first_callout = 1; + callout_extra = 0; + callout_count = 0; + callout_fail_count = 999999; + callout_fail_id = -1; + show_malloc = 0; + + if (extra != NULL) extra->flags &= + ~(PCRE_EXTRA_MATCH_LIMIT|PCRE_EXTRA_MATCH_LIMIT_RECURSION); + + len = 0; + for (;;) + { + if (infile == stdin) printf("data> "); + if (extend_inputline(infile, buffer + len) == NULL) + { + if (len > 0) break; + done = 1; + goto CONTINUE; + } + if (infile != stdin) fprintf(outfile, "%s", (char *)buffer); + len = (int)strlen((char *)buffer); + if (buffer[len-1] == '\n') break; + } + + while (len > 0 && isspace(buffer[len-1])) len--; + buffer[len] = 0; + if (len == 0) break; + + p = buffer; + while (isspace(*p)) p++; + + bptr = q = dbuffer; + while ((c = *p++) != 0) + { + int i = 0; + int n = 0; + + if (c == '\\') switch ((c = *p++)) + { + case 'a': c = 7; break; + case 'b': c = '\b'; break; + case 'e': c = 27; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + while (i++ < 2 && isdigit(*p) && *p != '8' && *p != '9') + c = c * 8 + *p++ - '0'; + +#if !defined NOUTF8 + if (use_utf8 && c > 255) + { + unsigned char buff8[8]; + int ii, utn; + utn = ord2utf8(c, buff8); + for (ii = 0; ii < utn - 1; ii++) *q++ = buff8[ii]; + c = buff8[ii]; /* Last byte */ + } +#endif + break; + + case 'x': + + /* Handle \x{..} specially - new Perl thing for utf8 */ + +#if !defined NOUTF8 + if (*p == '{') + { + unsigned char *pt = p; + c = 0; + while (isxdigit(*(++pt))) + c = c * 16 + tolower(*pt) - ((isdigit(*pt))? '0' : 'W'); + if (*pt == '}') + { + unsigned char buff8[8]; + int ii, utn; + utn = ord2utf8(c, buff8); + for (ii = 0; ii < utn - 1; ii++) *q++ = buff8[ii]; + c = buff8[ii]; /* Last byte */ + p = pt + 1; + break; + } + /* Not correct form; fall through */ + } +#endif + + /* Ordinary \x */ + + c = 0; + while (i++ < 2 && isxdigit(*p)) + { + c = c * 16 + tolower(*p) - ((isdigit(*p))? '0' : 'W'); + p++; + } + break; + + case 0: /* \ followed by EOF allows for an empty line */ + p--; + continue; + + case '>': + while(isdigit(*p)) start_offset = start_offset * 10 + *p++ - '0'; + continue; + + case 'A': /* Option setting */ + options |= PCRE_ANCHORED; + continue; + + case 'B': + options |= PCRE_NOTBOL; + continue; + + case 'C': + if (isdigit(*p)) /* Set copy string */ + { + while(isdigit(*p)) n = n * 10 + *p++ - '0'; + copystrings |= 1 << n; + } + else if (isalnum(*p)) + { + uschar *npp = copynamesptr; + while (isalnum(*p)) *npp++ = *p++; + *npp++ = 0; + *npp = 0; + n = pcre_get_stringnumber(re, (char *)copynamesptr); + if (n < 0) + fprintf(outfile, "no parentheses with name \"%s\"\n", copynamesptr); + copynamesptr = npp; + } + else if (*p == '+') + { + callout_extra = 1; + p++; + } + else if (*p == '-') + { + pcre_callout = NULL; + p++; + } + else if (*p == '!') + { + callout_fail_id = 0; + p++; + while(isdigit(*p)) + callout_fail_id = callout_fail_id * 10 + *p++ - '0'; + callout_fail_count = 0; + if (*p == '!') + { + p++; + while(isdigit(*p)) + callout_fail_count = callout_fail_count * 10 + *p++ - '0'; + } + } + else if (*p == '*') + { + int sign = 1; + callout_data = 0; + if (*(++p) == '-') { sign = -1; p++; } + while(isdigit(*p)) + callout_data = callout_data * 10 + *p++ - '0'; + callout_data *= sign; + callout_data_set = 1; + } + continue; + +#if !defined NODFA + case 'D': +#if !defined NOPOSIX + if (posix || do_posix) + printf("** Can't use dfa matching in POSIX mode: \\D ignored\n"); + else +#endif + use_dfa = 1; + continue; + + case 'F': + options |= PCRE_DFA_SHORTEST; + continue; +#endif + + case 'G': + if (isdigit(*p)) + { + while(isdigit(*p)) n = n * 10 + *p++ - '0'; + getstrings |= 1 << n; + } + else if (isalnum(*p)) + { + uschar *npp = getnamesptr; + while (isalnum(*p)) *npp++ = *p++; + *npp++ = 0; + *npp = 0; + n = pcre_get_stringnumber(re, (char *)getnamesptr); + if (n < 0) + fprintf(outfile, "no parentheses with name \"%s\"\n", getnamesptr); + getnamesptr = npp; + } + continue; + + case 'L': + getlist = 1; + continue; + + case 'M': + find_match_limit = 1; + continue; + + case 'N': + options |= PCRE_NOTEMPTY; + continue; + + case 'O': + while(isdigit(*p)) n = n * 10 + *p++ - '0'; + if (n > size_offsets_max) + { + size_offsets_max = n; + free(offsets); + use_offsets = offsets = (int *)malloc(size_offsets_max * sizeof(int)); + if (offsets == NULL) + { + printf("** Failed to get %d bytes of memory for offsets vector\n", + (int)(size_offsets_max * sizeof(int))); + yield = 1; + goto EXIT; + } + } + use_size_offsets = n; + if (n == 0) use_offsets = NULL; /* Ensures it can't write to it */ + continue; + + case 'P': + options |= PCRE_PARTIAL; + continue; + + case 'Q': + while(isdigit(*p)) n = n * 10 + *p++ - '0'; + if (extra == NULL) + { + extra = (pcre_extra *)malloc(sizeof(pcre_extra)); + extra->flags = 0; + } + extra->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; + extra->match_limit_recursion = n; + continue; + + case 'q': + while(isdigit(*p)) n = n * 10 + *p++ - '0'; + if (extra == NULL) + { + extra = (pcre_extra *)malloc(sizeof(pcre_extra)); + extra->flags = 0; + } + extra->flags |= PCRE_EXTRA_MATCH_LIMIT; + extra->match_limit = n; + continue; + +#if !defined NODFA + case 'R': + options |= PCRE_DFA_RESTART; + continue; +#endif + + case 'S': + show_malloc = 1; + continue; + + case 'Z': + options |= PCRE_NOTEOL; + continue; + + case '?': + options |= PCRE_NO_UTF8_CHECK; + continue; + + case '<': + { + int x = check_newline(p, outfile); + if (x == 0) goto NEXT_DATA; + options |= x; + while (*p++ != '>'); + } + continue; + } + *q++ = c; + } + *q = 0; + len = q - dbuffer; + + if ((all_use_dfa || use_dfa) && find_match_limit) + { + printf("**Match limit not relevant for DFA matching: ignored\n"); + find_match_limit = 0; + } + + /* Handle matching via the POSIX interface, which does not + support timing or playing with the match limit or callout data. */ + +#if !defined NOPOSIX + if (posix || do_posix) + { + int rc; + int eflags = 0; + regmatch_t *pmatch = NULL; + if (use_size_offsets > 0) + pmatch = (regmatch_t *)malloc(sizeof(regmatch_t) * use_size_offsets); + if ((options & PCRE_NOTBOL) != 0) eflags |= REG_NOTBOL; + if ((options & PCRE_NOTEOL) != 0) eflags |= REG_NOTEOL; + + rc = regexec(&preg, (const char *)bptr, use_size_offsets, pmatch, eflags); + + if (rc != 0) + { + (void)regerror(rc, &preg, (char *)buffer, buffer_size); + fprintf(outfile, "No match: POSIX code %d: %s\n", rc, buffer); + } + else if ((((const pcre *)preg.re_pcre)->options & PCRE_NO_AUTO_CAPTURE) + != 0) + { + fprintf(outfile, "Matched with REG_NOSUB\n"); + } + else + { + size_t i; + for (i = 0; i < (size_t)use_size_offsets; i++) + { + if (pmatch[i].rm_so >= 0) + { + fprintf(outfile, "%2d: ", (int)i); + (void)pchars(dbuffer + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so, outfile); + fprintf(outfile, "\n"); + if (i == 0 && do_showrest) + { + fprintf(outfile, " 0+ "); + (void)pchars(dbuffer + pmatch[i].rm_eo, len - pmatch[i].rm_eo, + outfile); + fprintf(outfile, "\n"); + } + } + } + } + free(pmatch); + } + + /* Handle matching via the native interface - repeats for /g and /G */ + + else +#endif /* !defined NOPOSIX */ + + for (;; gmatched++) /* Loop for /g or /G */ + { + if (timeitm > 0) + { + register int i; + clock_t time_taken; + clock_t start_time = clock(); + +#if !defined NODFA + if (all_use_dfa || use_dfa) + { + int workspace[1000]; + for (i = 0; i < timeitm; i++) + count = pcre_dfa_exec(re, NULL, (char *)bptr, len, start_offset, + options | g_notempty, use_offsets, use_size_offsets, workspace, + sizeof(workspace)/sizeof(int)); + } + else +#endif + + for (i = 0; i < timeitm; i++) + count = pcre_exec(re, extra, (char *)bptr, len, + start_offset, options | g_notempty, use_offsets, use_size_offsets); + + time_taken = clock() - start_time; + fprintf(outfile, "Execute time %.4f milliseconds\n", + (((double)time_taken * 1000.0) / (double)timeitm) / + (double)CLOCKS_PER_SEC); + } + + /* If find_match_limit is set, we want to do repeated matches with + varying limits in order to find the minimum value for the match limit and + for the recursion limit. */ + + if (find_match_limit) + { + if (extra == NULL) + { + extra = (pcre_extra *)malloc(sizeof(pcre_extra)); + extra->flags = 0; + } + + (void)check_match_limit(re, extra, bptr, len, start_offset, + options|g_notempty, use_offsets, use_size_offsets, + PCRE_EXTRA_MATCH_LIMIT, &(extra->match_limit), + PCRE_ERROR_MATCHLIMIT, "match()"); + + count = check_match_limit(re, extra, bptr, len, start_offset, + options|g_notempty, use_offsets, use_size_offsets, + PCRE_EXTRA_MATCH_LIMIT_RECURSION, &(extra->match_limit_recursion), + PCRE_ERROR_RECURSIONLIMIT, "match() recursion"); + } + + /* If callout_data is set, use the interface with additional data */ + + else if (callout_data_set) + { + if (extra == NULL) + { + extra = (pcre_extra *)malloc(sizeof(pcre_extra)); + extra->flags = 0; + } + extra->flags |= PCRE_EXTRA_CALLOUT_DATA; + extra->callout_data = &callout_data; + count = pcre_exec(re, extra, (char *)bptr, len, start_offset, + options | g_notempty, use_offsets, use_size_offsets); + extra->flags &= ~PCRE_EXTRA_CALLOUT_DATA; + } + + /* The normal case is just to do the match once, with the default + value of match_limit. */ + +#if !defined NODFA + else if (all_use_dfa || use_dfa) + { + int workspace[1000]; + count = pcre_dfa_exec(re, NULL, (char *)bptr, len, start_offset, + options | g_notempty, use_offsets, use_size_offsets, workspace, + sizeof(workspace)/sizeof(int)); + if (count == 0) + { + fprintf(outfile, "Matched, but too many subsidiary matches\n"); + count = use_size_offsets/2; + } + } +#endif + + else + { + count = pcre_exec(re, extra, (char *)bptr, len, + start_offset, options | g_notempty, use_offsets, use_size_offsets); + if (count == 0) + { + fprintf(outfile, "Matched, but too many substrings\n"); + count = use_size_offsets/3; + } + } + + /* Matched */ + + if (count >= 0) + { + int i, maxcount; + +#if !defined NODFA + if (all_use_dfa || use_dfa) maxcount = use_size_offsets/2; else +#endif + maxcount = use_size_offsets/3; + + /* This is a check against a lunatic return value. */ + + if (count > maxcount) + { + fprintf(outfile, + "** PCRE error: returned count %d is too big for offset size %d\n", + count, use_size_offsets); + count = use_size_offsets/3; + if (do_g || do_G) + { + fprintf(outfile, "** /%c loop abandoned\n", do_g? 'g' : 'G'); + do_g = do_G = FALSE; /* Break g/G loop */ + } + } + + for (i = 0; i < count * 2; i += 2) + { + if (use_offsets[i] < 0) + fprintf(outfile, "%2d: <unset>\n", i/2); + else + { + fprintf(outfile, "%2d: ", i/2); + (void)pchars(bptr + use_offsets[i], + use_offsets[i+1] - use_offsets[i], outfile); + fprintf(outfile, "\n"); + if (i == 0) + { + if (do_showrest) + { + fprintf(outfile, " 0+ "); + (void)pchars(bptr + use_offsets[i+1], len - use_offsets[i+1], + outfile); + fprintf(outfile, "\n"); + } + } + } + } + + for (i = 0; i < 32; i++) + { + if ((copystrings & (1 << i)) != 0) + { + char copybuffer[256]; + int rc = pcre_copy_substring((char *)bptr, use_offsets, count, + i, copybuffer, sizeof(copybuffer)); + if (rc < 0) + fprintf(outfile, "copy substring %d failed %d\n", i, rc); + else + fprintf(outfile, "%2dC %s (%d)\n", i, copybuffer, rc); + } + } + + for (copynamesptr = copynames; + *copynamesptr != 0; + copynamesptr += (int)strlen((char*)copynamesptr) + 1) + { + char copybuffer[256]; + int rc = pcre_copy_named_substring(re, (char *)bptr, use_offsets, + count, (char *)copynamesptr, copybuffer, sizeof(copybuffer)); + if (rc < 0) + fprintf(outfile, "copy substring %s failed %d\n", copynamesptr, rc); + else + fprintf(outfile, " C %s (%d) %s\n", copybuffer, rc, copynamesptr); + } + + for (i = 0; i < 32; i++) + { + if ((getstrings & (1 << i)) != 0) + { + const char *substring; + int rc = pcre_get_substring((char *)bptr, use_offsets, count, + i, &substring); + if (rc < 0) + fprintf(outfile, "get substring %d failed %d\n", i, rc); + else + { + fprintf(outfile, "%2dG %s (%d)\n", i, substring, rc); + pcre_free_substring(substring); + } + } + } + + for (getnamesptr = getnames; + *getnamesptr != 0; + getnamesptr += (int)strlen((char*)getnamesptr) + 1) + { + const char *substring; + int rc = pcre_get_named_substring(re, (char *)bptr, use_offsets, + count, (char *)getnamesptr, &substring); + if (rc < 0) + fprintf(outfile, "copy substring %s failed %d\n", getnamesptr, rc); + else + { + fprintf(outfile, " G %s (%d) %s\n", substring, rc, getnamesptr); + pcre_free_substring(substring); + } + } + + if (getlist) + { + const char **stringlist; + int rc = pcre_get_substring_list((char *)bptr, use_offsets, count, + &stringlist); + if (rc < 0) + fprintf(outfile, "get substring list failed %d\n", rc); + else + { + for (i = 0; i < count; i++) + fprintf(outfile, "%2dL %s\n", i, stringlist[i]); + if (stringlist[i] != NULL) + fprintf(outfile, "string list not terminated by NULL\n"); + /* free((void *)stringlist); */ + pcre_free_substring_list(stringlist); + } + } + } + + /* There was a partial match */ + + else if (count == PCRE_ERROR_PARTIAL) + { + fprintf(outfile, "Partial match"); +#if !defined NODFA + if ((all_use_dfa || use_dfa) && use_size_offsets > 2) + fprintf(outfile, ": %.*s", use_offsets[1] - use_offsets[0], + bptr + use_offsets[0]); +#endif + fprintf(outfile, "\n"); + break; /* Out of the /g loop */ + } + + /* Failed to match. If this is a /g or /G loop and we previously set + g_notempty after a null match, this is not necessarily the end. We want + to advance the start offset, and continue. We won't be at the end of the + string - that was checked before setting g_notempty. + + Complication arises in the case when the newline option is "any" or + "anycrlf". If the previous match was at the end of a line terminated by + CRLF, an advance of one character just passes the \r, whereas we should + prefer the longer newline sequence, as does the code in pcre_exec(). + Fudge the offset value to achieve this. + + Otherwise, in the case of UTF-8 matching, the advance must be one + character, not one byte. */ + + else + { + if (g_notempty != 0) + { + int onechar = 1; + unsigned int obits = ((real_pcre *)re)->options; + use_offsets[0] = start_offset; + if ((obits & PCRE_NEWLINE_BITS) == 0) + { + int d; + (void)pcre_config(PCRE_CONFIG_NEWLINE, &d); + obits = (d == '\r')? PCRE_NEWLINE_CR : + (d == '\n')? PCRE_NEWLINE_LF : + (d == ('\r'<<8 | '\n'))? PCRE_NEWLINE_CRLF : + (d == -2)? PCRE_NEWLINE_ANYCRLF : + (d == -1)? PCRE_NEWLINE_ANY : 0; + } + if (((obits & PCRE_NEWLINE_BITS) == PCRE_NEWLINE_ANY || + (obits & PCRE_NEWLINE_BITS) == PCRE_NEWLINE_ANYCRLF) + && + start_offset < len - 1 && + bptr[start_offset] == '\r' && + bptr[start_offset+1] == '\n') + onechar++; + else if (use_utf8) + { + while (start_offset + onechar < len) + { + int tb = bptr[start_offset+onechar]; + if (tb <= 127) break; + tb &= 0xc0; + if (tb != 0 && tb != 0xc0) onechar++; + } + } + use_offsets[1] = start_offset + onechar; + } + else + { + if (count == PCRE_ERROR_NOMATCH) + { + if (gmatched == 0) fprintf(outfile, "No match\n"); + } + else fprintf(outfile, "Error %d\n", count); + break; /* Out of the /g loop */ + } + } + + /* If not /g or /G we are done */ + + if (!do_g && !do_G) break; + + /* If we have matched an empty string, first check to see if we are at + the end of the subject. If so, the /g loop is over. Otherwise, mimic + what Perl's /g options does. This turns out to be rather cunning. First + we set PCRE_NOTEMPTY and PCRE_ANCHORED and try the match again at the + same point. If this fails (picked up above) we advance to the next + character. */ + + g_notempty = 0; + + if (use_offsets[0] == use_offsets[1]) + { + if (use_offsets[0] == len) break; + g_notempty = PCRE_NOTEMPTY | PCRE_ANCHORED; + } + + /* For /g, update the start offset, leaving the rest alone */ + + if (do_g) start_offset = use_offsets[1]; + + /* For /G, update the pointer and length */ + + else + { + bptr += use_offsets[1]; + len -= use_offsets[1]; + } + } /* End of loop for /g and /G */ + + NEXT_DATA: continue; + } /* End of loop for data lines */ + + CONTINUE: + +#if !defined NOPOSIX + if (posix || do_posix) regfree(&preg); +#endif + + if (re != NULL) new_free(re); + if (extra != NULL) new_free(extra); + if (tables != NULL) + { + new_free((void *)tables); + setlocale(LC_CTYPE, "C"); + locale_set = 0; + } + } + +if (infile == stdin) fprintf(outfile, "\n"); + +EXIT: + +if (infile != NULL && infile != stdin) fclose(infile); +if (outfile != NULL && outfile != stdout) fclose(outfile); + +free(buffer); +free(dbuffer); +free(pbuffer); +free(offsets); + +return yield; +} + +/* End of pcretest.c */ diff --git a/pcre-7.4/ucp.h b/pcre-7.4/ucp.h new file mode 100644 index 0000000..3a4179b --- /dev/null +++ b/pcre-7.4/ucp.h @@ -0,0 +1,133 @@ +/************************************************* +* Unicode Property Table handler * +*************************************************/ + +#ifndef _UCP_H +#define _UCP_H + +/* This file contains definitions of the property values that are returned by +the function _pcre_ucp_findprop(). New values that are added for new releases +of Unicode should always be at the end of each enum, for backwards +compatibility. */ + +/* These are the general character categories. */ + +enum { + ucp_C, /* Other */ + ucp_L, /* Letter */ + ucp_M, /* Mark */ + ucp_N, /* Number */ + ucp_P, /* Punctuation */ + ucp_S, /* Symbol */ + ucp_Z /* Separator */ +}; + +/* These are the particular character types. */ + +enum { + ucp_Cc, /* Control */ + ucp_Cf, /* Format */ + ucp_Cn, /* Unassigned */ + ucp_Co, /* Private use */ + ucp_Cs, /* Surrogate */ + ucp_Ll, /* Lower case letter */ + ucp_Lm, /* Modifier letter */ + ucp_Lo, /* Other letter */ + ucp_Lt, /* Title case letter */ + ucp_Lu, /* Upper case letter */ + ucp_Mc, /* Spacing mark */ + ucp_Me, /* Enclosing mark */ + ucp_Mn, /* Non-spacing mark */ + ucp_Nd, /* Decimal number */ + ucp_Nl, /* Letter number */ + ucp_No, /* Other number */ + ucp_Pc, /* Connector punctuation */ + ucp_Pd, /* Dash punctuation */ + ucp_Pe, /* Close punctuation */ + ucp_Pf, /* Final punctuation */ + ucp_Pi, /* Initial punctuation */ + ucp_Po, /* Other punctuation */ + ucp_Ps, /* Open punctuation */ + ucp_Sc, /* Currency symbol */ + ucp_Sk, /* Modifier symbol */ + ucp_Sm, /* Mathematical symbol */ + ucp_So, /* Other symbol */ + ucp_Zl, /* Line separator */ + ucp_Zp, /* Paragraph separator */ + ucp_Zs /* Space separator */ +}; + +/* These are the script identifications. */ + +enum { + ucp_Arabic, + ucp_Armenian, + ucp_Bengali, + ucp_Bopomofo, + ucp_Braille, + ucp_Buginese, + ucp_Buhid, + ucp_Canadian_Aboriginal, + ucp_Cherokee, + ucp_Common, + ucp_Coptic, + ucp_Cypriot, + ucp_Cyrillic, + ucp_Deseret, + ucp_Devanagari, + ucp_Ethiopic, + ucp_Georgian, + ucp_Glagolitic, + ucp_Gothic, + ucp_Greek, + ucp_Gujarati, + ucp_Gurmukhi, + ucp_Han, + ucp_Hangul, + ucp_Hanunoo, + ucp_Hebrew, + ucp_Hiragana, + ucp_Inherited, + ucp_Kannada, + ucp_Katakana, + ucp_Kharoshthi, + ucp_Khmer, + ucp_Lao, + ucp_Latin, + ucp_Limbu, + ucp_Linear_B, + ucp_Malayalam, + ucp_Mongolian, + ucp_Myanmar, + ucp_New_Tai_Lue, + ucp_Ogham, + ucp_Old_Italic, + ucp_Old_Persian, + ucp_Oriya, + ucp_Osmanya, + ucp_Runic, + ucp_Shavian, + ucp_Sinhala, + ucp_Syloti_Nagri, + ucp_Syriac, + ucp_Tagalog, + ucp_Tagbanwa, + ucp_Tai_Le, + ucp_Tamil, + ucp_Telugu, + ucp_Thaana, + ucp_Thai, + ucp_Tibetan, + ucp_Tifinagh, + ucp_Ugaritic, + ucp_Yi, + ucp_Balinese, /* New for Unicode 5.0.0 */ + ucp_Cuneiform, /* New for Unicode 5.0.0 */ + ucp_Nko, /* New for Unicode 5.0.0 */ + ucp_Phags_Pa, /* New for Unicode 5.0.0 */ + ucp_Phoenician /* New for Unicode 5.0.0 */ +}; + +#endif + +/* End of ucp.h */ diff --git a/pcre-7.4/ucpinternal.h b/pcre-7.4/ucpinternal.h new file mode 100644 index 0000000..811a373 --- /dev/null +++ b/pcre-7.4/ucpinternal.h @@ -0,0 +1,92 @@ +/************************************************* +* Unicode Property Table handler * +*************************************************/ + +#ifndef _UCPINTERNAL_H +#define _UCPINTERNAL_H + +/* Internal header file defining the layout of the bits in each pair of 32-bit +words that form a data item in the table. */ + +typedef struct cnode { + pcre_uint32 f0; + pcre_uint32 f1; +} cnode; + +/* Things for the f0 field */ + +#define f0_scriptmask 0xff000000 /* Mask for script field */ +#define f0_scriptshift 24 /* Shift for script value */ +#define f0_rangeflag 0x00f00000 /* Flag for a range item */ +#define f0_charmask 0x001fffff /* Mask for code point value */ + +/* Things for the f1 field */ + +#define f1_typemask 0xfc000000 /* Mask for char type field */ +#define f1_typeshift 26 /* Shift for the type field */ +#define f1_rangemask 0x0000ffff /* Mask for a range offset */ +#define f1_casemask 0x0000ffff /* Mask for a case offset */ +#define f1_caseneg 0xffff8000 /* Bits for negation */ + +/* The data consists of a vector of structures of type cnode. The two unsigned +32-bit integers are used as follows: + +(f0) (1) The most significant byte holds the script number. The numbers are + defined by the enum in ucp.h. + + (2) The 0x00800000 bit is set if this entry defines a range of characters. + It is not set if this entry defines a single character + + (3) The 0x00600000 bits are spare. + + (4) The 0x001fffff bits contain the code point. No Unicode code point will + ever be greater than 0x0010ffff, so this should be OK for ever. + +(f1) (1) The 0xfc000000 bits contain the character type number. The numbers are + defined by an enum in ucp.h. + + (2) The 0x03ff0000 bits are spare. + + (3) The 0x0000ffff bits contain EITHER the unsigned offset to the top of + range if this entry defines a range, OR the *signed* offset to the + character's "other case" partner if this entry defines a single + character. There is no partner if the value is zero. + +------------------------------------------------------------------------------- +| script (8) |.|.|.| codepoint (21) || type (6) |.|.| spare (8) | offset (16) | +------------------------------------------------------------------------------- + | | | | | + | | |-> spare | |-> spare + | | | + | |-> spare |-> spare + | + |-> range flag + +The upper/lower casing information is set only for characters that come in +pairs. The non-one-to-one mappings in the Unicode data are ignored. + +When searching the data, proceed as follows: + +(1) Set up for a binary chop search. + +(2) If the top is not greater than the bottom, the character is not in the + table. Its type must therefore be "Cn" ("Undefined"). + +(3) Find the middle vector element. + +(4) Extract the code point and compare. If equal, we are done. + +(5) If the test character is smaller, set the top to the current point, and + goto (2). + +(6) If the current entry defines a range, compute the last character by adding + the offset, and see if the test character is within the range. If it is, + we are done. + +(7) Otherwise, set the bottom to one element past the current point and goto + (2). +*/ + +#endif /* _UCPINTERNAL_H */ + +/* End of ucpinternal.h */ diff --git a/pcre-7.4/ucptable.h b/pcre-7.4/ucptable.h new file mode 100644 index 0000000..07eaced --- /dev/null +++ b/pcre-7.4/ucptable.h @@ -0,0 +1,3068 @@ +/* This source module is automatically generated from the Unicode +property table. See ucpinternal.h for a description of the layout. +This version was made from the Unicode 5.0.0 tables. */ + +static const cnode ucp_table[] = { + { 0x09800000, 0x0000001f }, + { 0x09000020, 0x74000000 }, + { 0x09800021, 0x54000002 }, + { 0x09000024, 0x5c000000 }, + { 0x09800025, 0x54000002 }, + { 0x09000028, 0x58000000 }, + { 0x09000029, 0x48000000 }, + { 0x0900002a, 0x54000000 }, + { 0x0900002b, 0x64000000 }, + { 0x0900002c, 0x54000000 }, + { 0x0900002d, 0x44000000 }, + { 0x0980002e, 0x54000001 }, + { 0x09800030, 0x34000009 }, + { 0x0980003a, 0x54000001 }, + { 0x0980003c, 0x64000002 }, + { 0x0980003f, 0x54000001 }, + { 0x21000041, 0x24000020 }, + { 0x21000042, 0x24000020 }, + { 0x21000043, 0x24000020 }, + { 0x21000044, 0x24000020 }, + { 0x21000045, 0x24000020 }, + { 0x21000046, 0x24000020 }, + { 0x21000047, 0x24000020 }, + { 0x21000048, 0x24000020 }, + { 0x21000049, 0x24000020 }, + { 0x2100004a, 0x24000020 }, + { 0x2100004b, 0x24000020 }, + { 0x2100004c, 0x24000020 }, + { 0x2100004d, 0x24000020 }, + { 0x2100004e, 0x24000020 }, + { 0x2100004f, 0x24000020 }, + { 0x21000050, 0x24000020 }, + { 0x21000051, 0x24000020 }, + { 0x21000052, 0x24000020 }, + { 0x21000053, 0x24000020 }, + { 0x21000054, 0x24000020 }, + { 0x21000055, 0x24000020 }, + { 0x21000056, 0x24000020 }, + { 0x21000057, 0x24000020 }, + { 0x21000058, 0x24000020 }, + { 0x21000059, 0x24000020 }, + { 0x2100005a, 0x24000020 }, + { 0x0900005b, 0x58000000 }, + { 0x0900005c, 0x54000000 }, + { 0x0900005d, 0x48000000 }, + { 0x0900005e, 0x60000000 }, + { 0x0900005f, 0x40000000 }, + { 0x09000060, 0x60000000 }, + { 0x21000061, 0x1400ffe0 }, + { 0x21000062, 0x1400ffe0 }, + { 0x21000063, 0x1400ffe0 }, + { 0x21000064, 0x1400ffe0 }, + { 0x21000065, 0x1400ffe0 }, + { 0x21000066, 0x1400ffe0 }, + { 0x21000067, 0x1400ffe0 }, + { 0x21000068, 0x1400ffe0 }, + { 0x21000069, 0x1400ffe0 }, + { 0x2100006a, 0x1400ffe0 }, + { 0x2100006b, 0x1400ffe0 }, + { 0x2100006c, 0x1400ffe0 }, + { 0x2100006d, 0x1400ffe0 }, + { 0x2100006e, 0x1400ffe0 }, + { 0x2100006f, 0x1400ffe0 }, + { 0x21000070, 0x1400ffe0 }, + { 0x21000071, 0x1400ffe0 }, + { 0x21000072, 0x1400ffe0 }, + { 0x21000073, 0x1400ffe0 }, + { 0x21000074, 0x1400ffe0 }, + { 0x21000075, 0x1400ffe0 }, + { 0x21000076, 0x1400ffe0 }, + { 0x21000077, 0x1400ffe0 }, + { 0x21000078, 0x1400ffe0 }, + { 0x21000079, 0x1400ffe0 }, + { 0x2100007a, 0x1400ffe0 }, + { 0x0900007b, 0x58000000 }, + { 0x0900007c, 0x64000000 }, + { 0x0900007d, 0x48000000 }, + { 0x0900007e, 0x64000000 }, + { 0x0980007f, 0x00000020 }, + { 0x090000a0, 0x74000000 }, + { 0x090000a1, 0x54000000 }, + { 0x098000a2, 0x5c000003 }, + { 0x098000a6, 0x68000001 }, + { 0x090000a8, 0x60000000 }, + { 0x090000a9, 0x68000000 }, + { 0x210000aa, 0x14000000 }, + { 0x090000ab, 0x50000000 }, + { 0x090000ac, 0x64000000 }, + { 0x090000ad, 0x04000000 }, + { 0x090000ae, 0x68000000 }, + { 0x090000af, 0x60000000 }, + { 0x090000b0, 0x68000000 }, + { 0x090000b1, 0x64000000 }, + { 0x098000b2, 0x3c000001 }, + { 0x090000b4, 0x60000000 }, + { 0x090000b5, 0x140002e7 }, + { 0x090000b6, 0x68000000 }, + { 0x090000b7, 0x54000000 }, + { 0x090000b8, 0x60000000 }, + { 0x090000b9, 0x3c000000 }, + { 0x210000ba, 0x14000000 }, + { 0x090000bb, 0x4c000000 }, + { 0x098000bc, 0x3c000002 }, + { 0x090000bf, 0x54000000 }, + { 0x210000c0, 0x24000020 }, + { 0x210000c1, 0x24000020 }, + { 0x210000c2, 0x24000020 }, + { 0x210000c3, 0x24000020 }, + { 0x210000c4, 0x24000020 }, + { 0x210000c5, 0x24000020 }, + { 0x210000c6, 0x24000020 }, + { 0x210000c7, 0x24000020 }, + { 0x210000c8, 0x24000020 }, + { 0x210000c9, 0x24000020 }, + { 0x210000ca, 0x24000020 }, + { 0x210000cb, 0x24000020 }, + { 0x210000cc, 0x24000020 }, + { 0x210000cd, 0x24000020 }, + { 0x210000ce, 0x24000020 }, + { 0x210000cf, 0x24000020 }, + { 0x210000d0, 0x24000020 }, + { 0x210000d1, 0x24000020 }, + { 0x210000d2, 0x24000020 }, + { 0x210000d3, 0x24000020 }, + { 0x210000d4, 0x24000020 }, + { 0x210000d5, 0x24000020 }, + { 0x210000d6, 0x24000020 }, + { 0x090000d7, 0x64000000 }, + { 0x210000d8, 0x24000020 }, + { 0x210000d9, 0x24000020 }, + { 0x210000da, 0x24000020 }, + { 0x210000db, 0x24000020 }, + { 0x210000dc, 0x24000020 }, + { 0x210000dd, 0x24000020 }, + { 0x210000de, 0x24000020 }, + { 0x210000df, 0x14000000 }, + { 0x210000e0, 0x1400ffe0 }, + { 0x210000e1, 0x1400ffe0 }, + { 0x210000e2, 0x1400ffe0 }, + { 0x210000e3, 0x1400ffe0 }, + { 0x210000e4, 0x1400ffe0 }, + { 0x210000e5, 0x1400ffe0 }, + { 0x210000e6, 0x1400ffe0 }, + { 0x210000e7, 0x1400ffe0 }, + { 0x210000e8, 0x1400ffe0 }, + { 0x210000e9, 0x1400ffe0 }, + { 0x210000ea, 0x1400ffe0 }, + { 0x210000eb, 0x1400ffe0 }, + { 0x210000ec, 0x1400ffe0 }, + { 0x210000ed, 0x1400ffe0 }, + { 0x210000ee, 0x1400ffe0 }, + { 0x210000ef, 0x1400ffe0 }, + { 0x210000f0, 0x1400ffe0 }, + { 0x210000f1, 0x1400ffe0 }, + { 0x210000f2, 0x1400ffe0 }, + { 0x210000f3, 0x1400ffe0 }, + { 0x210000f4, 0x1400ffe0 }, + { 0x210000f5, 0x1400ffe0 }, + { 0x210000f6, 0x1400ffe0 }, + { 0x090000f7, 0x64000000 }, + { 0x210000f8, 0x1400ffe0 }, + { 0x210000f9, 0x1400ffe0 }, + { 0x210000fa, 0x1400ffe0 }, + { 0x210000fb, 0x1400ffe0 }, + { 0x210000fc, 0x1400ffe0 }, + { 0x210000fd, 0x1400ffe0 }, + { 0x210000fe, 0x1400ffe0 }, + { 0x210000ff, 0x14000079 }, + { 0x21000100, 0x24000001 }, + { 0x21000101, 0x1400ffff }, + { 0x21000102, 0x24000001 }, + { 0x21000103, 0x1400ffff }, + { 0x21000104, 0x24000001 }, + { 0x21000105, 0x1400ffff }, + { 0x21000106, 0x24000001 }, + { 0x21000107, 0x1400ffff }, + { 0x21000108, 0x24000001 }, + { 0x21000109, 0x1400ffff }, + { 0x2100010a, 0x24000001 }, + { 0x2100010b, 0x1400ffff }, + { 0x2100010c, 0x24000001 }, + { 0x2100010d, 0x1400ffff }, + { 0x2100010e, 0x24000001 }, + { 0x2100010f, 0x1400ffff }, + { 0x21000110, 0x24000001 }, + { 0x21000111, 0x1400ffff }, + { 0x21000112, 0x24000001 }, + { 0x21000113, 0x1400ffff }, + { 0x21000114, 0x24000001 }, + { 0x21000115, 0x1400ffff }, + { 0x21000116, 0x24000001 }, + { 0x21000117, 0x1400ffff }, + { 0x21000118, 0x24000001 }, + { 0x21000119, 0x1400ffff }, + { 0x2100011a, 0x24000001 }, + { 0x2100011b, 0x1400ffff }, + { 0x2100011c, 0x24000001 }, + { 0x2100011d, 0x1400ffff }, + { 0x2100011e, 0x24000001 }, + { 0x2100011f, 0x1400ffff }, + { 0x21000120, 0x24000001 }, + { 0x21000121, 0x1400ffff }, + { 0x21000122, 0x24000001 }, + { 0x21000123, 0x1400ffff }, + { 0x21000124, 0x24000001 }, + { 0x21000125, 0x1400ffff }, + { 0x21000126, 0x24000001 }, + { 0x21000127, 0x1400ffff }, + { 0x21000128, 0x24000001 }, + { 0x21000129, 0x1400ffff }, + { 0x2100012a, 0x24000001 }, + { 0x2100012b, 0x1400ffff }, + { 0x2100012c, 0x24000001 }, + { 0x2100012d, 0x1400ffff }, + { 0x2100012e, 0x24000001 }, + { 0x2100012f, 0x1400ffff }, + { 0x21000130, 0x2400ff39 }, + { 0x21000131, 0x1400ff18 }, + { 0x21000132, 0x24000001 }, + { 0x21000133, 0x1400ffff }, + { 0x21000134, 0x24000001 }, + { 0x21000135, 0x1400ffff }, + { 0x21000136, 0x24000001 }, + { 0x21000137, 0x1400ffff }, + { 0x21000138, 0x14000000 }, + { 0x21000139, 0x24000001 }, + { 0x2100013a, 0x1400ffff }, + { 0x2100013b, 0x24000001 }, + { 0x2100013c, 0x1400ffff }, + { 0x2100013d, 0x24000001 }, + { 0x2100013e, 0x1400ffff }, + { 0x2100013f, 0x24000001 }, + { 0x21000140, 0x1400ffff }, + { 0x21000141, 0x24000001 }, + { 0x21000142, 0x1400ffff }, + { 0x21000143, 0x24000001 }, + { 0x21000144, 0x1400ffff }, + { 0x21000145, 0x24000001 }, + { 0x21000146, 0x1400ffff }, + { 0x21000147, 0x24000001 }, + { 0x21000148, 0x1400ffff }, + { 0x21000149, 0x14000000 }, + { 0x2100014a, 0x24000001 }, + { 0x2100014b, 0x1400ffff }, + { 0x2100014c, 0x24000001 }, + { 0x2100014d, 0x1400ffff }, + { 0x2100014e, 0x24000001 }, + { 0x2100014f, 0x1400ffff }, + { 0x21000150, 0x24000001 }, + { 0x21000151, 0x1400ffff }, + { 0x21000152, 0x24000001 }, + { 0x21000153, 0x1400ffff }, + { 0x21000154, 0x24000001 }, + { 0x21000155, 0x1400ffff }, + { 0x21000156, 0x24000001 }, + { 0x21000157, 0x1400ffff }, + { 0x21000158, 0x24000001 }, + { 0x21000159, 0x1400ffff }, + { 0x2100015a, 0x24000001 }, + { 0x2100015b, 0x1400ffff }, + { 0x2100015c, 0x24000001 }, + { 0x2100015d, 0x1400ffff }, + { 0x2100015e, 0x24000001 }, + { 0x2100015f, 0x1400ffff }, + { 0x21000160, 0x24000001 }, + { 0x21000161, 0x1400ffff }, + { 0x21000162, 0x24000001 }, + { 0x21000163, 0x1400ffff }, + { 0x21000164, 0x24000001 }, + { 0x21000165, 0x1400ffff }, + { 0x21000166, 0x24000001 }, + { 0x21000167, 0x1400ffff }, + { 0x21000168, 0x24000001 }, + { 0x21000169, 0x1400ffff }, + { 0x2100016a, 0x24000001 }, + { 0x2100016b, 0x1400ffff }, + { 0x2100016c, 0x24000001 }, + { 0x2100016d, 0x1400ffff }, + { 0x2100016e, 0x24000001 }, + { 0x2100016f, 0x1400ffff }, + { 0x21000170, 0x24000001 }, + { 0x21000171, 0x1400ffff }, + { 0x21000172, 0x24000001 }, + { 0x21000173, 0x1400ffff }, + { 0x21000174, 0x24000001 }, + { 0x21000175, 0x1400ffff }, + { 0x21000176, 0x24000001 }, + { 0x21000177, 0x1400ffff }, + { 0x21000178, 0x2400ff87 }, + { 0x21000179, 0x24000001 }, + { 0x2100017a, 0x1400ffff }, + { 0x2100017b, 0x24000001 }, + { 0x2100017c, 0x1400ffff }, + { 0x2100017d, 0x24000001 }, + { 0x2100017e, 0x1400ffff }, + { 0x2100017f, 0x1400fed4 }, + { 0x21000180, 0x140000c3 }, + { 0x21000181, 0x240000d2 }, + { 0x21000182, 0x24000001 }, + { 0x21000183, 0x1400ffff }, + { 0x21000184, 0x24000001 }, + { 0x21000185, 0x1400ffff }, + { 0x21000186, 0x240000ce }, + { 0x21000187, 0x24000001 }, + { 0x21000188, 0x1400ffff }, + { 0x21000189, 0x240000cd }, + { 0x2100018a, 0x240000cd }, + { 0x2100018b, 0x24000001 }, + { 0x2100018c, 0x1400ffff }, + { 0x2100018d, 0x14000000 }, + { 0x2100018e, 0x2400004f }, + { 0x2100018f, 0x240000ca }, + { 0x21000190, 0x240000cb }, + { 0x21000191, 0x24000001 }, + { 0x21000192, 0x1400ffff }, + { 0x21000193, 0x240000cd }, + { 0x21000194, 0x240000cf }, + { 0x21000195, 0x14000061 }, + { 0x21000196, 0x240000d3 }, + { 0x21000197, 0x240000d1 }, + { 0x21000198, 0x24000001 }, + { 0x21000199, 0x1400ffff }, + { 0x2100019a, 0x140000a3 }, + { 0x2100019b, 0x14000000 }, + { 0x2100019c, 0x240000d3 }, + { 0x2100019d, 0x240000d5 }, + { 0x2100019e, 0x14000082 }, + { 0x2100019f, 0x240000d6 }, + { 0x210001a0, 0x24000001 }, + { 0x210001a1, 0x1400ffff }, + { 0x210001a2, 0x24000001 }, + { 0x210001a3, 0x1400ffff }, + { 0x210001a4, 0x24000001 }, + { 0x210001a5, 0x1400ffff }, + { 0x210001a6, 0x240000da }, + { 0x210001a7, 0x24000001 }, + { 0x210001a8, 0x1400ffff }, + { 0x210001a9, 0x240000da }, + { 0x218001aa, 0x14000001 }, + { 0x210001ac, 0x24000001 }, + { 0x210001ad, 0x1400ffff }, + { 0x210001ae, 0x240000da }, + { 0x210001af, 0x24000001 }, + { 0x210001b0, 0x1400ffff }, + { 0x210001b1, 0x240000d9 }, + { 0x210001b2, 0x240000d9 }, + { 0x210001b3, 0x24000001 }, + { 0x210001b4, 0x1400ffff }, + { 0x210001b5, 0x24000001 }, + { 0x210001b6, 0x1400ffff }, + { 0x210001b7, 0x240000db }, + { 0x210001b8, 0x24000001 }, + { 0x210001b9, 0x1400ffff }, + { 0x210001ba, 0x14000000 }, + { 0x210001bb, 0x1c000000 }, + { 0x210001bc, 0x24000001 }, + { 0x210001bd, 0x1400ffff }, + { 0x210001be, 0x14000000 }, + { 0x210001bf, 0x14000038 }, + { 0x218001c0, 0x1c000003 }, + { 0x210001c4, 0x24000002 }, + { 0x210001c5, 0x2000ffff }, + { 0x210001c6, 0x1400fffe }, + { 0x210001c7, 0x24000002 }, + { 0x210001c8, 0x2000ffff }, + { 0x210001c9, 0x1400fffe }, + { 0x210001ca, 0x24000002 }, + { 0x210001cb, 0x2000ffff }, + { 0x210001cc, 0x1400fffe }, + { 0x210001cd, 0x24000001 }, + { 0x210001ce, 0x1400ffff }, + { 0x210001cf, 0x24000001 }, + { 0x210001d0, 0x1400ffff }, + { 0x210001d1, 0x24000001 }, + { 0x210001d2, 0x1400ffff }, + { 0x210001d3, 0x24000001 }, + { 0x210001d4, 0x1400ffff }, + { 0x210001d5, 0x24000001 }, + { 0x210001d6, 0x1400ffff }, + { 0x210001d7, 0x24000001 }, + { 0x210001d8, 0x1400ffff }, + { 0x210001d9, 0x24000001 }, + { 0x210001da, 0x1400ffff }, + { 0x210001db, 0x24000001 }, + { 0x210001dc, 0x1400ffff }, + { 0x210001dd, 0x1400ffb1 }, + { 0x210001de, 0x24000001 }, + { 0x210001df, 0x1400ffff }, + { 0x210001e0, 0x24000001 }, + { 0x210001e1, 0x1400ffff }, + { 0x210001e2, 0x24000001 }, + { 0x210001e3, 0x1400ffff }, + { 0x210001e4, 0x24000001 }, + { 0x210001e5, 0x1400ffff }, + { 0x210001e6, 0x24000001 }, + { 0x210001e7, 0x1400ffff }, + { 0x210001e8, 0x24000001 }, + { 0x210001e9, 0x1400ffff }, + { 0x210001ea, 0x24000001 }, + { 0x210001eb, 0x1400ffff }, + { 0x210001ec, 0x24000001 }, + { 0x210001ed, 0x1400ffff }, + { 0x210001ee, 0x24000001 }, + { 0x210001ef, 0x1400ffff }, + { 0x210001f0, 0x14000000 }, + { 0x210001f1, 0x24000002 }, + { 0x210001f2, 0x2000ffff }, + { 0x210001f3, 0x1400fffe }, + { 0x210001f4, 0x24000001 }, + { 0x210001f5, 0x1400ffff }, + { 0x210001f6, 0x2400ff9f }, + { 0x210001f7, 0x2400ffc8 }, + { 0x210001f8, 0x24000001 }, + { 0x210001f9, 0x1400ffff }, + { 0x210001fa, 0x24000001 }, + { 0x210001fb, 0x1400ffff }, + { 0x210001fc, 0x24000001 }, + { 0x210001fd, 0x1400ffff }, + { 0x210001fe, 0x24000001 }, + { 0x210001ff, 0x1400ffff }, + { 0x21000200, 0x24000001 }, + { 0x21000201, 0x1400ffff }, + { 0x21000202, 0x24000001 }, + { 0x21000203, 0x1400ffff }, + { 0x21000204, 0x24000001 }, + { 0x21000205, 0x1400ffff }, + { 0x21000206, 0x24000001 }, + { 0x21000207, 0x1400ffff }, + { 0x21000208, 0x24000001 }, + { 0x21000209, 0x1400ffff }, + { 0x2100020a, 0x24000001 }, + { 0x2100020b, 0x1400ffff }, + { 0x2100020c, 0x24000001 }, + { 0x2100020d, 0x1400ffff }, + { 0x2100020e, 0x24000001 }, + { 0x2100020f, 0x1400ffff }, + { 0x21000210, 0x24000001 }, + { 0x21000211, 0x1400ffff }, + { 0x21000212, 0x24000001 }, + { 0x21000213, 0x1400ffff }, + { 0x21000214, 0x24000001 }, + { 0x21000215, 0x1400ffff }, + { 0x21000216, 0x24000001 }, + { 0x21000217, 0x1400ffff }, + { 0x21000218, 0x24000001 }, + { 0x21000219, 0x1400ffff }, + { 0x2100021a, 0x24000001 }, + { 0x2100021b, 0x1400ffff }, + { 0x2100021c, 0x24000001 }, + { 0x2100021d, 0x1400ffff }, + { 0x2100021e, 0x24000001 }, + { 0x2100021f, 0x1400ffff }, + { 0x21000220, 0x2400ff7e }, + { 0x21000221, 0x14000000 }, + { 0x21000222, 0x24000001 }, + { 0x21000223, 0x1400ffff }, + { 0x21000224, 0x24000001 }, + { 0x21000225, 0x1400ffff }, + { 0x21000226, 0x24000001 }, + { 0x21000227, 0x1400ffff }, + { 0x21000228, 0x24000001 }, + { 0x21000229, 0x1400ffff }, + { 0x2100022a, 0x24000001 }, + { 0x2100022b, 0x1400ffff }, + { 0x2100022c, 0x24000001 }, + { 0x2100022d, 0x1400ffff }, + { 0x2100022e, 0x24000001 }, + { 0x2100022f, 0x1400ffff }, + { 0x21000230, 0x24000001 }, + { 0x21000231, 0x1400ffff }, + { 0x21000232, 0x24000001 }, + { 0x21000233, 0x1400ffff }, + { 0x21800234, 0x14000005 }, + { 0x2100023a, 0x24002a2b }, + { 0x2100023b, 0x24000001 }, + { 0x2100023c, 0x1400ffff }, + { 0x2100023d, 0x2400ff5d }, + { 0x2100023e, 0x24002a28 }, + { 0x2180023f, 0x14000001 }, + { 0x21000241, 0x24000001 }, + { 0x21000242, 0x1400ffff }, + { 0x21000243, 0x2400ff3d }, + { 0x21000244, 0x24000045 }, + { 0x21000245, 0x24000047 }, + { 0x21000246, 0x24000001 }, + { 0x21000247, 0x1400ffff }, + { 0x21000248, 0x24000001 }, + { 0x21000249, 0x1400ffff }, + { 0x2100024a, 0x24000001 }, + { 0x2100024b, 0x1400ffff }, + { 0x2100024c, 0x24000001 }, + { 0x2100024d, 0x1400ffff }, + { 0x2100024e, 0x24000001 }, + { 0x2100024f, 0x1400ffff }, + { 0x21800250, 0x14000002 }, + { 0x21000253, 0x1400ff2e }, + { 0x21000254, 0x1400ff32 }, + { 0x21000255, 0x14000000 }, + { 0x21000256, 0x1400ff33 }, + { 0x21000257, 0x1400ff33 }, + { 0x21000258, 0x14000000 }, + { 0x21000259, 0x1400ff36 }, + { 0x2100025a, 0x14000000 }, + { 0x2100025b, 0x1400ff35 }, + { 0x2180025c, 0x14000003 }, + { 0x21000260, 0x1400ff33 }, + { 0x21800261, 0x14000001 }, + { 0x21000263, 0x1400ff31 }, + { 0x21800264, 0x14000003 }, + { 0x21000268, 0x1400ff2f }, + { 0x21000269, 0x1400ff2d }, + { 0x2100026a, 0x14000000 }, + { 0x2100026b, 0x140029f7 }, + { 0x2180026c, 0x14000002 }, + { 0x2100026f, 0x1400ff2d }, + { 0x21800270, 0x14000001 }, + { 0x21000272, 0x1400ff2b }, + { 0x21800273, 0x14000001 }, + { 0x21000275, 0x1400ff2a }, + { 0x21800276, 0x14000006 }, + { 0x2100027d, 0x140029e7 }, + { 0x2180027e, 0x14000001 }, + { 0x21000280, 0x1400ff26 }, + { 0x21800281, 0x14000001 }, + { 0x21000283, 0x1400ff26 }, + { 0x21800284, 0x14000003 }, + { 0x21000288, 0x1400ff26 }, + { 0x21000289, 0x1400ffbb }, + { 0x2100028a, 0x1400ff27 }, + { 0x2100028b, 0x1400ff27 }, + { 0x2100028c, 0x1400ffb9 }, + { 0x2180028d, 0x14000004 }, + { 0x21000292, 0x1400ff25 }, + { 0x21000293, 0x14000000 }, + { 0x21000294, 0x1c000000 }, + { 0x21800295, 0x1400001a }, + { 0x218002b0, 0x18000011 }, + { 0x098002c2, 0x60000003 }, + { 0x098002c6, 0x1800000b }, + { 0x098002d2, 0x6000000d }, + { 0x218002e0, 0x18000004 }, + { 0x098002e5, 0x60000008 }, + { 0x090002ee, 0x18000000 }, + { 0x098002ef, 0x60000010 }, + { 0x1b800300, 0x30000044 }, + { 0x1b000345, 0x30000054 }, + { 0x1b800346, 0x30000029 }, + { 0x13800374, 0x60000001 }, + { 0x1300037a, 0x18000000 }, + { 0x1300037b, 0x14000082 }, + { 0x1300037c, 0x14000082 }, + { 0x1300037d, 0x14000082 }, + { 0x0900037e, 0x54000000 }, + { 0x13800384, 0x60000001 }, + { 0x13000386, 0x24000026 }, + { 0x09000387, 0x54000000 }, + { 0x13000388, 0x24000025 }, + { 0x13000389, 0x24000025 }, + { 0x1300038a, 0x24000025 }, + { 0x1300038c, 0x24000040 }, + { 0x1300038e, 0x2400003f }, + { 0x1300038f, 0x2400003f }, + { 0x13000390, 0x14000000 }, + { 0x13000391, 0x24000020 }, + { 0x13000392, 0x24000020 }, + { 0x13000393, 0x24000020 }, + { 0x13000394, 0x24000020 }, + { 0x13000395, 0x24000020 }, + { 0x13000396, 0x24000020 }, + { 0x13000397, 0x24000020 }, + { 0x13000398, 0x24000020 }, + { 0x13000399, 0x24000020 }, + { 0x1300039a, 0x24000020 }, + { 0x1300039b, 0x24000020 }, + { 0x1300039c, 0x24000020 }, + { 0x1300039d, 0x24000020 }, + { 0x1300039e, 0x24000020 }, + { 0x1300039f, 0x24000020 }, + { 0x130003a0, 0x24000020 }, + { 0x130003a1, 0x24000020 }, + { 0x130003a3, 0x24000020 }, + { 0x130003a4, 0x24000020 }, + { 0x130003a5, 0x24000020 }, + { 0x130003a6, 0x24000020 }, + { 0x130003a7, 0x24000020 }, + { 0x130003a8, 0x24000020 }, + { 0x130003a9, 0x24000020 }, + { 0x130003aa, 0x24000020 }, + { 0x130003ab, 0x24000020 }, + { 0x130003ac, 0x1400ffda }, + { 0x130003ad, 0x1400ffdb }, + { 0x130003ae, 0x1400ffdb }, + { 0x130003af, 0x1400ffdb }, + { 0x130003b0, 0x14000000 }, + { 0x130003b1, 0x1400ffe0 }, + { 0x130003b2, 0x1400ffe0 }, + { 0x130003b3, 0x1400ffe0 }, + { 0x130003b4, 0x1400ffe0 }, + { 0x130003b5, 0x1400ffe0 }, + { 0x130003b6, 0x1400ffe0 }, + { 0x130003b7, 0x1400ffe0 }, + { 0x130003b8, 0x1400ffe0 }, + { 0x130003b9, 0x1400ffe0 }, + { 0x130003ba, 0x1400ffe0 }, + { 0x130003bb, 0x1400ffe0 }, + { 0x130003bc, 0x1400ffe0 }, + { 0x130003bd, 0x1400ffe0 }, + { 0x130003be, 0x1400ffe0 }, + { 0x130003bf, 0x1400ffe0 }, + { 0x130003c0, 0x1400ffe0 }, + { 0x130003c1, 0x1400ffe0 }, + { 0x130003c2, 0x1400ffe1 }, + { 0x130003c3, 0x1400ffe0 }, + { 0x130003c4, 0x1400ffe0 }, + { 0x130003c5, 0x1400ffe0 }, + { 0x130003c6, 0x1400ffe0 }, + { 0x130003c7, 0x1400ffe0 }, + { 0x130003c8, 0x1400ffe0 }, + { 0x130003c9, 0x1400ffe0 }, + { 0x130003ca, 0x1400ffe0 }, + { 0x130003cb, 0x1400ffe0 }, + { 0x130003cc, 0x1400ffc0 }, + { 0x130003cd, 0x1400ffc1 }, + { 0x130003ce, 0x1400ffc1 }, + { 0x130003d0, 0x1400ffc2 }, + { 0x130003d1, 0x1400ffc7 }, + { 0x138003d2, 0x24000002 }, + { 0x130003d5, 0x1400ffd1 }, + { 0x130003d6, 0x1400ffca }, + { 0x130003d7, 0x14000000 }, + { 0x130003d8, 0x24000001 }, + { 0x130003d9, 0x1400ffff }, + { 0x130003da, 0x24000001 }, + { 0x130003db, 0x1400ffff }, + { 0x130003dc, 0x24000001 }, + { 0x130003dd, 0x1400ffff }, + { 0x130003de, 0x24000001 }, + { 0x130003df, 0x1400ffff }, + { 0x130003e0, 0x24000001 }, + { 0x130003e1, 0x1400ffff }, + { 0x0a0003e2, 0x24000001 }, + { 0x0a0003e3, 0x1400ffff }, + { 0x0a0003e4, 0x24000001 }, + { 0x0a0003e5, 0x1400ffff }, + { 0x0a0003e6, 0x24000001 }, + { 0x0a0003e7, 0x1400ffff }, + { 0x0a0003e8, 0x24000001 }, + { 0x0a0003e9, 0x1400ffff }, + { 0x0a0003ea, 0x24000001 }, + { 0x0a0003eb, 0x1400ffff }, + { 0x0a0003ec, 0x24000001 }, + { 0x0a0003ed, 0x1400ffff }, + { 0x0a0003ee, 0x24000001 }, + { 0x0a0003ef, 0x1400ffff }, + { 0x130003f0, 0x1400ffaa }, + { 0x130003f1, 0x1400ffb0 }, + { 0x130003f2, 0x14000007 }, + { 0x130003f3, 0x14000000 }, + { 0x130003f4, 0x2400ffc4 }, + { 0x130003f5, 0x1400ffa0 }, + { 0x130003f6, 0x64000000 }, + { 0x130003f7, 0x24000001 }, + { 0x130003f8, 0x1400ffff }, + { 0x130003f9, 0x2400fff9 }, + { 0x130003fa, 0x24000001 }, + { 0x130003fb, 0x1400ffff }, + { 0x130003fc, 0x14000000 }, + { 0x130003fd, 0x2400ff7e }, + { 0x130003fe, 0x2400ff7e }, + { 0x130003ff, 0x2400ff7e }, + { 0x0c000400, 0x24000050 }, + { 0x0c000401, 0x24000050 }, + { 0x0c000402, 0x24000050 }, + { 0x0c000403, 0x24000050 }, + { 0x0c000404, 0x24000050 }, + { 0x0c000405, 0x24000050 }, + { 0x0c000406, 0x24000050 }, + { 0x0c000407, 0x24000050 }, + { 0x0c000408, 0x24000050 }, + { 0x0c000409, 0x24000050 }, + { 0x0c00040a, 0x24000050 }, + { 0x0c00040b, 0x24000050 }, + { 0x0c00040c, 0x24000050 }, + { 0x0c00040d, 0x24000050 }, + { 0x0c00040e, 0x24000050 }, + { 0x0c00040f, 0x24000050 }, + { 0x0c000410, 0x24000020 }, + { 0x0c000411, 0x24000020 }, + { 0x0c000412, 0x24000020 }, + { 0x0c000413, 0x24000020 }, + { 0x0c000414, 0x24000020 }, + { 0x0c000415, 0x24000020 }, + { 0x0c000416, 0x24000020 }, + { 0x0c000417, 0x24000020 }, + { 0x0c000418, 0x24000020 }, + { 0x0c000419, 0x24000020 }, + { 0x0c00041a, 0x24000020 }, + { 0x0c00041b, 0x24000020 }, + { 0x0c00041c, 0x24000020 }, + { 0x0c00041d, 0x24000020 }, + { 0x0c00041e, 0x24000020 }, + { 0x0c00041f, 0x24000020 }, + { 0x0c000420, 0x24000020 }, + { 0x0c000421, 0x24000020 }, + { 0x0c000422, 0x24000020 }, + { 0x0c000423, 0x24000020 }, + { 0x0c000424, 0x24000020 }, + { 0x0c000425, 0x24000020 }, + { 0x0c000426, 0x24000020 }, + { 0x0c000427, 0x24000020 }, + { 0x0c000428, 0x24000020 }, + { 0x0c000429, 0x24000020 }, + { 0x0c00042a, 0x24000020 }, + { 0x0c00042b, 0x24000020 }, + { 0x0c00042c, 0x24000020 }, + { 0x0c00042d, 0x24000020 }, + { 0x0c00042e, 0x24000020 }, + { 0x0c00042f, 0x24000020 }, + { 0x0c000430, 0x1400ffe0 }, + { 0x0c000431, 0x1400ffe0 }, + { 0x0c000432, 0x1400ffe0 }, + { 0x0c000433, 0x1400ffe0 }, + { 0x0c000434, 0x1400ffe0 }, + { 0x0c000435, 0x1400ffe0 }, + { 0x0c000436, 0x1400ffe0 }, + { 0x0c000437, 0x1400ffe0 }, + { 0x0c000438, 0x1400ffe0 }, + { 0x0c000439, 0x1400ffe0 }, + { 0x0c00043a, 0x1400ffe0 }, + { 0x0c00043b, 0x1400ffe0 }, + { 0x0c00043c, 0x1400ffe0 }, + { 0x0c00043d, 0x1400ffe0 }, + { 0x0c00043e, 0x1400ffe0 }, + { 0x0c00043f, 0x1400ffe0 }, + { 0x0c000440, 0x1400ffe0 }, + { 0x0c000441, 0x1400ffe0 }, + { 0x0c000442, 0x1400ffe0 }, + { 0x0c000443, 0x1400ffe0 }, + { 0x0c000444, 0x1400ffe0 }, + { 0x0c000445, 0x1400ffe0 }, + { 0x0c000446, 0x1400ffe0 }, + { 0x0c000447, 0x1400ffe0 }, + { 0x0c000448, 0x1400ffe0 }, + { 0x0c000449, 0x1400ffe0 }, + { 0x0c00044a, 0x1400ffe0 }, + { 0x0c00044b, 0x1400ffe0 }, + { 0x0c00044c, 0x1400ffe0 }, + { 0x0c00044d, 0x1400ffe0 }, + { 0x0c00044e, 0x1400ffe0 }, + { 0x0c00044f, 0x1400ffe0 }, + { 0x0c000450, 0x1400ffb0 }, + { 0x0c000451, 0x1400ffb0 }, + { 0x0c000452, 0x1400ffb0 }, + { 0x0c000453, 0x1400ffb0 }, + { 0x0c000454, 0x1400ffb0 }, + { 0x0c000455, 0x1400ffb0 }, + { 0x0c000456, 0x1400ffb0 }, + { 0x0c000457, 0x1400ffb0 }, + { 0x0c000458, 0x1400ffb0 }, + { 0x0c000459, 0x1400ffb0 }, + { 0x0c00045a, 0x1400ffb0 }, + { 0x0c00045b, 0x1400ffb0 }, + { 0x0c00045c, 0x1400ffb0 }, + { 0x0c00045d, 0x1400ffb0 }, + { 0x0c00045e, 0x1400ffb0 }, + { 0x0c00045f, 0x1400ffb0 }, + { 0x0c000460, 0x24000001 }, + { 0x0c000461, 0x1400ffff }, + { 0x0c000462, 0x24000001 }, + { 0x0c000463, 0x1400ffff }, + { 0x0c000464, 0x24000001 }, + { 0x0c000465, 0x1400ffff }, + { 0x0c000466, 0x24000001 }, + { 0x0c000467, 0x1400ffff }, + { 0x0c000468, 0x24000001 }, + { 0x0c000469, 0x1400ffff }, + { 0x0c00046a, 0x24000001 }, + { 0x0c00046b, 0x1400ffff }, + { 0x0c00046c, 0x24000001 }, + { 0x0c00046d, 0x1400ffff }, + { 0x0c00046e, 0x24000001 }, + { 0x0c00046f, 0x1400ffff }, + { 0x0c000470, 0x24000001 }, + { 0x0c000471, 0x1400ffff }, + { 0x0c000472, 0x24000001 }, + { 0x0c000473, 0x1400ffff }, + { 0x0c000474, 0x24000001 }, + { 0x0c000475, 0x1400ffff }, + { 0x0c000476, 0x24000001 }, + { 0x0c000477, 0x1400ffff }, + { 0x0c000478, 0x24000001 }, + { 0x0c000479, 0x1400ffff }, + { 0x0c00047a, 0x24000001 }, + { 0x0c00047b, 0x1400ffff }, + { 0x0c00047c, 0x24000001 }, + { 0x0c00047d, 0x1400ffff }, + { 0x0c00047e, 0x24000001 }, + { 0x0c00047f, 0x1400ffff }, + { 0x0c000480, 0x24000001 }, + { 0x0c000481, 0x1400ffff }, + { 0x0c000482, 0x68000000 }, + { 0x0c800483, 0x30000003 }, + { 0x0c800488, 0x2c000001 }, + { 0x0c00048a, 0x24000001 }, + { 0x0c00048b, 0x1400ffff }, + { 0x0c00048c, 0x24000001 }, + { 0x0c00048d, 0x1400ffff }, + { 0x0c00048e, 0x24000001 }, + { 0x0c00048f, 0x1400ffff }, + { 0x0c000490, 0x24000001 }, + { 0x0c000491, 0x1400ffff }, + { 0x0c000492, 0x24000001 }, + { 0x0c000493, 0x1400ffff }, + { 0x0c000494, 0x24000001 }, + { 0x0c000495, 0x1400ffff }, + { 0x0c000496, 0x24000001 }, + { 0x0c000497, 0x1400ffff }, + { 0x0c000498, 0x24000001 }, + { 0x0c000499, 0x1400ffff }, + { 0x0c00049a, 0x24000001 }, + { 0x0c00049b, 0x1400ffff }, + { 0x0c00049c, 0x24000001 }, + { 0x0c00049d, 0x1400ffff }, + { 0x0c00049e, 0x24000001 }, + { 0x0c00049f, 0x1400ffff }, + { 0x0c0004a0, 0x24000001 }, + { 0x0c0004a1, 0x1400ffff }, + { 0x0c0004a2, 0x24000001 }, + { 0x0c0004a3, 0x1400ffff }, + { 0x0c0004a4, 0x24000001 }, + { 0x0c0004a5, 0x1400ffff }, + { 0x0c0004a6, 0x24000001 }, + { 0x0c0004a7, 0x1400ffff }, + { 0x0c0004a8, 0x24000001 }, + { 0x0c0004a9, 0x1400ffff }, + { 0x0c0004aa, 0x24000001 }, + { 0x0c0004ab, 0x1400ffff }, + { 0x0c0004ac, 0x24000001 }, + { 0x0c0004ad, 0x1400ffff }, + { 0x0c0004ae, 0x24000001 }, + { 0x0c0004af, 0x1400ffff }, + { 0x0c0004b0, 0x24000001 }, + { 0x0c0004b1, 0x1400ffff }, + { 0x0c0004b2, 0x24000001 }, + { 0x0c0004b3, 0x1400ffff }, + { 0x0c0004b4, 0x24000001 }, + { 0x0c0004b5, 0x1400ffff }, + { 0x0c0004b6, 0x24000001 }, + { 0x0c0004b7, 0x1400ffff }, + { 0x0c0004b8, 0x24000001 }, + { 0x0c0004b9, 0x1400ffff }, + { 0x0c0004ba, 0x24000001 }, + { 0x0c0004bb, 0x1400ffff }, + { 0x0c0004bc, 0x24000001 }, + { 0x0c0004bd, 0x1400ffff }, + { 0x0c0004be, 0x24000001 }, + { 0x0c0004bf, 0x1400ffff }, + { 0x0c0004c0, 0x2400000f }, + { 0x0c0004c1, 0x24000001 }, + { 0x0c0004c2, 0x1400ffff }, + { 0x0c0004c3, 0x24000001 }, + { 0x0c0004c4, 0x1400ffff }, + { 0x0c0004c5, 0x24000001 }, + { 0x0c0004c6, 0x1400ffff }, + { 0x0c0004c7, 0x24000001 }, + { 0x0c0004c8, 0x1400ffff }, + { 0x0c0004c9, 0x24000001 }, + { 0x0c0004ca, 0x1400ffff }, + { 0x0c0004cb, 0x24000001 }, + { 0x0c0004cc, 0x1400ffff }, + { 0x0c0004cd, 0x24000001 }, + { 0x0c0004ce, 0x1400ffff }, + { 0x0c0004cf, 0x1400fff1 }, + { 0x0c0004d0, 0x24000001 }, + { 0x0c0004d1, 0x1400ffff }, + { 0x0c0004d2, 0x24000001 }, + { 0x0c0004d3, 0x1400ffff }, + { 0x0c0004d4, 0x24000001 }, + { 0x0c0004d5, 0x1400ffff }, + { 0x0c0004d6, 0x24000001 }, + { 0x0c0004d7, 0x1400ffff }, + { 0x0c0004d8, 0x24000001 }, + { 0x0c0004d9, 0x1400ffff }, + { 0x0c0004da, 0x24000001 }, + { 0x0c0004db, 0x1400ffff }, + { 0x0c0004dc, 0x24000001 }, + { 0x0c0004dd, 0x1400ffff }, + { 0x0c0004de, 0x24000001 }, + { 0x0c0004df, 0x1400ffff }, + { 0x0c0004e0, 0x24000001 }, + { 0x0c0004e1, 0x1400ffff }, + { 0x0c0004e2, 0x24000001 }, + { 0x0c0004e3, 0x1400ffff }, + { 0x0c0004e4, 0x24000001 }, + { 0x0c0004e5, 0x1400ffff }, + { 0x0c0004e6, 0x24000001 }, + { 0x0c0004e7, 0x1400ffff }, + { 0x0c0004e8, 0x24000001 }, + { 0x0c0004e9, 0x1400ffff }, + { 0x0c0004ea, 0x24000001 }, + { 0x0c0004eb, 0x1400ffff }, + { 0x0c0004ec, 0x24000001 }, + { 0x0c0004ed, 0x1400ffff }, + { 0x0c0004ee, 0x24000001 }, + { 0x0c0004ef, 0x1400ffff }, + { 0x0c0004f0, 0x24000001 }, + { 0x0c0004f1, 0x1400ffff }, + { 0x0c0004f2, 0x24000001 }, + { 0x0c0004f3, 0x1400ffff }, + { 0x0c0004f4, 0x24000001 }, + { 0x0c0004f5, 0x1400ffff }, + { 0x0c0004f6, 0x24000001 }, + { 0x0c0004f7, 0x1400ffff }, + { 0x0c0004f8, 0x24000001 }, + { 0x0c0004f9, 0x1400ffff }, + { 0x0c0004fa, 0x24000001 }, + { 0x0c0004fb, 0x1400ffff }, + { 0x0c0004fc, 0x24000001 }, + { 0x0c0004fd, 0x1400ffff }, + { 0x0c0004fe, 0x24000001 }, + { 0x0c0004ff, 0x1400ffff }, + { 0x0c000500, 0x24000001 }, + { 0x0c000501, 0x1400ffff }, + { 0x0c000502, 0x24000001 }, + { 0x0c000503, 0x1400ffff }, + { 0x0c000504, 0x24000001 }, + { 0x0c000505, 0x1400ffff }, + { 0x0c000506, 0x24000001 }, + { 0x0c000507, 0x1400ffff }, + { 0x0c000508, 0x24000001 }, + { 0x0c000509, 0x1400ffff }, + { 0x0c00050a, 0x24000001 }, + { 0x0c00050b, 0x1400ffff }, + { 0x0c00050c, 0x24000001 }, + { 0x0c00050d, 0x1400ffff }, + { 0x0c00050e, 0x24000001 }, + { 0x0c00050f, 0x1400ffff }, + { 0x0c000510, 0x24000001 }, + { 0x0c000511, 0x1400ffff }, + { 0x0c000512, 0x24000001 }, + { 0x0c000513, 0x1400ffff }, + { 0x01000531, 0x24000030 }, + { 0x01000532, 0x24000030 }, + { 0x01000533, 0x24000030 }, + { 0x01000534, 0x24000030 }, + { 0x01000535, 0x24000030 }, + { 0x01000536, 0x24000030 }, + { 0x01000537, 0x24000030 }, + { 0x01000538, 0x24000030 }, + { 0x01000539, 0x24000030 }, + { 0x0100053a, 0x24000030 }, + { 0x0100053b, 0x24000030 }, + { 0x0100053c, 0x24000030 }, + { 0x0100053d, 0x24000030 }, + { 0x0100053e, 0x24000030 }, + { 0x0100053f, 0x24000030 }, + { 0x01000540, 0x24000030 }, + { 0x01000541, 0x24000030 }, + { 0x01000542, 0x24000030 }, + { 0x01000543, 0x24000030 }, + { 0x01000544, 0x24000030 }, + { 0x01000545, 0x24000030 }, + { 0x01000546, 0x24000030 }, + { 0x01000547, 0x24000030 }, + { 0x01000548, 0x24000030 }, + { 0x01000549, 0x24000030 }, + { 0x0100054a, 0x24000030 }, + { 0x0100054b, 0x24000030 }, + { 0x0100054c, 0x24000030 }, + { 0x0100054d, 0x24000030 }, + { 0x0100054e, 0x24000030 }, + { 0x0100054f, 0x24000030 }, + { 0x01000550, 0x24000030 }, + { 0x01000551, 0x24000030 }, + { 0x01000552, 0x24000030 }, + { 0x01000553, 0x24000030 }, + { 0x01000554, 0x24000030 }, + { 0x01000555, 0x24000030 }, + { 0x01000556, 0x24000030 }, + { 0x01000559, 0x18000000 }, + { 0x0180055a, 0x54000005 }, + { 0x01000561, 0x1400ffd0 }, + { 0x01000562, 0x1400ffd0 }, + { 0x01000563, 0x1400ffd0 }, + { 0x01000564, 0x1400ffd0 }, + { 0x01000565, 0x1400ffd0 }, + { 0x01000566, 0x1400ffd0 }, + { 0x01000567, 0x1400ffd0 }, + { 0x01000568, 0x1400ffd0 }, + { 0x01000569, 0x1400ffd0 }, + { 0x0100056a, 0x1400ffd0 }, + { 0x0100056b, 0x1400ffd0 }, + { 0x0100056c, 0x1400ffd0 }, + { 0x0100056d, 0x1400ffd0 }, + { 0x0100056e, 0x1400ffd0 }, + { 0x0100056f, 0x1400ffd0 }, + { 0x01000570, 0x1400ffd0 }, + { 0x01000571, 0x1400ffd0 }, + { 0x01000572, 0x1400ffd0 }, + { 0x01000573, 0x1400ffd0 }, + { 0x01000574, 0x1400ffd0 }, + { 0x01000575, 0x1400ffd0 }, + { 0x01000576, 0x1400ffd0 }, + { 0x01000577, 0x1400ffd0 }, + { 0x01000578, 0x1400ffd0 }, + { 0x01000579, 0x1400ffd0 }, + { 0x0100057a, 0x1400ffd0 }, + { 0x0100057b, 0x1400ffd0 }, + { 0x0100057c, 0x1400ffd0 }, + { 0x0100057d, 0x1400ffd0 }, + { 0x0100057e, 0x1400ffd0 }, + { 0x0100057f, 0x1400ffd0 }, + { 0x01000580, 0x1400ffd0 }, + { 0x01000581, 0x1400ffd0 }, + { 0x01000582, 0x1400ffd0 }, + { 0x01000583, 0x1400ffd0 }, + { 0x01000584, 0x1400ffd0 }, + { 0x01000585, 0x1400ffd0 }, + { 0x01000586, 0x1400ffd0 }, + { 0x01000587, 0x14000000 }, + { 0x09000589, 0x54000000 }, + { 0x0100058a, 0x44000000 }, + { 0x19800591, 0x3000002c }, + { 0x190005be, 0x54000000 }, + { 0x190005bf, 0x30000000 }, + { 0x190005c0, 0x54000000 }, + { 0x198005c1, 0x30000001 }, + { 0x190005c3, 0x54000000 }, + { 0x198005c4, 0x30000001 }, + { 0x190005c6, 0x54000000 }, + { 0x190005c7, 0x30000000 }, + { 0x198005d0, 0x1c00001a }, + { 0x198005f0, 0x1c000002 }, + { 0x198005f3, 0x54000001 }, + { 0x09800600, 0x04000003 }, + { 0x0000060b, 0x5c000000 }, + { 0x0980060c, 0x54000001 }, + { 0x0080060e, 0x68000001 }, + { 0x00800610, 0x30000005 }, + { 0x0900061b, 0x54000000 }, + { 0x0080061e, 0x54000001 }, + { 0x00800621, 0x1c000019 }, + { 0x09000640, 0x18000000 }, + { 0x00800641, 0x1c000009 }, + { 0x1b80064b, 0x30000013 }, + { 0x09800660, 0x34000009 }, + { 0x0080066a, 0x54000003 }, + { 0x0080066e, 0x1c000001 }, + { 0x1b000670, 0x30000000 }, + { 0x00800671, 0x1c000062 }, + { 0x000006d4, 0x54000000 }, + { 0x000006d5, 0x1c000000 }, + { 0x008006d6, 0x30000006 }, + { 0x090006dd, 0x04000000 }, + { 0x000006de, 0x2c000000 }, + { 0x008006df, 0x30000005 }, + { 0x008006e5, 0x18000001 }, + { 0x008006e7, 0x30000001 }, + { 0x000006e9, 0x68000000 }, + { 0x008006ea, 0x30000003 }, + { 0x008006ee, 0x1c000001 }, + { 0x008006f0, 0x34000009 }, + { 0x008006fa, 0x1c000002 }, + { 0x008006fd, 0x68000001 }, + { 0x000006ff, 0x1c000000 }, + { 0x31800700, 0x5400000d }, + { 0x3100070f, 0x04000000 }, + { 0x31000710, 0x1c000000 }, + { 0x31000711, 0x30000000 }, + { 0x31800712, 0x1c00001d }, + { 0x31800730, 0x3000001a }, + { 0x3180074d, 0x1c000020 }, + { 0x37800780, 0x1c000025 }, + { 0x378007a6, 0x3000000a }, + { 0x370007b1, 0x1c000000 }, + { 0x3f8007c0, 0x34000009 }, + { 0x3f8007ca, 0x1c000020 }, + { 0x3f8007eb, 0x30000008 }, + { 0x3f8007f4, 0x18000001 }, + { 0x3f0007f6, 0x68000000 }, + { 0x3f8007f7, 0x54000002 }, + { 0x3f0007fa, 0x18000000 }, + { 0x0e800901, 0x30000001 }, + { 0x0e000903, 0x28000000 }, + { 0x0e800904, 0x1c000035 }, + { 0x0e00093c, 0x30000000 }, + { 0x0e00093d, 0x1c000000 }, + { 0x0e80093e, 0x28000002 }, + { 0x0e800941, 0x30000007 }, + { 0x0e800949, 0x28000003 }, + { 0x0e00094d, 0x30000000 }, + { 0x0e000950, 0x1c000000 }, + { 0x0e800951, 0x30000003 }, + { 0x0e800958, 0x1c000009 }, + { 0x0e800962, 0x30000001 }, + { 0x09800964, 0x54000001 }, + { 0x0e800966, 0x34000009 }, + { 0x09000970, 0x54000000 }, + { 0x0e80097b, 0x1c000004 }, + { 0x02000981, 0x30000000 }, + { 0x02800982, 0x28000001 }, + { 0x02800985, 0x1c000007 }, + { 0x0280098f, 0x1c000001 }, + { 0x02800993, 0x1c000015 }, + { 0x028009aa, 0x1c000006 }, + { 0x020009b2, 0x1c000000 }, + { 0x028009b6, 0x1c000003 }, + { 0x020009bc, 0x30000000 }, + { 0x020009bd, 0x1c000000 }, + { 0x028009be, 0x28000002 }, + { 0x028009c1, 0x30000003 }, + { 0x028009c7, 0x28000001 }, + { 0x028009cb, 0x28000001 }, + { 0x020009cd, 0x30000000 }, + { 0x020009ce, 0x1c000000 }, + { 0x020009d7, 0x28000000 }, + { 0x028009dc, 0x1c000001 }, + { 0x028009df, 0x1c000002 }, + { 0x028009e2, 0x30000001 }, + { 0x028009e6, 0x34000009 }, + { 0x028009f0, 0x1c000001 }, + { 0x028009f2, 0x5c000001 }, + { 0x028009f4, 0x3c000005 }, + { 0x020009fa, 0x68000000 }, + { 0x15800a01, 0x30000001 }, + { 0x15000a03, 0x28000000 }, + { 0x15800a05, 0x1c000005 }, + { 0x15800a0f, 0x1c000001 }, + { 0x15800a13, 0x1c000015 }, + { 0x15800a2a, 0x1c000006 }, + { 0x15800a32, 0x1c000001 }, + { 0x15800a35, 0x1c000001 }, + { 0x15800a38, 0x1c000001 }, + { 0x15000a3c, 0x30000000 }, + { 0x15800a3e, 0x28000002 }, + { 0x15800a41, 0x30000001 }, + { 0x15800a47, 0x30000001 }, + { 0x15800a4b, 0x30000002 }, + { 0x15800a59, 0x1c000003 }, + { 0x15000a5e, 0x1c000000 }, + { 0x15800a66, 0x34000009 }, + { 0x15800a70, 0x30000001 }, + { 0x15800a72, 0x1c000002 }, + { 0x14800a81, 0x30000001 }, + { 0x14000a83, 0x28000000 }, + { 0x14800a85, 0x1c000008 }, + { 0x14800a8f, 0x1c000002 }, + { 0x14800a93, 0x1c000015 }, + { 0x14800aaa, 0x1c000006 }, + { 0x14800ab2, 0x1c000001 }, + { 0x14800ab5, 0x1c000004 }, + { 0x14000abc, 0x30000000 }, + { 0x14000abd, 0x1c000000 }, + { 0x14800abe, 0x28000002 }, + { 0x14800ac1, 0x30000004 }, + { 0x14800ac7, 0x30000001 }, + { 0x14000ac9, 0x28000000 }, + { 0x14800acb, 0x28000001 }, + { 0x14000acd, 0x30000000 }, + { 0x14000ad0, 0x1c000000 }, + { 0x14800ae0, 0x1c000001 }, + { 0x14800ae2, 0x30000001 }, + { 0x14800ae6, 0x34000009 }, + { 0x14000af1, 0x5c000000 }, + { 0x2b000b01, 0x30000000 }, + { 0x2b800b02, 0x28000001 }, + { 0x2b800b05, 0x1c000007 }, + { 0x2b800b0f, 0x1c000001 }, + { 0x2b800b13, 0x1c000015 }, + { 0x2b800b2a, 0x1c000006 }, + { 0x2b800b32, 0x1c000001 }, + { 0x2b800b35, 0x1c000004 }, + { 0x2b000b3c, 0x30000000 }, + { 0x2b000b3d, 0x1c000000 }, + { 0x2b000b3e, 0x28000000 }, + { 0x2b000b3f, 0x30000000 }, + { 0x2b000b40, 0x28000000 }, + { 0x2b800b41, 0x30000002 }, + { 0x2b800b47, 0x28000001 }, + { 0x2b800b4b, 0x28000001 }, + { 0x2b000b4d, 0x30000000 }, + { 0x2b000b56, 0x30000000 }, + { 0x2b000b57, 0x28000000 }, + { 0x2b800b5c, 0x1c000001 }, + { 0x2b800b5f, 0x1c000002 }, + { 0x2b800b66, 0x34000009 }, + { 0x2b000b70, 0x68000000 }, + { 0x2b000b71, 0x1c000000 }, + { 0x35000b82, 0x30000000 }, + { 0x35000b83, 0x1c000000 }, + { 0x35800b85, 0x1c000005 }, + { 0x35800b8e, 0x1c000002 }, + { 0x35800b92, 0x1c000003 }, + { 0x35800b99, 0x1c000001 }, + { 0x35000b9c, 0x1c000000 }, + { 0x35800b9e, 0x1c000001 }, + { 0x35800ba3, 0x1c000001 }, + { 0x35800ba8, 0x1c000002 }, + { 0x35800bae, 0x1c00000b }, + { 0x35800bbe, 0x28000001 }, + { 0x35000bc0, 0x30000000 }, + { 0x35800bc1, 0x28000001 }, + { 0x35800bc6, 0x28000002 }, + { 0x35800bca, 0x28000002 }, + { 0x35000bcd, 0x30000000 }, + { 0x35000bd7, 0x28000000 }, + { 0x35800be6, 0x34000009 }, + { 0x35800bf0, 0x3c000002 }, + { 0x35800bf3, 0x68000005 }, + { 0x35000bf9, 0x5c000000 }, + { 0x35000bfa, 0x68000000 }, + { 0x36800c01, 0x28000002 }, + { 0x36800c05, 0x1c000007 }, + { 0x36800c0e, 0x1c000002 }, + { 0x36800c12, 0x1c000016 }, + { 0x36800c2a, 0x1c000009 }, + { 0x36800c35, 0x1c000004 }, + { 0x36800c3e, 0x30000002 }, + { 0x36800c41, 0x28000003 }, + { 0x36800c46, 0x30000002 }, + { 0x36800c4a, 0x30000003 }, + { 0x36800c55, 0x30000001 }, + { 0x36800c60, 0x1c000001 }, + { 0x36800c66, 0x34000009 }, + { 0x1c800c82, 0x28000001 }, + { 0x1c800c85, 0x1c000007 }, + { 0x1c800c8e, 0x1c000002 }, + { 0x1c800c92, 0x1c000016 }, + { 0x1c800caa, 0x1c000009 }, + { 0x1c800cb5, 0x1c000004 }, + { 0x1c000cbc, 0x30000000 }, + { 0x1c000cbd, 0x1c000000 }, + { 0x1c000cbe, 0x28000000 }, + { 0x1c000cbf, 0x30000000 }, + { 0x1c800cc0, 0x28000004 }, + { 0x1c000cc6, 0x30000000 }, + { 0x1c800cc7, 0x28000001 }, + { 0x1c800cca, 0x28000001 }, + { 0x1c800ccc, 0x30000001 }, + { 0x1c800cd5, 0x28000001 }, + { 0x1c000cde, 0x1c000000 }, + { 0x1c800ce0, 0x1c000001 }, + { 0x1c800ce2, 0x30000001 }, + { 0x1c800ce6, 0x34000009 }, + { 0x1c800cf1, 0x68000001 }, + { 0x24800d02, 0x28000001 }, + { 0x24800d05, 0x1c000007 }, + { 0x24800d0e, 0x1c000002 }, + { 0x24800d12, 0x1c000016 }, + { 0x24800d2a, 0x1c00000f }, + { 0x24800d3e, 0x28000002 }, + { 0x24800d41, 0x30000002 }, + { 0x24800d46, 0x28000002 }, + { 0x24800d4a, 0x28000002 }, + { 0x24000d4d, 0x30000000 }, + { 0x24000d57, 0x28000000 }, + { 0x24800d60, 0x1c000001 }, + { 0x24800d66, 0x34000009 }, + { 0x2f800d82, 0x28000001 }, + { 0x2f800d85, 0x1c000011 }, + { 0x2f800d9a, 0x1c000017 }, + { 0x2f800db3, 0x1c000008 }, + { 0x2f000dbd, 0x1c000000 }, + { 0x2f800dc0, 0x1c000006 }, + { 0x2f000dca, 0x30000000 }, + { 0x2f800dcf, 0x28000002 }, + { 0x2f800dd2, 0x30000002 }, + { 0x2f000dd6, 0x30000000 }, + { 0x2f800dd8, 0x28000007 }, + { 0x2f800df2, 0x28000001 }, + { 0x2f000df4, 0x54000000 }, + { 0x38800e01, 0x1c00002f }, + { 0x38000e31, 0x30000000 }, + { 0x38800e32, 0x1c000001 }, + { 0x38800e34, 0x30000006 }, + { 0x09000e3f, 0x5c000000 }, + { 0x38800e40, 0x1c000005 }, + { 0x38000e46, 0x18000000 }, + { 0x38800e47, 0x30000007 }, + { 0x38000e4f, 0x54000000 }, + { 0x38800e50, 0x34000009 }, + { 0x38800e5a, 0x54000001 }, + { 0x20800e81, 0x1c000001 }, + { 0x20000e84, 0x1c000000 }, + { 0x20800e87, 0x1c000001 }, + { 0x20000e8a, 0x1c000000 }, + { 0x20000e8d, 0x1c000000 }, + { 0x20800e94, 0x1c000003 }, + { 0x20800e99, 0x1c000006 }, + { 0x20800ea1, 0x1c000002 }, + { 0x20000ea5, 0x1c000000 }, + { 0x20000ea7, 0x1c000000 }, + { 0x20800eaa, 0x1c000001 }, + { 0x20800ead, 0x1c000003 }, + { 0x20000eb1, 0x30000000 }, + { 0x20800eb2, 0x1c000001 }, + { 0x20800eb4, 0x30000005 }, + { 0x20800ebb, 0x30000001 }, + { 0x20000ebd, 0x1c000000 }, + { 0x20800ec0, 0x1c000004 }, + { 0x20000ec6, 0x18000000 }, + { 0x20800ec8, 0x30000005 }, + { 0x20800ed0, 0x34000009 }, + { 0x20800edc, 0x1c000001 }, + { 0x39000f00, 0x1c000000 }, + { 0x39800f01, 0x68000002 }, + { 0x39800f04, 0x5400000e }, + { 0x39800f13, 0x68000004 }, + { 0x39800f18, 0x30000001 }, + { 0x39800f1a, 0x68000005 }, + { 0x39800f20, 0x34000009 }, + { 0x39800f2a, 0x3c000009 }, + { 0x39000f34, 0x68000000 }, + { 0x39000f35, 0x30000000 }, + { 0x39000f36, 0x68000000 }, + { 0x39000f37, 0x30000000 }, + { 0x39000f38, 0x68000000 }, + { 0x39000f39, 0x30000000 }, + { 0x39000f3a, 0x58000000 }, + { 0x39000f3b, 0x48000000 }, + { 0x39000f3c, 0x58000000 }, + { 0x39000f3d, 0x48000000 }, + { 0x39800f3e, 0x28000001 }, + { 0x39800f40, 0x1c000007 }, + { 0x39800f49, 0x1c000021 }, + { 0x39800f71, 0x3000000d }, + { 0x39000f7f, 0x28000000 }, + { 0x39800f80, 0x30000004 }, + { 0x39000f85, 0x54000000 }, + { 0x39800f86, 0x30000001 }, + { 0x39800f88, 0x1c000003 }, + { 0x39800f90, 0x30000007 }, + { 0x39800f99, 0x30000023 }, + { 0x39800fbe, 0x68000007 }, + { 0x39000fc6, 0x30000000 }, + { 0x39800fc7, 0x68000005 }, + { 0x39000fcf, 0x68000000 }, + { 0x39800fd0, 0x54000001 }, + { 0x26801000, 0x1c000021 }, + { 0x26801023, 0x1c000004 }, + { 0x26801029, 0x1c000001 }, + { 0x2600102c, 0x28000000 }, + { 0x2680102d, 0x30000003 }, + { 0x26001031, 0x28000000 }, + { 0x26001032, 0x30000000 }, + { 0x26801036, 0x30000001 }, + { 0x26001038, 0x28000000 }, + { 0x26001039, 0x30000000 }, + { 0x26801040, 0x34000009 }, + { 0x2680104a, 0x54000005 }, + { 0x26801050, 0x1c000005 }, + { 0x26801056, 0x28000001 }, + { 0x26801058, 0x30000001 }, + { 0x100010a0, 0x24001c60 }, + { 0x100010a1, 0x24001c60 }, + { 0x100010a2, 0x24001c60 }, + { 0x100010a3, 0x24001c60 }, + { 0x100010a4, 0x24001c60 }, + { 0x100010a5, 0x24001c60 }, + { 0x100010a6, 0x24001c60 }, + { 0x100010a7, 0x24001c60 }, + { 0x100010a8, 0x24001c60 }, + { 0x100010a9, 0x24001c60 }, + { 0x100010aa, 0x24001c60 }, + { 0x100010ab, 0x24001c60 }, + { 0x100010ac, 0x24001c60 }, + { 0x100010ad, 0x24001c60 }, + { 0x100010ae, 0x24001c60 }, + { 0x100010af, 0x24001c60 }, + { 0x100010b0, 0x24001c60 }, + { 0x100010b1, 0x24001c60 }, + { 0x100010b2, 0x24001c60 }, + { 0x100010b3, 0x24001c60 }, + { 0x100010b4, 0x24001c60 }, + { 0x100010b5, 0x24001c60 }, + { 0x100010b6, 0x24001c60 }, + { 0x100010b7, 0x24001c60 }, + { 0x100010b8, 0x24001c60 }, + { 0x100010b9, 0x24001c60 }, + { 0x100010ba, 0x24001c60 }, + { 0x100010bb, 0x24001c60 }, + { 0x100010bc, 0x24001c60 }, + { 0x100010bd, 0x24001c60 }, + { 0x100010be, 0x24001c60 }, + { 0x100010bf, 0x24001c60 }, + { 0x100010c0, 0x24001c60 }, + { 0x100010c1, 0x24001c60 }, + { 0x100010c2, 0x24001c60 }, + { 0x100010c3, 0x24001c60 }, + { 0x100010c4, 0x24001c60 }, + { 0x100010c5, 0x24001c60 }, + { 0x108010d0, 0x1c00002a }, + { 0x090010fb, 0x54000000 }, + { 0x100010fc, 0x18000000 }, + { 0x17801100, 0x1c000059 }, + { 0x1780115f, 0x1c000043 }, + { 0x178011a8, 0x1c000051 }, + { 0x0f801200, 0x1c000048 }, + { 0x0f80124a, 0x1c000003 }, + { 0x0f801250, 0x1c000006 }, + { 0x0f001258, 0x1c000000 }, + { 0x0f80125a, 0x1c000003 }, + { 0x0f801260, 0x1c000028 }, + { 0x0f80128a, 0x1c000003 }, + { 0x0f801290, 0x1c000020 }, + { 0x0f8012b2, 0x1c000003 }, + { 0x0f8012b8, 0x1c000006 }, + { 0x0f0012c0, 0x1c000000 }, + { 0x0f8012c2, 0x1c000003 }, + { 0x0f8012c8, 0x1c00000e }, + { 0x0f8012d8, 0x1c000038 }, + { 0x0f801312, 0x1c000003 }, + { 0x0f801318, 0x1c000042 }, + { 0x0f00135f, 0x30000000 }, + { 0x0f001360, 0x68000000 }, + { 0x0f801361, 0x54000007 }, + { 0x0f801369, 0x3c000013 }, + { 0x0f801380, 0x1c00000f }, + { 0x0f801390, 0x68000009 }, + { 0x088013a0, 0x1c000054 }, + { 0x07801401, 0x1c00026b }, + { 0x0780166d, 0x54000001 }, + { 0x0780166f, 0x1c000007 }, + { 0x28001680, 0x74000000 }, + { 0x28801681, 0x1c000019 }, + { 0x2800169b, 0x58000000 }, + { 0x2800169c, 0x48000000 }, + { 0x2d8016a0, 0x1c00004a }, + { 0x098016eb, 0x54000002 }, + { 0x2d8016ee, 0x38000002 }, + { 0x32801700, 0x1c00000c }, + { 0x3280170e, 0x1c000003 }, + { 0x32801712, 0x30000002 }, + { 0x18801720, 0x1c000011 }, + { 0x18801732, 0x30000002 }, + { 0x09801735, 0x54000001 }, + { 0x06801740, 0x1c000011 }, + { 0x06801752, 0x30000001 }, + { 0x33801760, 0x1c00000c }, + { 0x3380176e, 0x1c000002 }, + { 0x33801772, 0x30000001 }, + { 0x1f801780, 0x1c000033 }, + { 0x1f8017b4, 0x04000001 }, + { 0x1f0017b6, 0x28000000 }, + { 0x1f8017b7, 0x30000006 }, + { 0x1f8017be, 0x28000007 }, + { 0x1f0017c6, 0x30000000 }, + { 0x1f8017c7, 0x28000001 }, + { 0x1f8017c9, 0x3000000a }, + { 0x1f8017d4, 0x54000002 }, + { 0x1f0017d7, 0x18000000 }, + { 0x1f8017d8, 0x54000002 }, + { 0x1f0017db, 0x5c000000 }, + { 0x1f0017dc, 0x1c000000 }, + { 0x1f0017dd, 0x30000000 }, + { 0x1f8017e0, 0x34000009 }, + { 0x1f8017f0, 0x3c000009 }, + { 0x25801800, 0x54000005 }, + { 0x25001806, 0x44000000 }, + { 0x25801807, 0x54000003 }, + { 0x2580180b, 0x30000002 }, + { 0x2500180e, 0x74000000 }, + { 0x25801810, 0x34000009 }, + { 0x25801820, 0x1c000022 }, + { 0x25001843, 0x18000000 }, + { 0x25801844, 0x1c000033 }, + { 0x25801880, 0x1c000028 }, + { 0x250018a9, 0x30000000 }, + { 0x22801900, 0x1c00001c }, + { 0x22801920, 0x30000002 }, + { 0x22801923, 0x28000003 }, + { 0x22801927, 0x30000001 }, + { 0x22801929, 0x28000002 }, + { 0x22801930, 0x28000001 }, + { 0x22001932, 0x30000000 }, + { 0x22801933, 0x28000005 }, + { 0x22801939, 0x30000002 }, + { 0x22001940, 0x68000000 }, + { 0x22801944, 0x54000001 }, + { 0x22801946, 0x34000009 }, + { 0x34801950, 0x1c00001d }, + { 0x34801970, 0x1c000004 }, + { 0x27801980, 0x1c000029 }, + { 0x278019b0, 0x28000010 }, + { 0x278019c1, 0x1c000006 }, + { 0x278019c8, 0x28000001 }, + { 0x278019d0, 0x34000009 }, + { 0x278019de, 0x54000001 }, + { 0x1f8019e0, 0x6800001f }, + { 0x05801a00, 0x1c000016 }, + { 0x05801a17, 0x30000001 }, + { 0x05801a19, 0x28000002 }, + { 0x05801a1e, 0x54000001 }, + { 0x3d801b00, 0x30000003 }, + { 0x3d001b04, 0x28000000 }, + { 0x3d801b05, 0x1c00002e }, + { 0x3d001b34, 0x30000000 }, + { 0x3d001b35, 0x28000000 }, + { 0x3d801b36, 0x30000004 }, + { 0x3d001b3b, 0x28000000 }, + { 0x3d001b3c, 0x30000000 }, + { 0x3d801b3d, 0x28000004 }, + { 0x3d001b42, 0x30000000 }, + { 0x3d801b43, 0x28000001 }, + { 0x3d801b45, 0x1c000006 }, + { 0x3d801b50, 0x34000009 }, + { 0x3d801b5a, 0x54000006 }, + { 0x3d801b61, 0x68000009 }, + { 0x3d801b6b, 0x30000008 }, + { 0x3d801b74, 0x68000008 }, + { 0x21801d00, 0x1400002b }, + { 0x21801d2c, 0x18000035 }, + { 0x21801d62, 0x14000015 }, + { 0x0c001d78, 0x18000000 }, + { 0x21801d79, 0x14000003 }, + { 0x21001d7d, 0x14000ee6 }, + { 0x21801d7e, 0x1400001c }, + { 0x21801d9b, 0x18000024 }, + { 0x1b801dc0, 0x3000000a }, + { 0x1b801dfe, 0x30000001 }, + { 0x21001e00, 0x24000001 }, + { 0x21001e01, 0x1400ffff }, + { 0x21001e02, 0x24000001 }, + { 0x21001e03, 0x1400ffff }, + { 0x21001e04, 0x24000001 }, + { 0x21001e05, 0x1400ffff }, + { 0x21001e06, 0x24000001 }, + { 0x21001e07, 0x1400ffff }, + { 0x21001e08, 0x24000001 }, + { 0x21001e09, 0x1400ffff }, + { 0x21001e0a, 0x24000001 }, + { 0x21001e0b, 0x1400ffff }, + { 0x21001e0c, 0x24000001 }, + { 0x21001e0d, 0x1400ffff }, + { 0x21001e0e, 0x24000001 }, + { 0x21001e0f, 0x1400ffff }, + { 0x21001e10, 0x24000001 }, + { 0x21001e11, 0x1400ffff }, + { 0x21001e12, 0x24000001 }, + { 0x21001e13, 0x1400ffff }, + { 0x21001e14, 0x24000001 }, + { 0x21001e15, 0x1400ffff }, + { 0x21001e16, 0x24000001 }, + { 0x21001e17, 0x1400ffff }, + { 0x21001e18, 0x24000001 }, + { 0x21001e19, 0x1400ffff }, + { 0x21001e1a, 0x24000001 }, + { 0x21001e1b, 0x1400ffff }, + { 0x21001e1c, 0x24000001 }, + { 0x21001e1d, 0x1400ffff }, + { 0x21001e1e, 0x24000001 }, + { 0x21001e1f, 0x1400ffff }, + { 0x21001e20, 0x24000001 }, + { 0x21001e21, 0x1400ffff }, + { 0x21001e22, 0x24000001 }, + { 0x21001e23, 0x1400ffff }, + { 0x21001e24, 0x24000001 }, + { 0x21001e25, 0x1400ffff }, + { 0x21001e26, 0x24000001 }, + { 0x21001e27, 0x1400ffff }, + { 0x21001e28, 0x24000001 }, + { 0x21001e29, 0x1400ffff }, + { 0x21001e2a, 0x24000001 }, + { 0x21001e2b, 0x1400ffff }, + { 0x21001e2c, 0x24000001 }, + { 0x21001e2d, 0x1400ffff }, + { 0x21001e2e, 0x24000001 }, + { 0x21001e2f, 0x1400ffff }, + { 0x21001e30, 0x24000001 }, + { 0x21001e31, 0x1400ffff }, + { 0x21001e32, 0x24000001 }, + { 0x21001e33, 0x1400ffff }, + { 0x21001e34, 0x24000001 }, + { 0x21001e35, 0x1400ffff }, + { 0x21001e36, 0x24000001 }, + { 0x21001e37, 0x1400ffff }, + { 0x21001e38, 0x24000001 }, + { 0x21001e39, 0x1400ffff }, + { 0x21001e3a, 0x24000001 }, + { 0x21001e3b, 0x1400ffff }, + { 0x21001e3c, 0x24000001 }, + { 0x21001e3d, 0x1400ffff }, + { 0x21001e3e, 0x24000001 }, + { 0x21001e3f, 0x1400ffff }, + { 0x21001e40, 0x24000001 }, + { 0x21001e41, 0x1400ffff }, + { 0x21001e42, 0x24000001 }, + { 0x21001e43, 0x1400ffff }, + { 0x21001e44, 0x24000001 }, + { 0x21001e45, 0x1400ffff }, + { 0x21001e46, 0x24000001 }, + { 0x21001e47, 0x1400ffff }, + { 0x21001e48, 0x24000001 }, + { 0x21001e49, 0x1400ffff }, + { 0x21001e4a, 0x24000001 }, + { 0x21001e4b, 0x1400ffff }, + { 0x21001e4c, 0x24000001 }, + { 0x21001e4d, 0x1400ffff }, + { 0x21001e4e, 0x24000001 }, + { 0x21001e4f, 0x1400ffff }, + { 0x21001e50, 0x24000001 }, + { 0x21001e51, 0x1400ffff }, + { 0x21001e52, 0x24000001 }, + { 0x21001e53, 0x1400ffff }, + { 0x21001e54, 0x24000001 }, + { 0x21001e55, 0x1400ffff }, + { 0x21001e56, 0x24000001 }, + { 0x21001e57, 0x1400ffff }, + { 0x21001e58, 0x24000001 }, + { 0x21001e59, 0x1400ffff }, + { 0x21001e5a, 0x24000001 }, + { 0x21001e5b, 0x1400ffff }, + { 0x21001e5c, 0x24000001 }, + { 0x21001e5d, 0x1400ffff }, + { 0x21001e5e, 0x24000001 }, + { 0x21001e5f, 0x1400ffff }, + { 0x21001e60, 0x24000001 }, + { 0x21001e61, 0x1400ffff }, + { 0x21001e62, 0x24000001 }, + { 0x21001e63, 0x1400ffff }, + { 0x21001e64, 0x24000001 }, + { 0x21001e65, 0x1400ffff }, + { 0x21001e66, 0x24000001 }, + { 0x21001e67, 0x1400ffff }, + { 0x21001e68, 0x24000001 }, + { 0x21001e69, 0x1400ffff }, + { 0x21001e6a, 0x24000001 }, + { 0x21001e6b, 0x1400ffff }, + { 0x21001e6c, 0x24000001 }, + { 0x21001e6d, 0x1400ffff }, + { 0x21001e6e, 0x24000001 }, + { 0x21001e6f, 0x1400ffff }, + { 0x21001e70, 0x24000001 }, + { 0x21001e71, 0x1400ffff }, + { 0x21001e72, 0x24000001 }, + { 0x21001e73, 0x1400ffff }, + { 0x21001e74, 0x24000001 }, + { 0x21001e75, 0x1400ffff }, + { 0x21001e76, 0x24000001 }, + { 0x21001e77, 0x1400ffff }, + { 0x21001e78, 0x24000001 }, + { 0x21001e79, 0x1400ffff }, + { 0x21001e7a, 0x24000001 }, + { 0x21001e7b, 0x1400ffff }, + { 0x21001e7c, 0x24000001 }, + { 0x21001e7d, 0x1400ffff }, + { 0x21001e7e, 0x24000001 }, + { 0x21001e7f, 0x1400ffff }, + { 0x21001e80, 0x24000001 }, + { 0x21001e81, 0x1400ffff }, + { 0x21001e82, 0x24000001 }, + { 0x21001e83, 0x1400ffff }, + { 0x21001e84, 0x24000001 }, + { 0x21001e85, 0x1400ffff }, + { 0x21001e86, 0x24000001 }, + { 0x21001e87, 0x1400ffff }, + { 0x21001e88, 0x24000001 }, + { 0x21001e89, 0x1400ffff }, + { 0x21001e8a, 0x24000001 }, + { 0x21001e8b, 0x1400ffff }, + { 0x21001e8c, 0x24000001 }, + { 0x21001e8d, 0x1400ffff }, + { 0x21001e8e, 0x24000001 }, + { 0x21001e8f, 0x1400ffff }, + { 0x21001e90, 0x24000001 }, + { 0x21001e91, 0x1400ffff }, + { 0x21001e92, 0x24000001 }, + { 0x21001e93, 0x1400ffff }, + { 0x21001e94, 0x24000001 }, + { 0x21001e95, 0x1400ffff }, + { 0x21801e96, 0x14000004 }, + { 0x21001e9b, 0x1400ffc5 }, + { 0x21001ea0, 0x24000001 }, + { 0x21001ea1, 0x1400ffff }, + { 0x21001ea2, 0x24000001 }, + { 0x21001ea3, 0x1400ffff }, + { 0x21001ea4, 0x24000001 }, + { 0x21001ea5, 0x1400ffff }, + { 0x21001ea6, 0x24000001 }, + { 0x21001ea7, 0x1400ffff }, + { 0x21001ea8, 0x24000001 }, + { 0x21001ea9, 0x1400ffff }, + { 0x21001eaa, 0x24000001 }, + { 0x21001eab, 0x1400ffff }, + { 0x21001eac, 0x24000001 }, + { 0x21001ead, 0x1400ffff }, + { 0x21001eae, 0x24000001 }, + { 0x21001eaf, 0x1400ffff }, + { 0x21001eb0, 0x24000001 }, + { 0x21001eb1, 0x1400ffff }, + { 0x21001eb2, 0x24000001 }, + { 0x21001eb3, 0x1400ffff }, + { 0x21001eb4, 0x24000001 }, + { 0x21001eb5, 0x1400ffff }, + { 0x21001eb6, 0x24000001 }, + { 0x21001eb7, 0x1400ffff }, + { 0x21001eb8, 0x24000001 }, + { 0x21001eb9, 0x1400ffff }, + { 0x21001eba, 0x24000001 }, + { 0x21001ebb, 0x1400ffff }, + { 0x21001ebc, 0x24000001 }, + { 0x21001ebd, 0x1400ffff }, + { 0x21001ebe, 0x24000001 }, + { 0x21001ebf, 0x1400ffff }, + { 0x21001ec0, 0x24000001 }, + { 0x21001ec1, 0x1400ffff }, + { 0x21001ec2, 0x24000001 }, + { 0x21001ec3, 0x1400ffff }, + { 0x21001ec4, 0x24000001 }, + { 0x21001ec5, 0x1400ffff }, + { 0x21001ec6, 0x24000001 }, + { 0x21001ec7, 0x1400ffff }, + { 0x21001ec8, 0x24000001 }, + { 0x21001ec9, 0x1400ffff }, + { 0x21001eca, 0x24000001 }, + { 0x21001ecb, 0x1400ffff }, + { 0x21001ecc, 0x24000001 }, + { 0x21001ecd, 0x1400ffff }, + { 0x21001ece, 0x24000001 }, + { 0x21001ecf, 0x1400ffff }, + { 0x21001ed0, 0x24000001 }, + { 0x21001ed1, 0x1400ffff }, + { 0x21001ed2, 0x24000001 }, + { 0x21001ed3, 0x1400ffff }, + { 0x21001ed4, 0x24000001 }, + { 0x21001ed5, 0x1400ffff }, + { 0x21001ed6, 0x24000001 }, + { 0x21001ed7, 0x1400ffff }, + { 0x21001ed8, 0x24000001 }, + { 0x21001ed9, 0x1400ffff }, + { 0x21001eda, 0x24000001 }, + { 0x21001edb, 0x1400ffff }, + { 0x21001edc, 0x24000001 }, + { 0x21001edd, 0x1400ffff }, + { 0x21001ede, 0x24000001 }, + { 0x21001edf, 0x1400ffff }, + { 0x21001ee0, 0x24000001 }, + { 0x21001ee1, 0x1400ffff }, + { 0x21001ee2, 0x24000001 }, + { 0x21001ee3, 0x1400ffff }, + { 0x21001ee4, 0x24000001 }, + { 0x21001ee5, 0x1400ffff }, + { 0x21001ee6, 0x24000001 }, + { 0x21001ee7, 0x1400ffff }, + { 0x21001ee8, 0x24000001 }, + { 0x21001ee9, 0x1400ffff }, + { 0x21001eea, 0x24000001 }, + { 0x21001eeb, 0x1400ffff }, + { 0x21001eec, 0x24000001 }, + { 0x21001eed, 0x1400ffff }, + { 0x21001eee, 0x24000001 }, + { 0x21001eef, 0x1400ffff }, + { 0x21001ef0, 0x24000001 }, + { 0x21001ef1, 0x1400ffff }, + { 0x21001ef2, 0x24000001 }, + { 0x21001ef3, 0x1400ffff }, + { 0x21001ef4, 0x24000001 }, + { 0x21001ef5, 0x1400ffff }, + { 0x21001ef6, 0x24000001 }, + { 0x21001ef7, 0x1400ffff }, + { 0x21001ef8, 0x24000001 }, + { 0x21001ef9, 0x1400ffff }, + { 0x13001f00, 0x14000008 }, + { 0x13001f01, 0x14000008 }, + { 0x13001f02, 0x14000008 }, + { 0x13001f03, 0x14000008 }, + { 0x13001f04, 0x14000008 }, + { 0x13001f05, 0x14000008 }, + { 0x13001f06, 0x14000008 }, + { 0x13001f07, 0x14000008 }, + { 0x13001f08, 0x2400fff8 }, + { 0x13001f09, 0x2400fff8 }, + { 0x13001f0a, 0x2400fff8 }, + { 0x13001f0b, 0x2400fff8 }, + { 0x13001f0c, 0x2400fff8 }, + { 0x13001f0d, 0x2400fff8 }, + { 0x13001f0e, 0x2400fff8 }, + { 0x13001f0f, 0x2400fff8 }, + { 0x13001f10, 0x14000008 }, + { 0x13001f11, 0x14000008 }, + { 0x13001f12, 0x14000008 }, + { 0x13001f13, 0x14000008 }, + { 0x13001f14, 0x14000008 }, + { 0x13001f15, 0x14000008 }, + { 0x13001f18, 0x2400fff8 }, + { 0x13001f19, 0x2400fff8 }, + { 0x13001f1a, 0x2400fff8 }, + { 0x13001f1b, 0x2400fff8 }, + { 0x13001f1c, 0x2400fff8 }, + { 0x13001f1d, 0x2400fff8 }, + { 0x13001f20, 0x14000008 }, + { 0x13001f21, 0x14000008 }, + { 0x13001f22, 0x14000008 }, + { 0x13001f23, 0x14000008 }, + { 0x13001f24, 0x14000008 }, + { 0x13001f25, 0x14000008 }, + { 0x13001f26, 0x14000008 }, + { 0x13001f27, 0x14000008 }, + { 0x13001f28, 0x2400fff8 }, + { 0x13001f29, 0x2400fff8 }, + { 0x13001f2a, 0x2400fff8 }, + { 0x13001f2b, 0x2400fff8 }, + { 0x13001f2c, 0x2400fff8 }, + { 0x13001f2d, 0x2400fff8 }, + { 0x13001f2e, 0x2400fff8 }, + { 0x13001f2f, 0x2400fff8 }, + { 0x13001f30, 0x14000008 }, + { 0x13001f31, 0x14000008 }, + { 0x13001f32, 0x14000008 }, + { 0x13001f33, 0x14000008 }, + { 0x13001f34, 0x14000008 }, + { 0x13001f35, 0x14000008 }, + { 0x13001f36, 0x14000008 }, + { 0x13001f37, 0x14000008 }, + { 0x13001f38, 0x2400fff8 }, + { 0x13001f39, 0x2400fff8 }, + { 0x13001f3a, 0x2400fff8 }, + { 0x13001f3b, 0x2400fff8 }, + { 0x13001f3c, 0x2400fff8 }, + { 0x13001f3d, 0x2400fff8 }, + { 0x13001f3e, 0x2400fff8 }, + { 0x13001f3f, 0x2400fff8 }, + { 0x13001f40, 0x14000008 }, + { 0x13001f41, 0x14000008 }, + { 0x13001f42, 0x14000008 }, + { 0x13001f43, 0x14000008 }, + { 0x13001f44, 0x14000008 }, + { 0x13001f45, 0x14000008 }, + { 0x13001f48, 0x2400fff8 }, + { 0x13001f49, 0x2400fff8 }, + { 0x13001f4a, 0x2400fff8 }, + { 0x13001f4b, 0x2400fff8 }, + { 0x13001f4c, 0x2400fff8 }, + { 0x13001f4d, 0x2400fff8 }, + { 0x13001f50, 0x14000000 }, + { 0x13001f51, 0x14000008 }, + { 0x13001f52, 0x14000000 }, + { 0x13001f53, 0x14000008 }, + { 0x13001f54, 0x14000000 }, + { 0x13001f55, 0x14000008 }, + { 0x13001f56, 0x14000000 }, + { 0x13001f57, 0x14000008 }, + { 0x13001f59, 0x2400fff8 }, + { 0x13001f5b, 0x2400fff8 }, + { 0x13001f5d, 0x2400fff8 }, + { 0x13001f5f, 0x2400fff8 }, + { 0x13001f60, 0x14000008 }, + { 0x13001f61, 0x14000008 }, + { 0x13001f62, 0x14000008 }, + { 0x13001f63, 0x14000008 }, + { 0x13001f64, 0x14000008 }, + { 0x13001f65, 0x14000008 }, + { 0x13001f66, 0x14000008 }, + { 0x13001f67, 0x14000008 }, + { 0x13001f68, 0x2400fff8 }, + { 0x13001f69, 0x2400fff8 }, + { 0x13001f6a, 0x2400fff8 }, + { 0x13001f6b, 0x2400fff8 }, + { 0x13001f6c, 0x2400fff8 }, + { 0x13001f6d, 0x2400fff8 }, + { 0x13001f6e, 0x2400fff8 }, + { 0x13001f6f, 0x2400fff8 }, + { 0x13001f70, 0x1400004a }, + { 0x13001f71, 0x1400004a }, + { 0x13001f72, 0x14000056 }, + { 0x13001f73, 0x14000056 }, + { 0x13001f74, 0x14000056 }, + { 0x13001f75, 0x14000056 }, + { 0x13001f76, 0x14000064 }, + { 0x13001f77, 0x14000064 }, + { 0x13001f78, 0x14000080 }, + { 0x13001f79, 0x14000080 }, + { 0x13001f7a, 0x14000070 }, + { 0x13001f7b, 0x14000070 }, + { 0x13001f7c, 0x1400007e }, + { 0x13001f7d, 0x1400007e }, + { 0x13001f80, 0x14000008 }, + { 0x13001f81, 0x14000008 }, + { 0x13001f82, 0x14000008 }, + { 0x13001f83, 0x14000008 }, + { 0x13001f84, 0x14000008 }, + { 0x13001f85, 0x14000008 }, + { 0x13001f86, 0x14000008 }, + { 0x13001f87, 0x14000008 }, + { 0x13001f88, 0x2000fff8 }, + { 0x13001f89, 0x2000fff8 }, + { 0x13001f8a, 0x2000fff8 }, + { 0x13001f8b, 0x2000fff8 }, + { 0x13001f8c, 0x2000fff8 }, + { 0x13001f8d, 0x2000fff8 }, + { 0x13001f8e, 0x2000fff8 }, + { 0x13001f8f, 0x2000fff8 }, + { 0x13001f90, 0x14000008 }, + { 0x13001f91, 0x14000008 }, + { 0x13001f92, 0x14000008 }, + { 0x13001f93, 0x14000008 }, + { 0x13001f94, 0x14000008 }, + { 0x13001f95, 0x14000008 }, + { 0x13001f96, 0x14000008 }, + { 0x13001f97, 0x14000008 }, + { 0x13001f98, 0x2000fff8 }, + { 0x13001f99, 0x2000fff8 }, + { 0x13001f9a, 0x2000fff8 }, + { 0x13001f9b, 0x2000fff8 }, + { 0x13001f9c, 0x2000fff8 }, + { 0x13001f9d, 0x2000fff8 }, + { 0x13001f9e, 0x2000fff8 }, + { 0x13001f9f, 0x2000fff8 }, + { 0x13001fa0, 0x14000008 }, + { 0x13001fa1, 0x14000008 }, + { 0x13001fa2, 0x14000008 }, + { 0x13001fa3, 0x14000008 }, + { 0x13001fa4, 0x14000008 }, + { 0x13001fa5, 0x14000008 }, + { 0x13001fa6, 0x14000008 }, + { 0x13001fa7, 0x14000008 }, + { 0x13001fa8, 0x2000fff8 }, + { 0x13001fa9, 0x2000fff8 }, + { 0x13001faa, 0x2000fff8 }, + { 0x13001fab, 0x2000fff8 }, + { 0x13001fac, 0x2000fff8 }, + { 0x13001fad, 0x2000fff8 }, + { 0x13001fae, 0x2000fff8 }, + { 0x13001faf, 0x2000fff8 }, + { 0x13001fb0, 0x14000008 }, + { 0x13001fb1, 0x14000008 }, + { 0x13001fb2, 0x14000000 }, + { 0x13001fb3, 0x14000009 }, + { 0x13001fb4, 0x14000000 }, + { 0x13801fb6, 0x14000001 }, + { 0x13001fb8, 0x2400fff8 }, + { 0x13001fb9, 0x2400fff8 }, + { 0x13001fba, 0x2400ffb6 }, + { 0x13001fbb, 0x2400ffb6 }, + { 0x13001fbc, 0x2000fff7 }, + { 0x13001fbd, 0x60000000 }, + { 0x13001fbe, 0x1400e3db }, + { 0x13801fbf, 0x60000002 }, + { 0x13001fc2, 0x14000000 }, + { 0x13001fc3, 0x14000009 }, + { 0x13001fc4, 0x14000000 }, + { 0x13801fc6, 0x14000001 }, + { 0x13001fc8, 0x2400ffaa }, + { 0x13001fc9, 0x2400ffaa }, + { 0x13001fca, 0x2400ffaa }, + { 0x13001fcb, 0x2400ffaa }, + { 0x13001fcc, 0x2000fff7 }, + { 0x13801fcd, 0x60000002 }, + { 0x13001fd0, 0x14000008 }, + { 0x13001fd1, 0x14000008 }, + { 0x13801fd2, 0x14000001 }, + { 0x13801fd6, 0x14000001 }, + { 0x13001fd8, 0x2400fff8 }, + { 0x13001fd9, 0x2400fff8 }, + { 0x13001fda, 0x2400ff9c }, + { 0x13001fdb, 0x2400ff9c }, + { 0x13801fdd, 0x60000002 }, + { 0x13001fe0, 0x14000008 }, + { 0x13001fe1, 0x14000008 }, + { 0x13801fe2, 0x14000002 }, + { 0x13001fe5, 0x14000007 }, + { 0x13801fe6, 0x14000001 }, + { 0x13001fe8, 0x2400fff8 }, + { 0x13001fe9, 0x2400fff8 }, + { 0x13001fea, 0x2400ff90 }, + { 0x13001feb, 0x2400ff90 }, + { 0x13001fec, 0x2400fff9 }, + { 0x13801fed, 0x60000002 }, + { 0x13001ff2, 0x14000000 }, + { 0x13001ff3, 0x14000009 }, + { 0x13001ff4, 0x14000000 }, + { 0x13801ff6, 0x14000001 }, + { 0x13001ff8, 0x2400ff80 }, + { 0x13001ff9, 0x2400ff80 }, + { 0x13001ffa, 0x2400ff82 }, + { 0x13001ffb, 0x2400ff82 }, + { 0x13001ffc, 0x2000fff7 }, + { 0x13801ffd, 0x60000001 }, + { 0x09802000, 0x7400000a }, + { 0x0980200b, 0x04000004 }, + { 0x09802010, 0x44000005 }, + { 0x09802016, 0x54000001 }, + { 0x09002018, 0x50000000 }, + { 0x09002019, 0x4c000000 }, + { 0x0900201a, 0x58000000 }, + { 0x0980201b, 0x50000001 }, + { 0x0900201d, 0x4c000000 }, + { 0x0900201e, 0x58000000 }, + { 0x0900201f, 0x50000000 }, + { 0x09802020, 0x54000007 }, + { 0x09002028, 0x6c000000 }, + { 0x09002029, 0x70000000 }, + { 0x0980202a, 0x04000004 }, + { 0x0900202f, 0x74000000 }, + { 0x09802030, 0x54000008 }, + { 0x09002039, 0x50000000 }, + { 0x0900203a, 0x4c000000 }, + { 0x0980203b, 0x54000003 }, + { 0x0980203f, 0x40000001 }, + { 0x09802041, 0x54000002 }, + { 0x09002044, 0x64000000 }, + { 0x09002045, 0x58000000 }, + { 0x09002046, 0x48000000 }, + { 0x09802047, 0x5400000a }, + { 0x09002052, 0x64000000 }, + { 0x09002053, 0x54000000 }, + { 0x09002054, 0x40000000 }, + { 0x09802055, 0x54000009 }, + { 0x0900205f, 0x74000000 }, + { 0x09802060, 0x04000003 }, + { 0x0980206a, 0x04000005 }, + { 0x09002070, 0x3c000000 }, + { 0x21002071, 0x14000000 }, + { 0x09802074, 0x3c000005 }, + { 0x0980207a, 0x64000002 }, + { 0x0900207d, 0x58000000 }, + { 0x0900207e, 0x48000000 }, + { 0x2100207f, 0x14000000 }, + { 0x09802080, 0x3c000009 }, + { 0x0980208a, 0x64000002 }, + { 0x0900208d, 0x58000000 }, + { 0x0900208e, 0x48000000 }, + { 0x21802090, 0x18000004 }, + { 0x098020a0, 0x5c000015 }, + { 0x1b8020d0, 0x3000000c }, + { 0x1b8020dd, 0x2c000003 }, + { 0x1b0020e1, 0x30000000 }, + { 0x1b8020e2, 0x2c000002 }, + { 0x1b8020e5, 0x3000000a }, + { 0x09802100, 0x68000001 }, + { 0x09002102, 0x24000000 }, + { 0x09802103, 0x68000003 }, + { 0x09002107, 0x24000000 }, + { 0x09802108, 0x68000001 }, + { 0x0900210a, 0x14000000 }, + { 0x0980210b, 0x24000002 }, + { 0x0980210e, 0x14000001 }, + { 0x09802110, 0x24000002 }, + { 0x09002113, 0x14000000 }, + { 0x09002114, 0x68000000 }, + { 0x09002115, 0x24000000 }, + { 0x09802116, 0x68000002 }, + { 0x09802119, 0x24000004 }, + { 0x0980211e, 0x68000005 }, + { 0x09002124, 0x24000000 }, + { 0x09002125, 0x68000000 }, + { 0x13002126, 0x2400e2a3 }, + { 0x09002127, 0x68000000 }, + { 0x09002128, 0x24000000 }, + { 0x09002129, 0x68000000 }, + { 0x2100212a, 0x2400df41 }, + { 0x2100212b, 0x2400dfba }, + { 0x0980212c, 0x24000001 }, + { 0x0900212e, 0x68000000 }, + { 0x0900212f, 0x14000000 }, + { 0x09802130, 0x24000001 }, + { 0x21002132, 0x2400001c }, + { 0x09002133, 0x24000000 }, + { 0x09002134, 0x14000000 }, + { 0x09802135, 0x1c000003 }, + { 0x09002139, 0x14000000 }, + { 0x0980213a, 0x68000001 }, + { 0x0980213c, 0x14000001 }, + { 0x0980213e, 0x24000001 }, + { 0x09802140, 0x64000004 }, + { 0x09002145, 0x24000000 }, + { 0x09802146, 0x14000003 }, + { 0x0900214a, 0x68000000 }, + { 0x0900214b, 0x64000000 }, + { 0x0980214c, 0x68000001 }, + { 0x2100214e, 0x1400ffe4 }, + { 0x09802153, 0x3c00000c }, + { 0x09002160, 0x38000010 }, + { 0x09002161, 0x38000010 }, + { 0x09002162, 0x38000010 }, + { 0x09002163, 0x38000010 }, + { 0x09002164, 0x38000010 }, + { 0x09002165, 0x38000010 }, + { 0x09002166, 0x38000010 }, + { 0x09002167, 0x38000010 }, + { 0x09002168, 0x38000010 }, + { 0x09002169, 0x38000010 }, + { 0x0900216a, 0x38000010 }, + { 0x0900216b, 0x38000010 }, + { 0x0900216c, 0x38000010 }, + { 0x0900216d, 0x38000010 }, + { 0x0900216e, 0x38000010 }, + { 0x0900216f, 0x38000010 }, + { 0x09002170, 0x3800fff0 }, + { 0x09002171, 0x3800fff0 }, + { 0x09002172, 0x3800fff0 }, + { 0x09002173, 0x3800fff0 }, + { 0x09002174, 0x3800fff0 }, + { 0x09002175, 0x3800fff0 }, + { 0x09002176, 0x3800fff0 }, + { 0x09002177, 0x3800fff0 }, + { 0x09002178, 0x3800fff0 }, + { 0x09002179, 0x3800fff0 }, + { 0x0900217a, 0x3800fff0 }, + { 0x0900217b, 0x3800fff0 }, + { 0x0900217c, 0x3800fff0 }, + { 0x0900217d, 0x3800fff0 }, + { 0x0900217e, 0x3800fff0 }, + { 0x0900217f, 0x3800fff0 }, + { 0x09802180, 0x38000002 }, + { 0x09002183, 0x24000001 }, + { 0x21002184, 0x1400ffff }, + { 0x09802190, 0x64000004 }, + { 0x09802195, 0x68000004 }, + { 0x0980219a, 0x64000001 }, + { 0x0980219c, 0x68000003 }, + { 0x090021a0, 0x64000000 }, + { 0x098021a1, 0x68000001 }, + { 0x090021a3, 0x64000000 }, + { 0x098021a4, 0x68000001 }, + { 0x090021a6, 0x64000000 }, + { 0x098021a7, 0x68000006 }, + { 0x090021ae, 0x64000000 }, + { 0x098021af, 0x6800001e }, + { 0x098021ce, 0x64000001 }, + { 0x098021d0, 0x68000001 }, + { 0x090021d2, 0x64000000 }, + { 0x090021d3, 0x68000000 }, + { 0x090021d4, 0x64000000 }, + { 0x098021d5, 0x6800001e }, + { 0x098021f4, 0x6400010b }, + { 0x09802300, 0x68000007 }, + { 0x09802308, 0x64000003 }, + { 0x0980230c, 0x68000013 }, + { 0x09802320, 0x64000001 }, + { 0x09802322, 0x68000006 }, + { 0x09002329, 0x58000000 }, + { 0x0900232a, 0x48000000 }, + { 0x0980232b, 0x68000050 }, + { 0x0900237c, 0x64000000 }, + { 0x0980237d, 0x6800001d }, + { 0x0980239b, 0x64000018 }, + { 0x098023b4, 0x68000027 }, + { 0x098023dc, 0x64000005 }, + { 0x098023e2, 0x68000005 }, + { 0x09802400, 0x68000026 }, + { 0x09802440, 0x6800000a }, + { 0x09802460, 0x3c00003b }, + { 0x0980249c, 0x68000019 }, + { 0x090024b6, 0x6800001a }, + { 0x090024b7, 0x6800001a }, + { 0x090024b8, 0x6800001a }, + { 0x090024b9, 0x6800001a }, + { 0x090024ba, 0x6800001a }, + { 0x090024bb, 0x6800001a }, + { 0x090024bc, 0x6800001a }, + { 0x090024bd, 0x6800001a }, + { 0x090024be, 0x6800001a }, + { 0x090024bf, 0x6800001a }, + { 0x090024c0, 0x6800001a }, + { 0x090024c1, 0x6800001a }, + { 0x090024c2, 0x6800001a }, + { 0x090024c3, 0x6800001a }, + { 0x090024c4, 0x6800001a }, + { 0x090024c5, 0x6800001a }, + { 0x090024c6, 0x6800001a }, + { 0x090024c7, 0x6800001a }, + { 0x090024c8, 0x6800001a }, + { 0x090024c9, 0x6800001a }, + { 0x090024ca, 0x6800001a }, + { 0x090024cb, 0x6800001a }, + { 0x090024cc, 0x6800001a }, + { 0x090024cd, 0x6800001a }, + { 0x090024ce, 0x6800001a }, + { 0x090024cf, 0x6800001a }, + { 0x090024d0, 0x6800ffe6 }, + { 0x090024d1, 0x6800ffe6 }, + { 0x090024d2, 0x6800ffe6 }, + { 0x090024d3, 0x6800ffe6 }, + { 0x090024d4, 0x6800ffe6 }, + { 0x090024d5, 0x6800ffe6 }, + { 0x090024d6, 0x6800ffe6 }, + { 0x090024d7, 0x6800ffe6 }, + { 0x090024d8, 0x6800ffe6 }, + { 0x090024d9, 0x6800ffe6 }, + { 0x090024da, 0x6800ffe6 }, + { 0x090024db, 0x6800ffe6 }, + { 0x090024dc, 0x6800ffe6 }, + { 0x090024dd, 0x6800ffe6 }, + { 0x090024de, 0x6800ffe6 }, + { 0x090024df, 0x6800ffe6 }, + { 0x090024e0, 0x6800ffe6 }, + { 0x090024e1, 0x6800ffe6 }, + { 0x090024e2, 0x6800ffe6 }, + { 0x090024e3, 0x6800ffe6 }, + { 0x090024e4, 0x6800ffe6 }, + { 0x090024e5, 0x6800ffe6 }, + { 0x090024e6, 0x6800ffe6 }, + { 0x090024e7, 0x6800ffe6 }, + { 0x090024e8, 0x6800ffe6 }, + { 0x090024e9, 0x6800ffe6 }, + { 0x098024ea, 0x3c000015 }, + { 0x09802500, 0x680000b6 }, + { 0x090025b7, 0x64000000 }, + { 0x098025b8, 0x68000008 }, + { 0x090025c1, 0x64000000 }, + { 0x098025c2, 0x68000035 }, + { 0x098025f8, 0x64000007 }, + { 0x09802600, 0x6800006e }, + { 0x0900266f, 0x64000000 }, + { 0x09802670, 0x6800002c }, + { 0x098026a0, 0x68000012 }, + { 0x09802701, 0x68000003 }, + { 0x09802706, 0x68000003 }, + { 0x0980270c, 0x6800001b }, + { 0x09802729, 0x68000022 }, + { 0x0900274d, 0x68000000 }, + { 0x0980274f, 0x68000003 }, + { 0x09002756, 0x68000000 }, + { 0x09802758, 0x68000006 }, + { 0x09802761, 0x68000006 }, + { 0x09002768, 0x58000000 }, + { 0x09002769, 0x48000000 }, + { 0x0900276a, 0x58000000 }, + { 0x0900276b, 0x48000000 }, + { 0x0900276c, 0x58000000 }, + { 0x0900276d, 0x48000000 }, + { 0x0900276e, 0x58000000 }, + { 0x0900276f, 0x48000000 }, + { 0x09002770, 0x58000000 }, + { 0x09002771, 0x48000000 }, + { 0x09002772, 0x58000000 }, + { 0x09002773, 0x48000000 }, + { 0x09002774, 0x58000000 }, + { 0x09002775, 0x48000000 }, + { 0x09802776, 0x3c00001d }, + { 0x09002794, 0x68000000 }, + { 0x09802798, 0x68000017 }, + { 0x098027b1, 0x6800000d }, + { 0x098027c0, 0x64000004 }, + { 0x090027c5, 0x58000000 }, + { 0x090027c6, 0x48000000 }, + { 0x098027c7, 0x64000003 }, + { 0x098027d0, 0x64000015 }, + { 0x090027e6, 0x58000000 }, + { 0x090027e7, 0x48000000 }, + { 0x090027e8, 0x58000000 }, + { 0x090027e9, 0x48000000 }, + { 0x090027ea, 0x58000000 }, + { 0x090027eb, 0x48000000 }, + { 0x098027f0, 0x6400000f }, + { 0x04802800, 0x680000ff }, + { 0x09802900, 0x64000082 }, + { 0x09002983, 0x58000000 }, + { 0x09002984, 0x48000000 }, + { 0x09002985, 0x58000000 }, + { 0x09002986, 0x48000000 }, + { 0x09002987, 0x58000000 }, + { 0x09002988, 0x48000000 }, + { 0x09002989, 0x58000000 }, + { 0x0900298a, 0x48000000 }, + { 0x0900298b, 0x58000000 }, + { 0x0900298c, 0x48000000 }, + { 0x0900298d, 0x58000000 }, + { 0x0900298e, 0x48000000 }, + { 0x0900298f, 0x58000000 }, + { 0x09002990, 0x48000000 }, + { 0x09002991, 0x58000000 }, + { 0x09002992, 0x48000000 }, + { 0x09002993, 0x58000000 }, + { 0x09002994, 0x48000000 }, + { 0x09002995, 0x58000000 }, + { 0x09002996, 0x48000000 }, + { 0x09002997, 0x58000000 }, + { 0x09002998, 0x48000000 }, + { 0x09802999, 0x6400003e }, + { 0x090029d8, 0x58000000 }, + { 0x090029d9, 0x48000000 }, + { 0x090029da, 0x58000000 }, + { 0x090029db, 0x48000000 }, + { 0x098029dc, 0x6400001f }, + { 0x090029fc, 0x58000000 }, + { 0x090029fd, 0x48000000 }, + { 0x098029fe, 0x64000101 }, + { 0x09802b00, 0x6800001a }, + { 0x09802b20, 0x68000003 }, + { 0x11002c00, 0x24000030 }, + { 0x11002c01, 0x24000030 }, + { 0x11002c02, 0x24000030 }, + { 0x11002c03, 0x24000030 }, + { 0x11002c04, 0x24000030 }, + { 0x11002c05, 0x24000030 }, + { 0x11002c06, 0x24000030 }, + { 0x11002c07, 0x24000030 }, + { 0x11002c08, 0x24000030 }, + { 0x11002c09, 0x24000030 }, + { 0x11002c0a, 0x24000030 }, + { 0x11002c0b, 0x24000030 }, + { 0x11002c0c, 0x24000030 }, + { 0x11002c0d, 0x24000030 }, + { 0x11002c0e, 0x24000030 }, + { 0x11002c0f, 0x24000030 }, + { 0x11002c10, 0x24000030 }, + { 0x11002c11, 0x24000030 }, + { 0x11002c12, 0x24000030 }, + { 0x11002c13, 0x24000030 }, + { 0x11002c14, 0x24000030 }, + { 0x11002c15, 0x24000030 }, + { 0x11002c16, 0x24000030 }, + { 0x11002c17, 0x24000030 }, + { 0x11002c18, 0x24000030 }, + { 0x11002c19, 0x24000030 }, + { 0x11002c1a, 0x24000030 }, + { 0x11002c1b, 0x24000030 }, + { 0x11002c1c, 0x24000030 }, + { 0x11002c1d, 0x24000030 }, + { 0x11002c1e, 0x24000030 }, + { 0x11002c1f, 0x24000030 }, + { 0x11002c20, 0x24000030 }, + { 0x11002c21, 0x24000030 }, + { 0x11002c22, 0x24000030 }, + { 0x11002c23, 0x24000030 }, + { 0x11002c24, 0x24000030 }, + { 0x11002c25, 0x24000030 }, + { 0x11002c26, 0x24000030 }, + { 0x11002c27, 0x24000030 }, + { 0x11002c28, 0x24000030 }, + { 0x11002c29, 0x24000030 }, + { 0x11002c2a, 0x24000030 }, + { 0x11002c2b, 0x24000030 }, + { 0x11002c2c, 0x24000030 }, + { 0x11002c2d, 0x24000030 }, + { 0x11002c2e, 0x24000030 }, + { 0x11002c30, 0x1400ffd0 }, + { 0x11002c31, 0x1400ffd0 }, + { 0x11002c32, 0x1400ffd0 }, + { 0x11002c33, 0x1400ffd0 }, + { 0x11002c34, 0x1400ffd0 }, + { 0x11002c35, 0x1400ffd0 }, + { 0x11002c36, 0x1400ffd0 }, + { 0x11002c37, 0x1400ffd0 }, + { 0x11002c38, 0x1400ffd0 }, + { 0x11002c39, 0x1400ffd0 }, + { 0x11002c3a, 0x1400ffd0 }, + { 0x11002c3b, 0x1400ffd0 }, + { 0x11002c3c, 0x1400ffd0 }, + { 0x11002c3d, 0x1400ffd0 }, + { 0x11002c3e, 0x1400ffd0 }, + { 0x11002c3f, 0x1400ffd0 }, + { 0x11002c40, 0x1400ffd0 }, + { 0x11002c41, 0x1400ffd0 }, + { 0x11002c42, 0x1400ffd0 }, + { 0x11002c43, 0x1400ffd0 }, + { 0x11002c44, 0x1400ffd0 }, + { 0x11002c45, 0x1400ffd0 }, + { 0x11002c46, 0x1400ffd0 }, + { 0x11002c47, 0x1400ffd0 }, + { 0x11002c48, 0x1400ffd0 }, + { 0x11002c49, 0x1400ffd0 }, + { 0x11002c4a, 0x1400ffd0 }, + { 0x11002c4b, 0x1400ffd0 }, + { 0x11002c4c, 0x1400ffd0 }, + { 0x11002c4d, 0x1400ffd0 }, + { 0x11002c4e, 0x1400ffd0 }, + { 0x11002c4f, 0x1400ffd0 }, + { 0x11002c50, 0x1400ffd0 }, + { 0x11002c51, 0x1400ffd0 }, + { 0x11002c52, 0x1400ffd0 }, + { 0x11002c53, 0x1400ffd0 }, + { 0x11002c54, 0x1400ffd0 }, + { 0x11002c55, 0x1400ffd0 }, + { 0x11002c56, 0x1400ffd0 }, + { 0x11002c57, 0x1400ffd0 }, + { 0x11002c58, 0x1400ffd0 }, + { 0x11002c59, 0x1400ffd0 }, + { 0x11002c5a, 0x1400ffd0 }, + { 0x11002c5b, 0x1400ffd0 }, + { 0x11002c5c, 0x1400ffd0 }, + { 0x11002c5d, 0x1400ffd0 }, + { 0x11002c5e, 0x1400ffd0 }, + { 0x21002c60, 0x24000001 }, + { 0x21002c61, 0x1400ffff }, + { 0x21002c62, 0x2400d609 }, + { 0x21002c63, 0x2400f11a }, + { 0x21002c64, 0x2400d619 }, + { 0x21002c65, 0x1400d5d5 }, + { 0x21002c66, 0x1400d5d8 }, + { 0x21002c67, 0x24000001 }, + { 0x21002c68, 0x1400ffff }, + { 0x21002c69, 0x24000001 }, + { 0x21002c6a, 0x1400ffff }, + { 0x21002c6b, 0x24000001 }, + { 0x21002c6c, 0x1400ffff }, + { 0x21002c74, 0x14000000 }, + { 0x21002c75, 0x24000001 }, + { 0x21002c76, 0x1400ffff }, + { 0x21002c77, 0x14000000 }, + { 0x0a002c80, 0x24000001 }, + { 0x0a002c81, 0x1400ffff }, + { 0x0a002c82, 0x24000001 }, + { 0x0a002c83, 0x1400ffff }, + { 0x0a002c84, 0x24000001 }, + { 0x0a002c85, 0x1400ffff }, + { 0x0a002c86, 0x24000001 }, + { 0x0a002c87, 0x1400ffff }, + { 0x0a002c88, 0x24000001 }, + { 0x0a002c89, 0x1400ffff }, + { 0x0a002c8a, 0x24000001 }, + { 0x0a002c8b, 0x1400ffff }, + { 0x0a002c8c, 0x24000001 }, + { 0x0a002c8d, 0x1400ffff }, + { 0x0a002c8e, 0x24000001 }, + { 0x0a002c8f, 0x1400ffff }, + { 0x0a002c90, 0x24000001 }, + { 0x0a002c91, 0x1400ffff }, + { 0x0a002c92, 0x24000001 }, + { 0x0a002c93, 0x1400ffff }, + { 0x0a002c94, 0x24000001 }, + { 0x0a002c95, 0x1400ffff }, + { 0x0a002c96, 0x24000001 }, + { 0x0a002c97, 0x1400ffff }, + { 0x0a002c98, 0x24000001 }, + { 0x0a002c99, 0x1400ffff }, + { 0x0a002c9a, 0x24000001 }, + { 0x0a002c9b, 0x1400ffff }, + { 0x0a002c9c, 0x24000001 }, + { 0x0a002c9d, 0x1400ffff }, + { 0x0a002c9e, 0x24000001 }, + { 0x0a002c9f, 0x1400ffff }, + { 0x0a002ca0, 0x24000001 }, + { 0x0a002ca1, 0x1400ffff }, + { 0x0a002ca2, 0x24000001 }, + { 0x0a002ca3, 0x1400ffff }, + { 0x0a002ca4, 0x24000001 }, + { 0x0a002ca5, 0x1400ffff }, + { 0x0a002ca6, 0x24000001 }, + { 0x0a002ca7, 0x1400ffff }, + { 0x0a002ca8, 0x24000001 }, + { 0x0a002ca9, 0x1400ffff }, + { 0x0a002caa, 0x24000001 }, + { 0x0a002cab, 0x1400ffff }, + { 0x0a002cac, 0x24000001 }, + { 0x0a002cad, 0x1400ffff }, + { 0x0a002cae, 0x24000001 }, + { 0x0a002caf, 0x1400ffff }, + { 0x0a002cb0, 0x24000001 }, + { 0x0a002cb1, 0x1400ffff }, + { 0x0a002cb2, 0x24000001 }, + { 0x0a002cb3, 0x1400ffff }, + { 0x0a002cb4, 0x24000001 }, + { 0x0a002cb5, 0x1400ffff }, + { 0x0a002cb6, 0x24000001 }, + { 0x0a002cb7, 0x1400ffff }, + { 0x0a002cb8, 0x24000001 }, + { 0x0a002cb9, 0x1400ffff }, + { 0x0a002cba, 0x24000001 }, + { 0x0a002cbb, 0x1400ffff }, + { 0x0a002cbc, 0x24000001 }, + { 0x0a002cbd, 0x1400ffff }, + { 0x0a002cbe, 0x24000001 }, + { 0x0a002cbf, 0x1400ffff }, + { 0x0a002cc0, 0x24000001 }, + { 0x0a002cc1, 0x1400ffff }, + { 0x0a002cc2, 0x24000001 }, + { 0x0a002cc3, 0x1400ffff }, + { 0x0a002cc4, 0x24000001 }, + { 0x0a002cc5, 0x1400ffff }, + { 0x0a002cc6, 0x24000001 }, + { 0x0a002cc7, 0x1400ffff }, + { 0x0a002cc8, 0x24000001 }, + { 0x0a002cc9, 0x1400ffff }, + { 0x0a002cca, 0x24000001 }, + { 0x0a002ccb, 0x1400ffff }, + { 0x0a002ccc, 0x24000001 }, + { 0x0a002ccd, 0x1400ffff }, + { 0x0a002cce, 0x24000001 }, + { 0x0a002ccf, 0x1400ffff }, + { 0x0a002cd0, 0x24000001 }, + { 0x0a002cd1, 0x1400ffff }, + { 0x0a002cd2, 0x24000001 }, + { 0x0a002cd3, 0x1400ffff }, + { 0x0a002cd4, 0x24000001 }, + { 0x0a002cd5, 0x1400ffff }, + { 0x0a002cd6, 0x24000001 }, + { 0x0a002cd7, 0x1400ffff }, + { 0x0a002cd8, 0x24000001 }, + { 0x0a002cd9, 0x1400ffff }, + { 0x0a002cda, 0x24000001 }, + { 0x0a002cdb, 0x1400ffff }, + { 0x0a002cdc, 0x24000001 }, + { 0x0a002cdd, 0x1400ffff }, + { 0x0a002cde, 0x24000001 }, + { 0x0a002cdf, 0x1400ffff }, + { 0x0a002ce0, 0x24000001 }, + { 0x0a002ce1, 0x1400ffff }, + { 0x0a002ce2, 0x24000001 }, + { 0x0a002ce3, 0x1400ffff }, + { 0x0a002ce4, 0x14000000 }, + { 0x0a802ce5, 0x68000005 }, + { 0x0a802cf9, 0x54000003 }, + { 0x0a002cfd, 0x3c000000 }, + { 0x0a802cfe, 0x54000001 }, + { 0x10002d00, 0x1400e3a0 }, + { 0x10002d01, 0x1400e3a0 }, + { 0x10002d02, 0x1400e3a0 }, + { 0x10002d03, 0x1400e3a0 }, + { 0x10002d04, 0x1400e3a0 }, + { 0x10002d05, 0x1400e3a0 }, + { 0x10002d06, 0x1400e3a0 }, + { 0x10002d07, 0x1400e3a0 }, + { 0x10002d08, 0x1400e3a0 }, + { 0x10002d09, 0x1400e3a0 }, + { 0x10002d0a, 0x1400e3a0 }, + { 0x10002d0b, 0x1400e3a0 }, + { 0x10002d0c, 0x1400e3a0 }, + { 0x10002d0d, 0x1400e3a0 }, + { 0x10002d0e, 0x1400e3a0 }, + { 0x10002d0f, 0x1400e3a0 }, + { 0x10002d10, 0x1400e3a0 }, + { 0x10002d11, 0x1400e3a0 }, + { 0x10002d12, 0x1400e3a0 }, + { 0x10002d13, 0x1400e3a0 }, + { 0x10002d14, 0x1400e3a0 }, + { 0x10002d15, 0x1400e3a0 }, + { 0x10002d16, 0x1400e3a0 }, + { 0x10002d17, 0x1400e3a0 }, + { 0x10002d18, 0x1400e3a0 }, + { 0x10002d19, 0x1400e3a0 }, + { 0x10002d1a, 0x1400e3a0 }, + { 0x10002d1b, 0x1400e3a0 }, + { 0x10002d1c, 0x1400e3a0 }, + { 0x10002d1d, 0x1400e3a0 }, + { 0x10002d1e, 0x1400e3a0 }, + { 0x10002d1f, 0x1400e3a0 }, + { 0x10002d20, 0x1400e3a0 }, + { 0x10002d21, 0x1400e3a0 }, + { 0x10002d22, 0x1400e3a0 }, + { 0x10002d23, 0x1400e3a0 }, + { 0x10002d24, 0x1400e3a0 }, + { 0x10002d25, 0x1400e3a0 }, + { 0x3a802d30, 0x1c000035 }, + { 0x3a002d6f, 0x18000000 }, + { 0x0f802d80, 0x1c000016 }, + { 0x0f802da0, 0x1c000006 }, + { 0x0f802da8, 0x1c000006 }, + { 0x0f802db0, 0x1c000006 }, + { 0x0f802db8, 0x1c000006 }, + { 0x0f802dc0, 0x1c000006 }, + { 0x0f802dc8, 0x1c000006 }, + { 0x0f802dd0, 0x1c000006 }, + { 0x0f802dd8, 0x1c000006 }, + { 0x09802e00, 0x54000001 }, + { 0x09002e02, 0x50000000 }, + { 0x09002e03, 0x4c000000 }, + { 0x09002e04, 0x50000000 }, + { 0x09002e05, 0x4c000000 }, + { 0x09802e06, 0x54000002 }, + { 0x09002e09, 0x50000000 }, + { 0x09002e0a, 0x4c000000 }, + { 0x09002e0b, 0x54000000 }, + { 0x09002e0c, 0x50000000 }, + { 0x09002e0d, 0x4c000000 }, + { 0x09802e0e, 0x54000008 }, + { 0x09002e17, 0x44000000 }, + { 0x09002e1c, 0x50000000 }, + { 0x09002e1d, 0x4c000000 }, + { 0x16802e80, 0x68000019 }, + { 0x16802e9b, 0x68000058 }, + { 0x16802f00, 0x680000d5 }, + { 0x09802ff0, 0x6800000b }, + { 0x09003000, 0x74000000 }, + { 0x09803001, 0x54000002 }, + { 0x09003004, 0x68000000 }, + { 0x16003005, 0x18000000 }, + { 0x09003006, 0x1c000000 }, + { 0x16003007, 0x38000000 }, + { 0x09003008, 0x58000000 }, + { 0x09003009, 0x48000000 }, + { 0x0900300a, 0x58000000 }, + { 0x0900300b, 0x48000000 }, + { 0x0900300c, 0x58000000 }, + { 0x0900300d, 0x48000000 }, + { 0x0900300e, 0x58000000 }, + { 0x0900300f, 0x48000000 }, + { 0x09003010, 0x58000000 }, + { 0x09003011, 0x48000000 }, + { 0x09803012, 0x68000001 }, + { 0x09003014, 0x58000000 }, + { 0x09003015, 0x48000000 }, + { 0x09003016, 0x58000000 }, + { 0x09003017, 0x48000000 }, + { 0x09003018, 0x58000000 }, + { 0x09003019, 0x48000000 }, + { 0x0900301a, 0x58000000 }, + { 0x0900301b, 0x48000000 }, + { 0x0900301c, 0x44000000 }, + { 0x0900301d, 0x58000000 }, + { 0x0980301e, 0x48000001 }, + { 0x09003020, 0x68000000 }, + { 0x16803021, 0x38000008 }, + { 0x1b80302a, 0x30000005 }, + { 0x09003030, 0x44000000 }, + { 0x09803031, 0x18000004 }, + { 0x09803036, 0x68000001 }, + { 0x16803038, 0x38000002 }, + { 0x1600303b, 0x18000000 }, + { 0x0900303c, 0x1c000000 }, + { 0x0900303d, 0x54000000 }, + { 0x0980303e, 0x68000001 }, + { 0x1a803041, 0x1c000055 }, + { 0x1b803099, 0x30000001 }, + { 0x0980309b, 0x60000001 }, + { 0x1a80309d, 0x18000001 }, + { 0x1a00309f, 0x1c000000 }, + { 0x090030a0, 0x44000000 }, + { 0x1d8030a1, 0x1c000059 }, + { 0x090030fb, 0x54000000 }, + { 0x098030fc, 0x18000002 }, + { 0x1d0030ff, 0x1c000000 }, + { 0x03803105, 0x1c000027 }, + { 0x17803131, 0x1c00005d }, + { 0x09803190, 0x68000001 }, + { 0x09803192, 0x3c000003 }, + { 0x09803196, 0x68000009 }, + { 0x038031a0, 0x1c000017 }, + { 0x098031c0, 0x6800000f }, + { 0x1d8031f0, 0x1c00000f }, + { 0x17803200, 0x6800001e }, + { 0x09803220, 0x3c000009 }, + { 0x0980322a, 0x68000019 }, + { 0x09003250, 0x68000000 }, + { 0x09803251, 0x3c00000e }, + { 0x17803260, 0x6800001f }, + { 0x09803280, 0x3c000009 }, + { 0x0980328a, 0x68000026 }, + { 0x098032b1, 0x3c00000e }, + { 0x098032c0, 0x6800003e }, + { 0x09803300, 0x680000ff }, + { 0x16803400, 0x1c0019b5 }, + { 0x09804dc0, 0x6800003f }, + { 0x16804e00, 0x1c0051bb }, + { 0x3c80a000, 0x1c000014 }, + { 0x3c00a015, 0x18000000 }, + { 0x3c80a016, 0x1c000476 }, + { 0x3c80a490, 0x68000036 }, + { 0x0980a700, 0x60000016 }, + { 0x0980a717, 0x18000003 }, + { 0x0980a720, 0x60000001 }, + { 0x3080a800, 0x1c000001 }, + { 0x3000a802, 0x28000000 }, + { 0x3080a803, 0x1c000002 }, + { 0x3000a806, 0x30000000 }, + { 0x3080a807, 0x1c000003 }, + { 0x3000a80b, 0x30000000 }, + { 0x3080a80c, 0x1c000016 }, + { 0x3080a823, 0x28000001 }, + { 0x3080a825, 0x30000001 }, + { 0x3000a827, 0x28000000 }, + { 0x3080a828, 0x68000003 }, + { 0x4080a840, 0x1c000033 }, + { 0x4080a874, 0x54000003 }, + { 0x1780ac00, 0x1c002ba3 }, + { 0x0980d800, 0x1000037f }, + { 0x0980db80, 0x1000007f }, + { 0x0980dc00, 0x100003ff }, + { 0x0980e000, 0x0c0018ff }, + { 0x1680f900, 0x1c00012d }, + { 0x1680fa30, 0x1c00003a }, + { 0x1680fa70, 0x1c000069 }, + { 0x2180fb00, 0x14000006 }, + { 0x0180fb13, 0x14000004 }, + { 0x1900fb1d, 0x1c000000 }, + { 0x1900fb1e, 0x30000000 }, + { 0x1980fb1f, 0x1c000009 }, + { 0x1900fb29, 0x64000000 }, + { 0x1980fb2a, 0x1c00000c }, + { 0x1980fb38, 0x1c000004 }, + { 0x1900fb3e, 0x1c000000 }, + { 0x1980fb40, 0x1c000001 }, + { 0x1980fb43, 0x1c000001 }, + { 0x1980fb46, 0x1c00006b }, + { 0x0080fbd3, 0x1c00016a }, + { 0x0900fd3e, 0x58000000 }, + { 0x0900fd3f, 0x48000000 }, + { 0x0080fd50, 0x1c00003f }, + { 0x0080fd92, 0x1c000035 }, + { 0x0080fdf0, 0x1c00000b }, + { 0x0000fdfc, 0x5c000000 }, + { 0x0900fdfd, 0x68000000 }, + { 0x1b80fe00, 0x3000000f }, + { 0x0980fe10, 0x54000006 }, + { 0x0900fe17, 0x58000000 }, + { 0x0900fe18, 0x48000000 }, + { 0x0900fe19, 0x54000000 }, + { 0x1b80fe20, 0x30000003 }, + { 0x0900fe30, 0x54000000 }, + { 0x0980fe31, 0x44000001 }, + { 0x0980fe33, 0x40000001 }, + { 0x0900fe35, 0x58000000 }, + { 0x0900fe36, 0x48000000 }, + { 0x0900fe37, 0x58000000 }, + { 0x0900fe38, 0x48000000 }, + { 0x0900fe39, 0x58000000 }, + { 0x0900fe3a, 0x48000000 }, + { 0x0900fe3b, 0x58000000 }, + { 0x0900fe3c, 0x48000000 }, + { 0x0900fe3d, 0x58000000 }, + { 0x0900fe3e, 0x48000000 }, + { 0x0900fe3f, 0x58000000 }, + { 0x0900fe40, 0x48000000 }, + { 0x0900fe41, 0x58000000 }, + { 0x0900fe42, 0x48000000 }, + { 0x0900fe43, 0x58000000 }, + { 0x0900fe44, 0x48000000 }, + { 0x0980fe45, 0x54000001 }, + { 0x0900fe47, 0x58000000 }, + { 0x0900fe48, 0x48000000 }, + { 0x0980fe49, 0x54000003 }, + { 0x0980fe4d, 0x40000002 }, + { 0x0980fe50, 0x54000002 }, + { 0x0980fe54, 0x54000003 }, + { 0x0900fe58, 0x44000000 }, + { 0x0900fe59, 0x58000000 }, + { 0x0900fe5a, 0x48000000 }, + { 0x0900fe5b, 0x58000000 }, + { 0x0900fe5c, 0x48000000 }, + { 0x0900fe5d, 0x58000000 }, + { 0x0900fe5e, 0x48000000 }, + { 0x0980fe5f, 0x54000002 }, + { 0x0900fe62, 0x64000000 }, + { 0x0900fe63, 0x44000000 }, + { 0x0980fe64, 0x64000002 }, + { 0x0900fe68, 0x54000000 }, + { 0x0900fe69, 0x5c000000 }, + { 0x0980fe6a, 0x54000001 }, + { 0x0080fe70, 0x1c000004 }, + { 0x0080fe76, 0x1c000086 }, + { 0x0900feff, 0x04000000 }, + { 0x0980ff01, 0x54000002 }, + { 0x0900ff04, 0x5c000000 }, + { 0x0980ff05, 0x54000002 }, + { 0x0900ff08, 0x58000000 }, + { 0x0900ff09, 0x48000000 }, + { 0x0900ff0a, 0x54000000 }, + { 0x0900ff0b, 0x64000000 }, + { 0x0900ff0c, 0x54000000 }, + { 0x0900ff0d, 0x44000000 }, + { 0x0980ff0e, 0x54000001 }, + { 0x0980ff10, 0x34000009 }, + { 0x0980ff1a, 0x54000001 }, + { 0x0980ff1c, 0x64000002 }, + { 0x0980ff1f, 0x54000001 }, + { 0x2100ff21, 0x24000020 }, + { 0x2100ff22, 0x24000020 }, + { 0x2100ff23, 0x24000020 }, + { 0x2100ff24, 0x24000020 }, + { 0x2100ff25, 0x24000020 }, + { 0x2100ff26, 0x24000020 }, + { 0x2100ff27, 0x24000020 }, + { 0x2100ff28, 0x24000020 }, + { 0x2100ff29, 0x24000020 }, + { 0x2100ff2a, 0x24000020 }, + { 0x2100ff2b, 0x24000020 }, + { 0x2100ff2c, 0x24000020 }, + { 0x2100ff2d, 0x24000020 }, + { 0x2100ff2e, 0x24000020 }, + { 0x2100ff2f, 0x24000020 }, + { 0x2100ff30, 0x24000020 }, + { 0x2100ff31, 0x24000020 }, + { 0x2100ff32, 0x24000020 }, + { 0x2100ff33, 0x24000020 }, + { 0x2100ff34, 0x24000020 }, + { 0x2100ff35, 0x24000020 }, + { 0x2100ff36, 0x24000020 }, + { 0x2100ff37, 0x24000020 }, + { 0x2100ff38, 0x24000020 }, + { 0x2100ff39, 0x24000020 }, + { 0x2100ff3a, 0x24000020 }, + { 0x0900ff3b, 0x58000000 }, + { 0x0900ff3c, 0x54000000 }, + { 0x0900ff3d, 0x48000000 }, + { 0x0900ff3e, 0x60000000 }, + { 0x0900ff3f, 0x40000000 }, + { 0x0900ff40, 0x60000000 }, + { 0x2100ff41, 0x1400ffe0 }, + { 0x2100ff42, 0x1400ffe0 }, + { 0x2100ff43, 0x1400ffe0 }, + { 0x2100ff44, 0x1400ffe0 }, + { 0x2100ff45, 0x1400ffe0 }, + { 0x2100ff46, 0x1400ffe0 }, + { 0x2100ff47, 0x1400ffe0 }, + { 0x2100ff48, 0x1400ffe0 }, + { 0x2100ff49, 0x1400ffe0 }, + { 0x2100ff4a, 0x1400ffe0 }, + { 0x2100ff4b, 0x1400ffe0 }, + { 0x2100ff4c, 0x1400ffe0 }, + { 0x2100ff4d, 0x1400ffe0 }, + { 0x2100ff4e, 0x1400ffe0 }, + { 0x2100ff4f, 0x1400ffe0 }, + { 0x2100ff50, 0x1400ffe0 }, + { 0x2100ff51, 0x1400ffe0 }, + { 0x2100ff52, 0x1400ffe0 }, + { 0x2100ff53, 0x1400ffe0 }, + { 0x2100ff54, 0x1400ffe0 }, + { 0x2100ff55, 0x1400ffe0 }, + { 0x2100ff56, 0x1400ffe0 }, + { 0x2100ff57, 0x1400ffe0 }, + { 0x2100ff58, 0x1400ffe0 }, + { 0x2100ff59, 0x1400ffe0 }, + { 0x2100ff5a, 0x1400ffe0 }, + { 0x0900ff5b, 0x58000000 }, + { 0x0900ff5c, 0x64000000 }, + { 0x0900ff5d, 0x48000000 }, + { 0x0900ff5e, 0x64000000 }, + { 0x0900ff5f, 0x58000000 }, + { 0x0900ff60, 0x48000000 }, + { 0x0900ff61, 0x54000000 }, + { 0x0900ff62, 0x58000000 }, + { 0x0900ff63, 0x48000000 }, + { 0x0980ff64, 0x54000001 }, + { 0x1d80ff66, 0x1c000009 }, + { 0x0900ff70, 0x18000000 }, + { 0x1d80ff71, 0x1c00002c }, + { 0x0980ff9e, 0x18000001 }, + { 0x1780ffa0, 0x1c00001e }, + { 0x1780ffc2, 0x1c000005 }, + { 0x1780ffca, 0x1c000005 }, + { 0x1780ffd2, 0x1c000005 }, + { 0x1780ffda, 0x1c000002 }, + { 0x0980ffe0, 0x5c000001 }, + { 0x0900ffe2, 0x64000000 }, + { 0x0900ffe3, 0x60000000 }, + { 0x0900ffe4, 0x68000000 }, + { 0x0980ffe5, 0x5c000001 }, + { 0x0900ffe8, 0x68000000 }, + { 0x0980ffe9, 0x64000003 }, + { 0x0980ffed, 0x68000001 }, + { 0x0980fff9, 0x04000002 }, + { 0x0980fffc, 0x68000001 }, + { 0x23810000, 0x1c00000b }, + { 0x2381000d, 0x1c000019 }, + { 0x23810028, 0x1c000012 }, + { 0x2381003c, 0x1c000001 }, + { 0x2381003f, 0x1c00000e }, + { 0x23810050, 0x1c00000d }, + { 0x23810080, 0x1c00007a }, + { 0x09810100, 0x54000001 }, + { 0x09010102, 0x68000000 }, + { 0x09810107, 0x3c00002c }, + { 0x09810137, 0x68000008 }, + { 0x13810140, 0x38000034 }, + { 0x13810175, 0x3c000003 }, + { 0x13810179, 0x68000010 }, + { 0x1301018a, 0x3c000000 }, + { 0x29810300, 0x1c00001e }, + { 0x29810320, 0x3c000003 }, + { 0x12810330, 0x1c000010 }, + { 0x12010341, 0x38000000 }, + { 0x12810342, 0x1c000007 }, + { 0x1201034a, 0x38000000 }, + { 0x3b810380, 0x1c00001d }, + { 0x3b01039f, 0x54000000 }, + { 0x2a8103a0, 0x1c000023 }, + { 0x2a8103c8, 0x1c000007 }, + { 0x2a0103d0, 0x54000000 }, + { 0x2a8103d1, 0x38000004 }, + { 0x0d010400, 0x24000028 }, + { 0x0d010401, 0x24000028 }, + { 0x0d010402, 0x24000028 }, + { 0x0d010403, 0x24000028 }, + { 0x0d010404, 0x24000028 }, + { 0x0d010405, 0x24000028 }, + { 0x0d010406, 0x24000028 }, + { 0x0d010407, 0x24000028 }, + { 0x0d010408, 0x24000028 }, + { 0x0d010409, 0x24000028 }, + { 0x0d01040a, 0x24000028 }, + { 0x0d01040b, 0x24000028 }, + { 0x0d01040c, 0x24000028 }, + { 0x0d01040d, 0x24000028 }, + { 0x0d01040e, 0x24000028 }, + { 0x0d01040f, 0x24000028 }, + { 0x0d010410, 0x24000028 }, + { 0x0d010411, 0x24000028 }, + { 0x0d010412, 0x24000028 }, + { 0x0d010413, 0x24000028 }, + { 0x0d010414, 0x24000028 }, + { 0x0d010415, 0x24000028 }, + { 0x0d010416, 0x24000028 }, + { 0x0d010417, 0x24000028 }, + { 0x0d010418, 0x24000028 }, + { 0x0d010419, 0x24000028 }, + { 0x0d01041a, 0x24000028 }, + { 0x0d01041b, 0x24000028 }, + { 0x0d01041c, 0x24000028 }, + { 0x0d01041d, 0x24000028 }, + { 0x0d01041e, 0x24000028 }, + { 0x0d01041f, 0x24000028 }, + { 0x0d010420, 0x24000028 }, + { 0x0d010421, 0x24000028 }, + { 0x0d010422, 0x24000028 }, + { 0x0d010423, 0x24000028 }, + { 0x0d010424, 0x24000028 }, + { 0x0d010425, 0x24000028 }, + { 0x0d010426, 0x24000028 }, + { 0x0d010427, 0x24000028 }, + { 0x0d010428, 0x1400ffd8 }, + { 0x0d010429, 0x1400ffd8 }, + { 0x0d01042a, 0x1400ffd8 }, + { 0x0d01042b, 0x1400ffd8 }, + { 0x0d01042c, 0x1400ffd8 }, + { 0x0d01042d, 0x1400ffd8 }, + { 0x0d01042e, 0x1400ffd8 }, + { 0x0d01042f, 0x1400ffd8 }, + { 0x0d010430, 0x1400ffd8 }, + { 0x0d010431, 0x1400ffd8 }, + { 0x0d010432, 0x1400ffd8 }, + { 0x0d010433, 0x1400ffd8 }, + { 0x0d010434, 0x1400ffd8 }, + { 0x0d010435, 0x1400ffd8 }, + { 0x0d010436, 0x1400ffd8 }, + { 0x0d010437, 0x1400ffd8 }, + { 0x0d010438, 0x1400ffd8 }, + { 0x0d010439, 0x1400ffd8 }, + { 0x0d01043a, 0x1400ffd8 }, + { 0x0d01043b, 0x1400ffd8 }, + { 0x0d01043c, 0x1400ffd8 }, + { 0x0d01043d, 0x1400ffd8 }, + { 0x0d01043e, 0x1400ffd8 }, + { 0x0d01043f, 0x1400ffd8 }, + { 0x0d010440, 0x1400ffd8 }, + { 0x0d010441, 0x1400ffd8 }, + { 0x0d010442, 0x1400ffd8 }, + { 0x0d010443, 0x1400ffd8 }, + { 0x0d010444, 0x1400ffd8 }, + { 0x0d010445, 0x1400ffd8 }, + { 0x0d010446, 0x1400ffd8 }, + { 0x0d010447, 0x1400ffd8 }, + { 0x0d010448, 0x1400ffd8 }, + { 0x0d010449, 0x1400ffd8 }, + { 0x0d01044a, 0x1400ffd8 }, + { 0x0d01044b, 0x1400ffd8 }, + { 0x0d01044c, 0x1400ffd8 }, + { 0x0d01044d, 0x1400ffd8 }, + { 0x0d01044e, 0x1400ffd8 }, + { 0x0d01044f, 0x1400ffd8 }, + { 0x2e810450, 0x1c00004d }, + { 0x2c8104a0, 0x34000009 }, + { 0x0b810800, 0x1c000005 }, + { 0x0b010808, 0x1c000000 }, + { 0x0b81080a, 0x1c00002b }, + { 0x0b810837, 0x1c000001 }, + { 0x0b01083c, 0x1c000000 }, + { 0x0b01083f, 0x1c000000 }, + { 0x41810900, 0x1c000015 }, + { 0x41810916, 0x3c000003 }, + { 0x4101091f, 0x54000000 }, + { 0x1e010a00, 0x1c000000 }, + { 0x1e810a01, 0x30000002 }, + { 0x1e810a05, 0x30000001 }, + { 0x1e810a0c, 0x30000003 }, + { 0x1e810a10, 0x1c000003 }, + { 0x1e810a15, 0x1c000002 }, + { 0x1e810a19, 0x1c00001a }, + { 0x1e810a38, 0x30000002 }, + { 0x1e010a3f, 0x30000000 }, + { 0x1e810a40, 0x3c000007 }, + { 0x1e810a50, 0x54000008 }, + { 0x3e812000, 0x1c00036e }, + { 0x3e812400, 0x38000062 }, + { 0x3e812470, 0x54000003 }, + { 0x0981d000, 0x680000f5 }, + { 0x0981d100, 0x68000026 }, + { 0x0981d12a, 0x6800003a }, + { 0x0981d165, 0x28000001 }, + { 0x1b81d167, 0x30000002 }, + { 0x0981d16a, 0x68000002 }, + { 0x0981d16d, 0x28000005 }, + { 0x0981d173, 0x04000007 }, + { 0x1b81d17b, 0x30000007 }, + { 0x0981d183, 0x68000001 }, + { 0x1b81d185, 0x30000006 }, + { 0x0981d18c, 0x6800001d }, + { 0x1b81d1aa, 0x30000003 }, + { 0x0981d1ae, 0x6800002f }, + { 0x1381d200, 0x68000041 }, + { 0x1381d242, 0x30000002 }, + { 0x1301d245, 0x68000000 }, + { 0x0981d300, 0x68000056 }, + { 0x0981d360, 0x3c000011 }, + { 0x0981d400, 0x24000019 }, + { 0x0981d41a, 0x14000019 }, + { 0x0981d434, 0x24000019 }, + { 0x0981d44e, 0x14000006 }, + { 0x0981d456, 0x14000011 }, + { 0x0981d468, 0x24000019 }, + { 0x0981d482, 0x14000019 }, + { 0x0901d49c, 0x24000000 }, + { 0x0981d49e, 0x24000001 }, + { 0x0901d4a2, 0x24000000 }, + { 0x0981d4a5, 0x24000001 }, + { 0x0981d4a9, 0x24000003 }, + { 0x0981d4ae, 0x24000007 }, + { 0x0981d4b6, 0x14000003 }, + { 0x0901d4bb, 0x14000000 }, + { 0x0981d4bd, 0x14000006 }, + { 0x0981d4c5, 0x1400000a }, + { 0x0981d4d0, 0x24000019 }, + { 0x0981d4ea, 0x14000019 }, + { 0x0981d504, 0x24000001 }, + { 0x0981d507, 0x24000003 }, + { 0x0981d50d, 0x24000007 }, + { 0x0981d516, 0x24000006 }, + { 0x0981d51e, 0x14000019 }, + { 0x0981d538, 0x24000001 }, + { 0x0981d53b, 0x24000003 }, + { 0x0981d540, 0x24000004 }, + { 0x0901d546, 0x24000000 }, + { 0x0981d54a, 0x24000006 }, + { 0x0981d552, 0x14000019 }, + { 0x0981d56c, 0x24000019 }, + { 0x0981d586, 0x14000019 }, + { 0x0981d5a0, 0x24000019 }, + { 0x0981d5ba, 0x14000019 }, + { 0x0981d5d4, 0x24000019 }, + { 0x0981d5ee, 0x14000019 }, + { 0x0981d608, 0x24000019 }, + { 0x0981d622, 0x14000019 }, + { 0x0981d63c, 0x24000019 }, + { 0x0981d656, 0x14000019 }, + { 0x0981d670, 0x24000019 }, + { 0x0981d68a, 0x1400001b }, + { 0x0981d6a8, 0x24000018 }, + { 0x0901d6c1, 0x64000000 }, + { 0x0981d6c2, 0x14000018 }, + { 0x0901d6db, 0x64000000 }, + { 0x0981d6dc, 0x14000005 }, + { 0x0981d6e2, 0x24000018 }, + { 0x0901d6fb, 0x64000000 }, + { 0x0981d6fc, 0x14000018 }, + { 0x0901d715, 0x64000000 }, + { 0x0981d716, 0x14000005 }, + { 0x0981d71c, 0x24000018 }, + { 0x0901d735, 0x64000000 }, + { 0x0981d736, 0x14000018 }, + { 0x0901d74f, 0x64000000 }, + { 0x0981d750, 0x14000005 }, + { 0x0981d756, 0x24000018 }, + { 0x0901d76f, 0x64000000 }, + { 0x0981d770, 0x14000018 }, + { 0x0901d789, 0x64000000 }, + { 0x0981d78a, 0x14000005 }, + { 0x0981d790, 0x24000018 }, + { 0x0901d7a9, 0x64000000 }, + { 0x0981d7aa, 0x14000018 }, + { 0x0901d7c3, 0x64000000 }, + { 0x0981d7c4, 0x14000005 }, + { 0x0901d7ca, 0x24000000 }, + { 0x0901d7cb, 0x14000000 }, + { 0x0981d7ce, 0x34000031 }, + { 0x16820000, 0x1c00a6d6 }, + { 0x1682f800, 0x1c00021d }, + { 0x090e0001, 0x04000000 }, + { 0x098e0020, 0x0400005f }, + { 0x1b8e0100, 0x300000ef }, + { 0x098f0000, 0x0c00fffd }, + { 0x09900000, 0x0c00fffd }, +}; diff --git a/rpm/init.d-mongod b/rpm/init.d-mongod new file mode 100644 index 0000000..c099ef9 --- /dev/null +++ b/rpm/init.d-mongod @@ -0,0 +1,63 @@ +#!/bin/bash + +# mongod - Startup script for mongod + +# chkconfig: 35 85 15 +# description: Mongo is a scalable, document-oriented database. +# processname: mongod +# config: /etc/mongod.conf +# pidfile: /var/run/mongo/mongo.pid + +. /etc/rc.d/init.d/functions + +# things from mongod.conf get there by mongod reading it + +OPTIONS=" -f /etc/mongod.conf" + +mongod=${MONGOD-/usr/bin/mongod} +pidfile=${PIDFILE-/var/run/mongod.pid} +lockfile=${LOCKFILE-/var/lock/subsys/mongod} + +start() +{ + echo -n $"Starting mongod: " + #daemon --pidfile=${pidfile} $mongod $OPTIONS > /var/log/mongod + $mongod $OPTIONS > /var/log/mongod 2>&1 & + RETVAL=$? + [ $RETVAL = 0 ] && touch ${lockfile} + echo OK +} + +stop() +{ + echo -n $"Stopping mongod: " + #killproc -p ${pidfile} -d 10 $mongod + #RETVAL=$? + killall mongod > /dev/null 2>&1 + #[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} + echo OK +} + +ulimit -n 12000 +RETVAL=0 + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; +# status) +# status -p ${pidfile} $mongod +# ;; + *) + echo $"Usage: $0 {start|stop|restart}" + RETVAL=1 +esac + +exit $RETVAL diff --git a/rpm/mongo.spec b/rpm/mongo.spec new file mode 100644 index 0000000..f58b61c --- /dev/null +++ b/rpm/mongo.spec @@ -0,0 +1,128 @@ +Name: mongo +Version: 1.3.1 +Release: mongodb_1%{?dist} +Summary: mongo client shell and tools +License: AGPL 3.0 +URL: http://www.mongodb.org +Group: Applications/Databases + +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRequires: js-devel, readline-devel, boost-devel, pcre-devel +BuildRequires: gcc-c++, scons + +%description +Mongo (from "huMONGOus") is a schema-free document-oriented database. +It features dynamic profileable queries, full indexing, replication +and fail-over support, efficient storage of large binary data objects, +and auto-sharding. + +This package provides the mongo shell, import/export tools, and other +client utilities. + +%package server +Summary: mongo server, sharding server, and support scripts +Group: Applications/Databases + +%description server +Mongo (from "huMONGOus") is a schema-free document-oriented database. + +This package provides the mongo server software, mongo sharding server +softwware, default configuration files, and init.d scripts. + +%package devel +Summary: Headers and libraries for mongo development. +Group: Applications/Databases + +%description devel +Mongo (from "huMONGOus") is a schema-free document-oriented database. + +This package provides the mongo static library and header files needed +to develop mongo client software. + +%prep +%setup + +%build +scons --prefix=$RPM_BUILD_ROOT/usr all +# XXX really should have shared library here + +%install +scons --prefix=$RPM_BUILD_ROOT/usr install +mkdir -p $RPM_BUILD_ROOT/usr/share/man/man1 +cp debian/*.1 $RPM_BUILD_ROOT/usr/share/man/man1/ +mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d +cp rpm/init.d-mongod $RPM_BUILD_ROOT/etc/rc.d/init.d/mongod +chmod a+x $RPM_BUILD_ROOT/etc/rc.d/init.d/mongod +mkdir -p $RPM_BUILD_ROOT/etc +cp rpm/mongod.conf $RPM_BUILD_ROOT/etc/mongod.conf +mkdir -p $RPM_BUILD_ROOT/var/lib/mongo +mkdir -p $RPM_BUILD_ROOT/var/log +touch $RPM_BUILD_ROOT/var/log/mongo + +%clean +scons -c +rm -rf $RPM_BUILD_ROOT + +%pre server +#/usr/sbin/useradd -M -o -r -d /var/mongo -s /bin/bash \ +# -c "mongod" mongod > /dev/null 2>&1 || : + +%post server +if test $1 = 1 +then + /sbin/chkconfig --add mongod +fi + +%preun server +if test $1 = 0 +then + /sbin/chkconfig --del mongod +fi + +%postun server +if test $1 -ge 1 +then + /sbin/service mongod stop >/dev/null 2>&1 || : +fi + +%files +%defattr(-,root,root,-) +%doc README GNU-AGPL-3.0.txt + +%{_bindir}/mongo +%{_bindir}/mongodump +%{_bindir}/mongoexport +%{_bindir}/mongofiles +%{_bindir}/mongoimport +%{_bindir}/mongorestore + +%{_mandir}/man1/mongo.1* +%{_mandir}/man1/mongodump.1* +%{_mandir}/man1/mongoexport.1* +%{_mandir}/man1/mongofiles.1* +%{_mandir}/man1/mongoimport.1* +%{_mandir}/man1/mongorestore.1* + +%files server +%defattr(-,root,root,-) +%config(noreplace) /etc/mongod.conf +%{_bindir}/mongod +%{_bindir}/mongos +#%{_mandir}/man1/mongod.1* +%{_mandir}/man1/mongos.1* +/etc/rc.d/init.d/mongod +/etc/sysconfig/mongod +#/etc/rc.d/init.d/mongos +%attr(0755,root,root) %dir /var/mongo +%attr(0640,root,root) %config(noreplace) %verify(not md5 size mtime) /var/log/mongo + +%files devel +/usr/include/mongo +%{_libdir}/libmongoclient.a +#%{_libdir}/libmongotestfiles.a + +%changelog +* Sat Oct 24 2009 Joe Miklojcik <jmiklojcik@shopwiki.com> - +- Wrote mongo.spec. + diff --git a/rpm/mongod.conf b/rpm/mongod.conf new file mode 100644 index 0000000..0c87186 --- /dev/null +++ b/rpm/mongod.conf @@ -0,0 +1,86 @@ +# mongo.conf + +#where to log +logpath=/var/log/mongod + +#port = 27017 + +dbpath=/var/lib/mongo + +# Enables periodic logging of CPU utilization and I/O wait +#cpu = true + +# Turn on/off security. Off is currently the default +#noauth = true +#auth = true + +# Verbose logging output. +#verbose = true + +# Inspect all client data for validity on receipt (useful for +# developing drivers) +#objcheck = true + +# Enable db quota management +#quota = true + +# Set oplogging level where n is +# 0=off (default) +# 1=W +# 2=R +# 3=both +# 7=W+some reads +#oplog = 0 + +# Diagnostic/debugging option +#nocursors = true + +# Ignore query hints +#nohints = true + +# Disable the HTTP interface (Defaults to localhost:27018). +#nohttpinterface = true + +# Turns off server-side scripting. This will result in greatly limited +# functionality +#noscripting = true + +# Turns off table scans. Any query that would do a table scan fails. +#notablescan = true + +# Disable data file preallocation. +#noprealloc = true + +# Specify .ns file size for new databases. +# nssize = <size> + +# Accout token for Mongo monitoring server. +#mms-token = <token> + +# Server name for Mongo monitoring server. +#mms-name = <server-name> + +# Ping interval for Mongo monitoring server. +#mms-interval = <seconds> + +# Replication Options + +# in replicated mongo databases, specify here whether this is a slave or master +#slave = true +#source = master.example.com +# Slave only: specify a single database to replicate +#only = master.example.com +# or +#master = true +#source = slave.example.com + +# Address of a server to pair with. +#pairwith = <server:port> +# Address of arbiter server. +#arbiter = <server:port> +# Automatically resync if slave data is stale +#autoresync +# Custom size for replication operation log. +#oplogSize = <MB> +# Size limit for in-memory storage of op ids. +#opIdMem = <bytes> diff --git a/s/chunk.cpp b/s/chunk.cpp new file mode 100644 index 0000000..47c13e8 --- /dev/null +++ b/s/chunk.cpp @@ -0,0 +1,628 @@ +// shard.cpp + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "chunk.h" +#include "config.h" +#include "../util/unittest.h" +#include "../client/connpool.h" +#include "cursors.h" +#include "strategy.h" + +namespace mongo { + + // ------- Shard -------- + + long Chunk::MaxChunkSize = 1024 * 1204 * 50; + + Chunk::Chunk( ChunkManager * manager ) : _manager( manager ){ + _modified = false; + _lastmod = 0; + _dataWritten = 0; + } + + void Chunk::setShard( string s ){ + _shard = s; + _markModified(); + } + + bool Chunk::contains( const BSONObj& obj ){ + return + _manager->getShardKey().compare( getMin() , obj ) <= 0 && + _manager->getShardKey().compare( obj , getMax() ) < 0; + } + + BSONObj Chunk::pickSplitPoint(){ + int sort = 0; + + if ( _manager->getShardKey().globalMin().woCompare( getMin() ) == 0 ){ + sort = 1; + } + else if ( _manager->getShardKey().globalMax().woCompare( getMax() ) == 0 ){ + sort = -1; + } + + if ( sort ){ + ScopedDbConnection conn( getShard() ); + Query q; + if ( sort == 1 ) + q.sort( _manager->getShardKey().key() ); + else { + BSONObj k = _manager->getShardKey().key(); + BSONObjBuilder r; + + BSONObjIterator i(k); + while( i.more() ) { + BSONElement e = i.next(); + uassert( 10163 , "can only handle numbers here - which i think is correct" , e.isNumber() ); + r.append( e.fieldName() , -1 * e.number() ); + } + + q.sort( r.obj() ); + } + BSONObj end = conn->findOne( _ns , q ); + conn.done(); + + if ( ! end.isEmpty() ) + return _manager->getShardKey().extractKey( end ); + } + + ScopedDbConnection conn( getShard() ); + BSONObj result; + if ( ! conn->runCommand( "admin" , BSON( "medianKey" << _ns + << "keyPattern" << _manager->getShardKey().key() + << "min" << getMin() + << "max" << getMax() + ) , result ) ){ + stringstream ss; + ss << "medianKey command failed: " << result; + uassert( 10164 , ss.str() , 0 ); + } + conn.done(); + + return result.getObjectField( "median" ).getOwned(); + } + + Chunk * Chunk::split(){ + return split( pickSplitPoint() ); + } + + Chunk * Chunk::split( const BSONObj& m ){ + uassert( 10165 , "can't split as shard that doesn't have a manager" , _manager ); + + log(1) << " before split on: " << m << "\n" + << "\t self : " << toString() << endl; + + uassert( 10166 , "locking namespace on server failed" , lockNamespaceOnServer( getShard() , _ns ) ); + + Chunk * s = new Chunk( _manager ); + s->_ns = _ns; + s->_shard = _shard; + s->setMin(m.getOwned()); + s->setMax(_max); + + s->_markModified(); + _markModified(); + + _manager->_chunks.push_back( s ); + + setMax(m.getOwned()); + + log(1) << " after split:\n" + << "\t left : " << toString() << "\n" + << "\t right: "<< s->toString() << endl; + + + _manager->save(); + + return s; + } + + bool Chunk::moveAndCommit( const string& to , string& errmsg ){ + uassert( 10167 , "can't move shard to its current location!" , to != getShard() ); + + log() << "moving chunk ns: " << _ns << " moving chunk: " << toString() << " " << _shard << " -> " << to << endl; + + string from = _shard; + ShardChunkVersion oldVersion = _manager->getVersion( from ); + + BSONObj filter; + { + BSONObjBuilder b; + getFilter( b ); + filter = b.obj(); + } + + ScopedDbConnection fromconn( from ); + + BSONObj startRes; + bool worked = fromconn->runCommand( "admin" , + BSON( "movechunk.start" << _ns << + "from" << from << + "to" << to << + "filter" << filter + ) , + startRes + ); + + if ( ! worked ){ + errmsg = (string)"movechunk.start failed: " + startRes.toString(); + fromconn.done(); + return false; + } + + // update config db + setShard( to ); + + // need to increment version # for old server + Chunk * randomChunkOnOldServer = _manager->findChunkOnServer( from ); + if ( randomChunkOnOldServer ) + randomChunkOnOldServer->_markModified(); + + _manager->save(); + + BSONObj finishRes; + { + + ShardChunkVersion newVersion = _manager->getVersion( from ); + if ( newVersion == 0 && oldVersion > 0 ){ + newVersion = oldVersion; + newVersion++; + _manager->save(); + } + else if ( newVersion <= oldVersion ){ + log() << "newVersion: " << newVersion << " oldVersion: " << oldVersion << endl; + uassert( 10168 , "version has to be higher" , newVersion > oldVersion ); + } + + BSONObjBuilder b; + b << "movechunk.finish" << _ns; + b << "to" << to; + b.appendTimestamp( "newVersion" , newVersion ); + b.append( startRes["finishToken"] ); + + worked = fromconn->runCommand( "admin" , + b.done() , + finishRes ); + } + + if ( ! worked ){ + errmsg = (string)"movechunk.finish failed: " + finishRes.toString(); + fromconn.done(); + return false; + } + + fromconn.done(); + return true; + } + + bool Chunk::splitIfShould( long dataWritten ){ + _dataWritten += dataWritten; + + if ( _dataWritten < MaxChunkSize / 5 ) + return false; + + _dataWritten = 0; + + if ( _min.woCompare( _max ) == 0 ){ + log() << "SHARD PROBLEM** shard is too big, but can't split: " << toString() << endl; + return false; + } + + long size = getPhysicalSize(); + if ( size < MaxChunkSize ) + return false; + + log() << "autosplitting " << _ns << " size: " << size << " shard: " << toString() << endl; + Chunk * newShard = split(); + + moveIfShould( newShard ); + + return true; + } + + bool Chunk::moveIfShould( Chunk * newChunk ){ + Chunk * toMove = 0; + + if ( newChunk->countObjects() <= 1 ){ + toMove = newChunk; + } + else if ( this->countObjects() <= 1 ){ + toMove = this; + } + else { + log(1) << "don't know how to decide if i should move inner shard" << endl; + } + + if ( ! toMove ) + return false; + + string newLocation = grid.pickShardForNewDB(); + if ( newLocation == getShard() ){ + // if this is the best server, then we shouldn't do anything! + log(1) << "not moving chunk: " << toString() << " b/c would move to same place " << newLocation << " -> " << getShard() << endl; + return 0; + } + + log() << "moving chunk (auto): " << toMove->toString() << " to: " << newLocation << " #objcets: " << toMove->countObjects() << endl; + + string errmsg; + massert( 10412 , (string)"moveAndCommit failed: " + errmsg , + toMove->moveAndCommit( newLocation , errmsg ) ); + + return true; + } + + long Chunk::getPhysicalSize(){ + ScopedDbConnection conn( getShard() ); + + BSONObj result; + uassert( 10169 , "datasize failed!" , conn->runCommand( "admin" , BSON( "datasize" << _ns + << "keyPattern" << _manager->getShardKey().key() + << "min" << getMin() + << "max" << getMax() + ) , result ) ); + + conn.done(); + return (long)result["size"].number(); + } + + + long Chunk::countObjects( const BSONObj& filter ){ + ScopedDbConnection conn( getShard() ); + + BSONObj f = getFilter(); + if ( ! filter.isEmpty() ) + f = ClusteredCursor::concatQuery( f , filter ); + + BSONObj result; + unsigned long long n = conn->count( _ns , f ); + + conn.done(); + return (long)n; + } + + bool Chunk::operator==( const Chunk& s ){ + return + _manager->getShardKey().compare( _min , s._min ) == 0 && + _manager->getShardKey().compare( _max , s._max ) == 0 + ; + } + + void Chunk::getFilter( BSONObjBuilder& b ){ + _manager->_key.getFilter( b , _min , _max ); + } + + void Chunk::serialize(BSONObjBuilder& to){ + if ( _lastmod ) + to.appendTimestamp( "lastmod" , _lastmod ); + else + to.appendTimestamp( "lastmod" ); + + to << "ns" << _ns; + to << "min" << _min; + to << "max" << _max; + to << "shard" << _shard; + } + + void Chunk::unserialize(const BSONObj& from){ + _ns = from.getStringField( "ns" ); + _shard = from.getStringField( "shard" ); + _lastmod = from.hasField( "lastmod" ) ? from["lastmod"]._numberLong() : 0; + + BSONElement e = from["minDotted"]; + cout << from << endl; + if (e.eoo()){ + _min = from.getObjectField( "min" ).getOwned(); + _max = from.getObjectField( "max" ).getOwned(); + } else { // TODO delete this case after giving people a chance to migrate + _min = e.embeddedObject().getOwned(); + _max = from.getObjectField( "maxDotted" ).getOwned(); + } + + uassert( 10170 , "Chunk needs a ns" , ! _ns.empty() ); + uassert( 10171 , "Chunk needs a server" , ! _ns.empty() ); + + uassert( 10172 , "Chunk needs a min" , ! _min.isEmpty() ); + uassert( 10173 , "Chunk needs a max" , ! _max.isEmpty() ); + } + + string Chunk::modelServer() { + // TODO: this could move around? + return configServer.modelServer(); + } + + void Chunk::_markModified(){ + _modified = true; + // set to 0 so that the config server sets it + _lastmod = 0; + } + + void Chunk::save( bool check ){ + bool reload = ! _lastmod; + Model::save( check ); + if ( reload ){ + // need to do this so that we get the new _lastMod and therefore version number + massert( 10413 , "_id has to be filled in already" , ! _id.isEmpty() ); + + string b = toString(); + BSONObj q = _id.copy(); + massert( 10414 , "how could load fail?" , load( q ) ); + log(2) << "before: " << q << "\t" << b << endl; + log(2) << "after : " << _id << "\t" << toString() << endl; + massert( 10415 , "chunk reload changed content!" , b == toString() ); + massert( 10416 , "id changed!" , q["_id"] == _id["_id"] ); + } + } + + void Chunk::ensureIndex(){ + ScopedDbConnection conn( getShard() ); + conn->ensureIndex( _ns , _manager->getShardKey().key() , _manager->_unique ); + conn.done(); + } + + string Chunk::toString() const { + stringstream ss; + ss << "shard ns:" << _ns << " shard: " << _shard << " min: " << _min << " max: " << _max; + return ss.str(); + } + + + ShardKeyPattern Chunk::skey(){ + return _manager->getShardKey(); + } + + // ------- ChunkManager -------- + + unsigned long long ChunkManager::NextSequenceNumber = 1; + + ChunkManager::ChunkManager( DBConfig * config , string ns , ShardKeyPattern pattern , bool unique ) : + _config( config ) , _ns( ns ) , _key( pattern ) , _unique( unique ){ + Chunk temp(0); + + ScopedDbConnection conn( temp.modelServer() ); + auto_ptr<DBClientCursor> cursor = conn->query( temp.getNS() , BSON( "ns" << ns ) ); + while ( cursor->more() ){ + BSONObj d = cursor->next(); + if ( d["isMaxMarker"].trueValue() ){ + continue; + } + + Chunk * c = new Chunk( this ); + c->unserialize( d ); + _chunks.push_back( c ); + c->_id = d["_id"].wrap().getOwned(); + } + conn.done(); + + if ( _chunks.size() == 0 ){ + Chunk * c = new Chunk( this ); + c->_ns = ns; + c->setMin(_key.globalMin()); + c->setMax(_key.globalMax()); + c->_shard = config->getPrimary(); + c->_markModified(); + + _chunks.push_back( c ); + + log() << "no chunks for:" << ns << " so creating first: " << c->toString() << endl; + } + + _sequenceNumber = ++NextSequenceNumber; + } + + ChunkManager::~ChunkManager(){ + for ( vector<Chunk*>::iterator i=_chunks.begin(); i != _chunks.end(); i++ ){ + delete( *i ); + } + _chunks.clear(); + } + + bool ChunkManager::hasShardKey( const BSONObj& obj ){ + return _key.hasShardKey( obj ); + } + + Chunk& ChunkManager::findChunk( const BSONObj & obj ){ + + for ( vector<Chunk*>::iterator i=_chunks.begin(); i != _chunks.end(); i++ ){ + Chunk * c = *i; + if ( c->contains( obj ) ) + return *c; + } + stringstream ss; + ss << "couldn't find a chunk which should be impossible extracted: " << _key.extractKey( obj ); + throw UserException( 8070 , ss.str() ); + } + + Chunk* ChunkManager::findChunkOnServer( const string& server ) const { + + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk * c = *i; + if ( c->getShard() == server ) + return c; + } + + return 0; + } + + int ChunkManager::getChunksForQuery( vector<Chunk*>& chunks , const BSONObj& query ){ + int added = 0; + + for ( vector<Chunk*>::iterator i=_chunks.begin(); i != _chunks.end(); i++ ){ + Chunk * c = *i; + if ( _key.relevantForQuery( query , c ) ){ + chunks.push_back( c ); + added++; + } + } + return added; + } + + void ChunkManager::getAllServers( set<string>& allServers ){ + for ( vector<Chunk*>::iterator i=_chunks.begin(); i != _chunks.end(); i++ ){ + allServers.insert( (*i)->getShard() ); + } + } + + void ChunkManager::ensureIndex(){ + set<string> seen; + + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk * c = *i; + if ( seen.count( c->getShard() ) ) + continue; + seen.insert( c->getShard() ); + c->ensureIndex(); + } + } + + void ChunkManager::drop(){ + uassert( 10174 , "config servers not all up" , configServer.allUp() ); + + map<string,ShardChunkVersion> seen; + + log(1) << "ChunkManager::drop : " << _ns << endl; + + // lock all shards so no one can do a split/migrate + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk * c = *i; + ShardChunkVersion& version = seen[ c->getShard() ]; + if ( version ) + continue; + version = lockNamespaceOnServer( c->getShard() , _ns ); + if ( version ) + continue; + + // rollback + uassert( 10175 , "don't know how to rollback locks b/c drop can't lock all shards" , 0 ); + } + + log(1) << "ChunkManager::drop : " << _ns << "\t all locked" << endl; + + // wipe my meta-data + _chunks.clear(); + + + // delete data from mongod + for ( map<string,ShardChunkVersion>::iterator i=seen.begin(); i!=seen.end(); i++ ){ + string shard = i->first; + ScopedDbConnection conn( shard ); + conn->dropCollection( _ns ); + conn.done(); + } + + log(1) << "ChunkManager::drop : " << _ns << "\t removed shard data" << endl; + + // clean up database meta-data + uassert( 10176 , "no sharding data?" , _config->removeSharding( _ns ) ); + _config->save(); + + + // remove chunk data + Chunk temp(0); + ScopedDbConnection conn( temp.modelServer() ); + conn->remove( temp.getNS() , BSON( "ns" << _ns ) ); + conn.done(); + log(1) << "ChunkManager::drop : " << _ns << "\t removed chunk data" << endl; + + for ( map<string,ShardChunkVersion>::iterator i=seen.begin(); i!=seen.end(); i++ ){ + ScopedDbConnection conn( i->first ); + BSONObj res; + if ( ! setShardVersion( conn.conn() , _ns , 0 , true , res ) ) + throw UserException( 8071 , (string)"OH KNOW, cleaning up after drop failed: " + res.toString() ); + conn.done(); + } + + + log(1) << "ChunkManager::drop : " << _ns << "\t DONE" << endl; + } + + void ChunkManager::save(){ + ShardChunkVersion a = getVersion(); + + set<string> withRealChunks; + + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk* c = *i; + if ( ! c->_modified ) + continue; + c->save( true ); + _sequenceNumber = ++NextSequenceNumber; + + withRealChunks.insert( c->getShard() ); + } + + massert( 10417 , "how did version get smalled" , getVersion() >= a ); + + ensureIndex(); // TODO: this is too aggressive - but not really sooo bad + } + + ShardChunkVersion ChunkManager::getVersion( const string& server ) const{ + // TODO: cache or something? + + ShardChunkVersion max = 0; + + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk* c = *i; + if ( c->getShard() != server ) + continue; + + if ( c->_lastmod > max ) + max = c->_lastmod; + } + + return max; + } + + ShardChunkVersion ChunkManager::getVersion() const{ + ShardChunkVersion max = 0; + + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + Chunk* c = *i; + if ( c->_lastmod > max ) + max = c->_lastmod; + } + + return max; + } + + string ChunkManager::toString() const { + stringstream ss; + ss << "ChunkManager: " << _ns << " key:" << _key.toString() << "\n"; + for ( vector<Chunk*>::const_iterator i=_chunks.begin(); i!=_chunks.end(); i++ ){ + const Chunk* c = *i; + ss << "\t" << c->toString() << "\n"; + } + return ss.str(); + } + + + class ChunkObjUnitTest : public UnitTest { + public: + void runShard(){ + + } + + void run(){ + runShard(); + log(1) << "shardObjTest passed" << endl; + } + } shardObjTest; + + +} // namespace mongo diff --git a/s/chunk.h b/s/chunk.h new file mode 100644 index 0000000..7395133 --- /dev/null +++ b/s/chunk.h @@ -0,0 +1,221 @@ +// shard.h + +/* + A "shard" is a database (replica pair typically) which represents + one partition of the overall database. +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "../client/dbclient.h" +#include "../client/model.h" +#include "shardkey.h" +#include <boost/utility.hpp> +#undef assert +#define assert xassert + +namespace mongo { + + class DBConfig; + class ChunkManager; + class ChunkObjUnitTest; + + typedef unsigned long long ShardChunkVersion; + + /** + config.chunks + { ns : "alleyinsider.fs.chunks" , min : {} , max : {} , server : "localhost:30001" } + + x is in a shard iff + min <= x < max + */ + class Chunk : public Model , boost::noncopyable { + public: + + Chunk( ChunkManager * info ); + + const BSONObj& getMin() const { return _min; } + const BSONObj& getMax() const { return _max; } + + void setMin(const BSONObj& o){ + _min = o; + } + void setMax(const BSONObj& o){ + _max = o; + } + + string getShard(){ + return _shard; + } + void setShard( string shard ); + + bool contains( const BSONObj& obj ); + + string toString() const; + operator string() const { return toString(); } + + bool operator==(const Chunk& s); + + bool operator!=(const Chunk& s){ + return ! ( *this == s ); + } + + void getFilter( BSONObjBuilder& b ); + BSONObj getFilter(){ BSONObjBuilder b; getFilter( b ); return b.obj(); } + + + BSONObj pickSplitPoint(); + Chunk * split(); + Chunk * split( const BSONObj& middle ); + + /** + * @return size of shard in bytes + * talks to mongod to do this + */ + long getPhysicalSize(); + + long countObjects( const BSONObj& filter = BSONObj() ); + + /** + * if the amount of data written nears the max size of a shard + * then we check the real size, and if its too big, we split + */ + bool splitIfShould( long dataWritten ); + + + /* + * moves either this shard or newShard if it makes sense too + * @return whether or not a shard was moved + */ + bool moveIfShould( Chunk * newShard = 0 ); + + bool moveAndCommit( const string& to , string& errmsg ); + + virtual const char * getNS(){ return "config.chunks"; } + virtual void serialize(BSONObjBuilder& to); + virtual void unserialize(const BSONObj& from); + virtual string modelServer(); + + virtual void save( bool check=false ); + + void ensureIndex(); + + void _markModified(); + + static long MaxChunkSize; + + private: + + // main shard info + + ChunkManager * _manager; + ShardKeyPattern skey(); + + string _ns; + BSONObj _min; + BSONObj _max; + string _shard; + ShardChunkVersion _lastmod; + + bool _modified; + + // transient stuff + + long _dataWritten; + + // methods, etc.. + + void _split( BSONObj& middle ); + + friend class ChunkManager; + friend class ShardObjUnitTest; + }; + + /* config.sharding + { ns: 'alleyinsider.fs.chunks' , + key: { ts : 1 } , + shards: [ { min: 1, max: 100, server: a } , { min: 101, max: 200 , server : b } ] + } + */ + class ChunkManager { + public: + + ChunkManager( DBConfig * config , string ns , ShardKeyPattern pattern , bool unique ); + virtual ~ChunkManager(); + + string getns(){ + return _ns; + } + + int numChunks(){ return _chunks.size(); } + Chunk* getChunk( int i ){ return _chunks[i]; } + bool hasShardKey( const BSONObj& obj ); + + Chunk& findChunk( const BSONObj& obj ); + Chunk* findChunkOnServer( const string& server ) const; + + ShardKeyPattern& getShardKey(){ return _key; } + bool isUnique(){ return _unique; } + + /** + * makes sure the shard index is on all servers + */ + void ensureIndex(); + + /** + * @return number of Chunk added to the vector + */ + int getChunksForQuery( vector<Chunk*>& chunks , const BSONObj& query ); + + void getAllServers( set<string>& allServers ); + + void save(); + + string toString() const; + operator string() const { return toString(); } + + ShardChunkVersion getVersion( const string& server ) const; + ShardChunkVersion getVersion() const; + + /** + * this is just an increasing number of how many ChunkManagers we have so we know if something has been updated + */ + unsigned long long getSequenceNumber(){ + return _sequenceNumber; + } + + void drop(); + + private: + DBConfig * _config; + string _ns; + ShardKeyPattern _key; + bool _unique; + + vector<Chunk*> _chunks; + map<string,unsigned long long> _maxMarkers; + + unsigned long long _sequenceNumber; + + friend class Chunk; + static unsigned long long NextSequenceNumber; + }; + +} // namespace mongo diff --git a/s/commands_admin.cpp b/s/commands_admin.cpp new file mode 100644 index 0000000..e79b529 --- /dev/null +++ b/s/commands_admin.cpp @@ -0,0 +1,696 @@ +// s/commands_admin.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* TODO + _ concurrency control. + _ limit() works right? + _ KillCursors + + later + _ secondary indexes +*/ + +#include "stdafx.h" +#include "../util/message.h" +#include "../db/dbmessage.h" +#include "../client/connpool.h" +#include "../db/commands.h" + +#include "config.h" +#include "chunk.h" +#include "strategy.h" + +namespace mongo { + + extern string ourHostname; + + namespace dbgrid_cmds { + + set<string> dbgridCommands; + + class GridAdminCmd : public Command { + public: + GridAdminCmd( const char * n ) : Command( n ){ + dbgridCommands.insert( n ); + } + virtual bool slaveOk(){ + return true; + } + virtual bool adminOnly() { + return true; + } + }; + + // --------------- misc commands ---------------------- + + class NetStatCmd : public GridAdminCmd { + public: + NetStatCmd() : GridAdminCmd("netstat") { } + virtual void help( stringstream& help ) const { + help << " shows status/reachability of servers in the cluster"; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + result.append("configserver", configServer.getPrimary() ); + result.append("isdbgrid", 1); + return true; + } + } netstat; + + class ListGridCommands : public GridAdminCmd { + public: + ListGridCommands() : GridAdminCmd("gridcommands") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + BSONObjBuilder arr; + int num=0; + for ( set<string>::iterator i = dbgridCommands.begin(); i != dbgridCommands.end(); i++ ){ + string s = BSONObjBuilder::numStr( num++ ); + arr.append( s.c_str() , *i ); + } + + result.appendArray( "commands" , arr.done() ); + return true; + } + } listGridCommands; + + // ------------ database level commands ------------- + + class ListDatabaseCommand : public GridAdminCmd { + public: + ListDatabaseCommand() : GridAdminCmd("listdatabases") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + ScopedDbConnection conn( configServer.getPrimary() ); + + auto_ptr<DBClientCursor> cursor = conn->query( "config.databases" , BSONObj() ); + + BSONObjBuilder list; + int num = 0; + while ( cursor->more() ){ + string s = BSONObjBuilder::numStr( num++ ); + + BSONObj o = cursor->next(); + list.append( s.c_str() , o["name"].valuestrsafe() ); + } + + result.appendArray("databases" , list.obj() ); + conn.done(); + + return true; + } + } gridListDatabase; + + class MoveDatabasePrimaryCommand : public GridAdminCmd { + public: + MoveDatabasePrimaryCommand() : GridAdminCmd("moveprimary") { } + virtual void help( stringstream& help ) const { + help << " example: { moveprimary : 'foo' , to : 'localhost:9999' } TODO: locking? "; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string dbname = cmdObj["moveprimary"].valuestrsafe(); + + if ( dbname.size() == 0 ){ + errmsg = "no db"; + return false; + } + + if ( dbname == "config" ){ + errmsg = "can't move config db"; + return false; + } + + DBConfig * config = grid.getDBConfig( dbname , false ); + if ( ! config ){ + errmsg = "can't find db!"; + return false; + } + + string to = cmdObj["to"].valuestrsafe(); + if ( ! to.size() ){ + errmsg = "you have to specify where you want to move it"; + return false; + } + + if ( to == config->getPrimary() ){ + errmsg = "thats already the primary"; + return false; + } + + if ( ! grid.knowAboutShard( to ) ){ + errmsg = "that server isn't known to me"; + return false; + } + + ScopedDbConnection conn( configServer.getPrimary() ); + + log() << "moving " << dbname << " primary from: " << config->getPrimary() << " to: " << to << endl; + + // TODO LOCKING: this is not safe with multiple mongos + + + ScopedDbConnection toconn( to ); + + // TODO AARON - we need a clone command which replays operations from clone start to now + // using a seperate smaller oplog + BSONObj cloneRes; + bool worked = toconn->runCommand( dbname.c_str() , BSON( "clone" << config->getPrimary() ) , cloneRes ); + toconn.done(); + if ( ! worked ){ + log() << "clone failed" << cloneRes << endl; + errmsg = "clone failed"; + conn.done(); + return false; + } + + ScopedDbConnection fromconn( config->getPrimary() ); + + config->setPrimary( to ); + config->save( true ); + + log() << " dropping " << dbname << " from old" << endl; + + fromconn->dropDatabase( dbname.c_str() ); + fromconn.done(); + + result << "primary" << to; + + conn.done(); + return true; + } + } movePrimary; + + class EnableShardingCmd : public GridAdminCmd { + public: + EnableShardingCmd() : GridAdminCmd( "enablesharding" ){} + virtual void help( stringstream& help ) const { + help + << "Enable sharding for a db. (Use 'shardcollection' command afterwards.)\n" + << " { enablesharding : \"<dbname>\" }\n"; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string dbname = cmdObj["enablesharding"].valuestrsafe(); + if ( dbname.size() == 0 ){ + errmsg = "no db"; + return false; + } + + DBConfig * config = grid.getDBConfig( dbname ); + if ( config->isShardingEnabled() ){ + errmsg = "already enabled"; + return false; + } + + log() << "enabling sharding on: " << dbname << endl; + + config->enableSharding(); + config->save( true ); + + return true; + } + } enableShardingCmd; + + // ------------ collection level commands ------------- + + class ShardCollectionCmd : public GridAdminCmd { + public: + ShardCollectionCmd() : GridAdminCmd( "shardcollection" ){} + virtual void help( stringstream& help ) const { + help + << "Shard a collection. Requires key. Optional unique. Sharding must already be enabled for the database.\n" + << " { enablesharding : \"<dbname>\" }\n"; + } + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string ns = cmdObj["shardcollection"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "no ns"; + return false; + } + + DBConfig * config = grid.getDBConfig( ns ); + if ( ! config->isShardingEnabled() ){ + errmsg = "sharding not enabled for db"; + return false; + } + + if ( config->isSharded( ns ) ){ + errmsg = "already sharded"; + return false; + } + + BSONObj key = cmdObj.getObjectField( "key" ); + if ( key.isEmpty() ){ + errmsg = "no shard key"; + return false; + } else if (key.nFields() > 1){ + errmsg = "compound shard keys not supported yet"; + return false; + } + + if ( ns.find( ".system." ) != string::npos ){ + errmsg = "can't shard system namespaces"; + return false; + } + + { + ScopedDbConnection conn( config->getPrimary() ); + BSONObjBuilder b; + b.append( "ns" , ns ); + b.appendBool( "unique" , true ); + if ( conn->count( config->getName() + ".system.indexes" , b.obj() ) ){ + errmsg = "can't shard collection with unique indexes"; + conn.done(); + return false; + } + + BSONObj res = conn->findOne( config->getName() + ".system.namespaces" , BSON( "name" << ns ) ); + if ( res["options"].type() == Object && res["options"].embeddedObject()["capped"].trueValue() ){ + errmsg = "can't shard capped collection"; + conn.done(); + return false; + } + + conn.done(); + } + + log() << "CMD: shardcollection: " << cmdObj << endl; + + config->shardCollection( ns , key , cmdObj["unique"].trueValue() ); + config->save( true ); + + result << "collectionsharded" << ns; + return true; + } + } shardCollectionCmd; + + class GetShardVersion : public GridAdminCmd { + public: + GetShardVersion() : GridAdminCmd( "getShardVersion" ){} + virtual void help( stringstream& help ) const { + help << " example: { getShardVersion : 'alleyinsider.foo' } "; + } + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string ns = cmdObj["getShardVersion"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "need to speciy fully namespace"; + return false; + } + + DBConfig * config = grid.getDBConfig( ns ); + if ( ! config->isSharded( ns ) ){ + errmsg = "ns not sharded."; + return false; + } + + ChunkManager * cm = config->getChunkManager( ns ); + if ( ! cm ){ + errmsg = "no chunk manager?"; + return false; + } + + result.appendTimestamp( "version" , cm->getVersion() ); + + return 1; + } + } getShardVersionCmd; + + class SplitCollectionHelper : public GridAdminCmd { + public: + SplitCollectionHelper( const char * name ) : GridAdminCmd( name ) , _name( name ){} + virtual void help( stringstream& help ) const { + help + << " example: { shard : 'alleyinsider.blog.posts' , find : { ts : 1 } } - split the shard that contains give key \n" + << " example: { shard : 'alleyinsider.blog.posts' , middle : { ts : 1 } } - split the shard that contains the key with this as the middle \n" + << " NOTE: this does not move move the chunks, it merely creates a logical seperation \n" + ; + } + + virtual bool _split( BSONObjBuilder& result , string&errmsg , const string& ns , ChunkManager * manager , Chunk& old , BSONObj middle ) = 0; + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string ns = cmdObj[_name.c_str()].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "no ns"; + return false; + } + + DBConfig * config = grid.getDBConfig( ns ); + if ( ! config->isSharded( ns ) ){ + errmsg = "ns not sharded. have to shard before can split"; + return false; + } + + BSONObj find = cmdObj.getObjectField( "find" ); + if ( find.isEmpty() ){ + find = cmdObj.getObjectField( "middle" ); + + if ( find.isEmpty() ){ + errmsg = "need to specify find or middle"; + return false; + } + } + + ChunkManager * info = config->getChunkManager( ns ); + Chunk& old = info->findChunk( find ); + + return _split( result , errmsg , ns , info , old , cmdObj.getObjectField( "middle" ) ); + } + + protected: + string _name; + }; + + class SplitValueCommand : public SplitCollectionHelper { + public: + SplitValueCommand() : SplitCollectionHelper( "splitvalue" ){} + virtual bool _split( BSONObjBuilder& result , string& errmsg , const string& ns , ChunkManager * manager , Chunk& old , BSONObj middle ){ + + result << "shardinfo" << old.toString(); + + result.appendBool( "auto" , middle.isEmpty() ); + + if ( middle.isEmpty() ) + middle = old.pickSplitPoint(); + + result.append( "middle" , middle ); + + return true; + } + + } splitValueCmd; + + + class SplitCollection : public SplitCollectionHelper { + public: + SplitCollection() : SplitCollectionHelper( "split" ){} + virtual bool _split( BSONObjBuilder& result , string& errmsg , const string& ns , ChunkManager * manager , Chunk& old , BSONObj middle ){ + + log() << "splitting: " << ns << " shard: " << old << endl; + + if ( middle.isEmpty() ) + old.split(); + else + old.split( middle ); + + return true; + } + + + } splitCollectionCmd; + + class MoveChunkCmd : public GridAdminCmd { + public: + MoveChunkCmd() : GridAdminCmd( "movechunk" ){} + virtual void help( stringstream& help ) const { + help << "{ movechunk : 'test.foo' , find : { num : 1 } , to : 'localhost:30001' }"; + } + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string ns = cmdObj["movechunk"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "no ns"; + return false; + } + + DBConfig * config = grid.getDBConfig( ns ); + if ( ! config->isSharded( ns ) ){ + errmsg = "ns not sharded. have to shard before can move a chunk"; + return false; + } + + BSONObj find = cmdObj.getObjectField( "find" ); + if ( find.isEmpty() ){ + errmsg = "need to specify find. see help"; + return false; + } + + string to = cmdObj["to"].valuestrsafe(); + if ( ! to.size() ){ + errmsg = "you have to specify where you want to move the chunk"; + return false; + } + + log() << "CMD: movechunk: " << cmdObj << endl; + + ChunkManager * info = config->getChunkManager( ns ); + Chunk& c = info->findChunk( find ); + string from = c.getShard(); + + if ( from == to ){ + errmsg = "that chunk is already on that shard"; + return false; + } + + if ( ! grid.knowAboutShard( to ) ){ + errmsg = "that shard isn't known to me"; + return false; + } + + if ( ! c.moveAndCommit( to , errmsg ) ) + return false; + + return true; + } + } moveChunkCmd; + + // ------------ server level commands ------------- + + class ListShardsCmd : public GridAdminCmd { + public: + ListShardsCmd() : GridAdminCmd("listshards") { } + virtual void help( stringstream& help ) const { + help << "list all shards of the system"; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + ScopedDbConnection conn( configServer.getPrimary() ); + + vector<BSONObj> all; + auto_ptr<DBClientCursor> cursor = conn->query( "config.shards" , BSONObj() ); + while ( cursor->more() ){ + BSONObj o = cursor->next(); + all.push_back( o ); + } + + result.append("shards" , all ); + conn.done(); + + return true; + } + } listShardsCmd; + + /* a shard is a single mongod server or a replica pair. add it (them) to the cluster as a storage partition. */ + class AddShard : public GridAdminCmd { + public: + AddShard() : GridAdminCmd("addshard") { } + virtual void help( stringstream& help ) const { + help << "add a new shard to the system"; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + ScopedDbConnection conn( configServer.getPrimary() ); + + + string host = cmdObj["addshard"].valuestrsafe(); + + if ( host == "localhost" || host.find( "localhost:" ) == 0 || + host == "127.0.0.1" || host.find( "127.0.0.1:" ) == 0 ){ + if ( cmdObj["allowLocal"].type() != Bool || + ! cmdObj["allowLocal"].boolean() ){ + errmsg = + "can't use localhost as a shard since all shards need to communicate. " + "allowLocal to override for testing"; + return false; + } + } + + if ( host.find( ":" ) == string::npos ){ + stringstream ss; + ss << host << ":" << CmdLine::ShardServerPort; + host = ss.str(); + } + + BSONObj shard; + { + BSONObjBuilder b; + b.append( "host" , host ); + if ( cmdObj["maxSize"].isNumber() ) + b.append( cmdObj["maxSize"] ); + shard = b.obj(); + } + + BSONObj old = conn->findOne( "config.shards" , shard ); + if ( ! old.isEmpty() ){ + result.append( "msg" , "already exists" ); + conn.done(); + return false; + } + + try { + ScopedDbConnection newShardConn( host ); + newShardConn->getLastError(); + newShardConn.done(); + } + catch ( DBException& e ){ + errmsg = "couldn't connect to new shard"; + result.append( "host" , host ); + result.append( "exception" , e.what() ); + conn.done(); + return false; + } + + + + conn->insert( "config.shards" , shard ); + result.append( "added" , shard["host"].valuestrsafe() ); + conn.done(); + return true; + } + } addServer; + + class RemoveShardCmd : public GridAdminCmd { + public: + RemoveShardCmd() : GridAdminCmd("removeshard") { } + virtual void help( stringstream& help ) const { + help << "remove a shard to the system.\nshard must be empty or command will return an error."; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + if ( 1 ){ + errmsg = "removeshard not yet implemented"; + return 0; + } + + ScopedDbConnection conn( configServer.getPrimary() ); + + BSONObj server = BSON( "host" << cmdObj["removeshard"].valuestrsafe() ); + conn->remove( "config.shards" , server ); + + conn.done(); + return true; + } + } removeShardCmd; + + + // --------------- public commands ---------------- + + class IsDbGridCmd : public Command { + public: + virtual bool slaveOk() { + return true; + } + IsDbGridCmd() : Command("isdbgrid") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + result.append("isdbgrid", 1); + result.append("hostname", ourHostname); + return true; + } + } isdbgrid; + + class CmdIsMaster : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "test if this is master half of a replica pair"; + } + CmdIsMaster() : Command("ismaster") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + result.append("ismaster", 1.0 ); + result.append("msg", "isdbgrid"); + return true; + } + } ismaster; + + class CmdShardingGetPrevError : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "get previous error (since last reseterror command)"; + } + CmdShardingGetPrevError() : Command("getpreverror") { } + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + errmsg += "getpreverror not supported for sharded environments"; + return false; + } + } cmdGetPrevError; + + class CmdShardingGetLastError : public Command { + public: + virtual bool requiresAuth() { return false; } + virtual bool slaveOk() { + return true; + } + virtual void help( stringstream& help ) const { + help << "check for an error on the last command executed"; + } + CmdShardingGetLastError() : Command("getlasterror") { } + virtual bool run(const char *nsraw, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) { + string dbName = nsraw; + dbName = dbName.substr( 0 , dbName.size() - 5 ); + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + ClientInfo * client = ClientInfo::get(); + set<string> * shards = client->getPrev(); + + if ( shards->size() == 0 ){ + result.appendNull( "err" ); + return true; + } + + if ( shards->size() == 1 ){ + string theShard = *(shards->begin() ); + result.append( "theshard" , theShard.c_str() ); + ScopedDbConnection conn( theShard ); + BSONObj res; + bool ok = conn->runCommand( conf->getName() , cmdObj , res ); + result.appendElements( res ); + conn.done(); + return ok; + } + + vector<string> errors; + for ( set<string>::iterator i = shards->begin(); i != shards->end(); i++ ){ + string theShard = *i; + ScopedDbConnection conn( theShard ); + string temp = conn->getLastError(); + if ( temp.size() ) + errors.push_back( temp ); + conn.done(); + } + + if ( errors.size() == 0 ){ + result.appendNull( "err" ); + return true; + } + + result.append( "err" , errors[0].c_str() ); + + BSONObjBuilder all; + for ( unsigned i=0; i<errors.size(); i++ ){ + all.append( all.numStr( i ).c_str() , errors[i].c_str() ); + } + result.appendArray( "errs" , all.obj() ); + return true; + } + } cmdGetLastError; + + } + +} // namespace mongo diff --git a/s/commands_public.cpp b/s/commands_public.cpp new file mode 100644 index 0000000..2d3de7a --- /dev/null +++ b/s/commands_public.cpp @@ -0,0 +1,368 @@ +// s/commands_public.cpp + + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "../util/message.h" +#include "../db/dbmessage.h" +#include "../client/connpool.h" +#include "../client/parallel.h" +#include "../db/commands.h" + +#include "config.h" +#include "chunk.h" +#include "strategy.h" + +namespace mongo { + + namespace dbgrid_pub_cmds { + + class PublicGridCommand : public Command { + public: + PublicGridCommand( const char * n ) : Command( n ){ + } + virtual bool slaveOk(){ + return true; + } + virtual bool adminOnly() { + return false; + } + protected: + string getDBName( string ns ){ + return ns.substr( 0 , ns.size() - 5 ); + } + + bool passthrough( DBConfig * conf, const BSONObj& cmdObj , BSONObjBuilder& result ){ + ScopedDbConnection conn( conf->getPrimary() ); + BSONObj res; + bool ok = conn->runCommand( conf->getName() , cmdObj , res ); + result.appendElements( res ); + conn.done(); + return ok; + } + }; + + class NotAllowedOnShardedCollectionCmd : public PublicGridCommand { + public: + NotAllowedOnShardedCollectionCmd( const char * n ) : PublicGridCommand( n ){} + + virtual string getFullNS( const string& dbName , const BSONObj& cmdObj ) = 0; + + virtual bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + string dbName = getDBName( ns ); + string fullns = getFullNS( dbName , cmdObj ); + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + if ( ! conf || ! conf->isShardingEnabled() || ! conf->isSharded( fullns ) ){ + return passthrough( conf , cmdObj , result ); + } + errmsg = "can't do command: " + name + " on sharded collection"; + return false; + } + }; + + // ---- + + class DropCmd : public PublicGridCommand { + public: + DropCmd() : PublicGridCommand( "drop" ){} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + string dbName = getDBName( ns ); + string collection = cmdObj.firstElement().valuestrsafe(); + string fullns = dbName + "." + collection; + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + log() << "DROP: " << fullns << endl; + + if ( ! conf || ! conf->isShardingEnabled() || ! conf->isSharded( fullns ) ){ + return passthrough( conf , cmdObj , result ); + } + + ChunkManager * cm = conf->getChunkManager( fullns ); + massert( 10418 , "how could chunk manager be null!" , cm ); + + cm->drop(); + + return 1; + } + } dropCmd; + + class DropDBCmd : public PublicGridCommand { + public: + DropDBCmd() : PublicGridCommand( "dropDatabase" ){} + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + BSONElement e = cmdObj.firstElement(); + + if ( ! e.isNumber() || e.number() != 1 ){ + errmsg = "invalid params"; + return 0; + } + + string dbName = getDBName( ns ); + DBConfig * conf = grid.getDBConfig( dbName , false ); + + log() << "DROP DATABASE: " << dbName << endl; + + if ( ! conf || ! conf->isShardingEnabled() ){ + log(1) << " passing though drop database for: " << dbName << endl; + return passthrough( conf , cmdObj , result ); + } + + if ( ! conf->dropDatabase( errmsg ) ) + return false; + + result.append( "dropped" , dbName ); + return true; + } + } dropDBCmd; + + class CountCmd : public PublicGridCommand { + public: + CountCmd() : PublicGridCommand("count") { } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + string dbName = getDBName( ns ); + string collection = cmdObj.firstElement().valuestrsafe(); + string fullns = dbName + "." + collection; + + BSONObj filter = cmdObj["query"].embeddedObject(); + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + if ( ! conf || ! conf->isShardingEnabled() || ! conf->isSharded( fullns ) ){ + ScopedDbConnection conn( conf->getPrimary() ); + result.append( "n" , (double)conn->count( fullns , filter ) ); + conn.done(); + return true; + } + + ChunkManager * cm = conf->getChunkManager( fullns ); + massert( 10419 , "how could chunk manager be null!" , cm ); + + vector<Chunk*> chunks; + cm->getChunksForQuery( chunks , filter ); + + unsigned long long total = 0; + for ( vector<Chunk*>::iterator i = chunks.begin() ; i != chunks.end() ; i++ ){ + Chunk * c = *i; + total += c->countObjects( filter ); + } + + result.append( "n" , (double)total ); + return true; + } + } countCmd; + + class ConvertToCappedCmd : public NotAllowedOnShardedCollectionCmd { + public: + ConvertToCappedCmd() : NotAllowedOnShardedCollectionCmd("convertToCapped"){} + + virtual string getFullNS( const string& dbName , const BSONObj& cmdObj ){ + return dbName + "." + cmdObj.firstElement().valuestrsafe(); + } + + } convertToCappedCmd; + + + class GroupCmd : public NotAllowedOnShardedCollectionCmd { + public: + GroupCmd() : NotAllowedOnShardedCollectionCmd("group"){} + + virtual string getFullNS( const string& dbName , const BSONObj& cmdObj ){ + return dbName + "." + cmdObj.firstElement().embeddedObjectUserCheck()["ns"].valuestrsafe(); + } + + } groupCmd; + + class DistinctCmd : public PublicGridCommand { + public: + DistinctCmd() : PublicGridCommand("distinct"){} + virtual void help( stringstream &help ) const { + help << "{ distinct : 'collection name' , key : 'a.b' }"; + } + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + string dbName = getDBName( ns ); + string collection = cmdObj.firstElement().valuestrsafe(); + string fullns = dbName + "." + collection; + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + if ( ! conf || ! conf->isShardingEnabled() || ! conf->isSharded( fullns ) ){ + return passthrough( conf , cmdObj , result ); + } + + ChunkManager * cm = conf->getChunkManager( fullns ); + massert( 10420 , "how could chunk manager be null!" , cm ); + + vector<Chunk*> chunks; + cm->getChunksForQuery( chunks , BSONObj() ); + + set<BSONObj,BSONObjCmp> all; + int size = 32; + + for ( vector<Chunk*>::iterator i = chunks.begin() ; i != chunks.end() ; i++ ){ + Chunk * c = *i; + + ScopedDbConnection conn( c->getShard() ); + BSONObj res; + bool ok = conn->runCommand( conf->getName() , cmdObj , res ); + conn.done(); + + if ( ! ok ){ + result.appendElements( res ); + return false; + } + + BSONObjIterator it( res["values"].embeddedObjectUserCheck() ); + while ( it.more() ){ + BSONElement nxt = it.next(); + BSONObjBuilder temp(32); + temp.appendAs( nxt , "x" ); + all.insert( temp.obj() ); + } + + } + + BSONObjBuilder b( size ); + int n=0; + for ( set<BSONObj,BSONObjCmp>::iterator i = all.begin() ; i != all.end(); i++ ){ + b.appendAs( i->firstElement() , b.numStr( n++ ).c_str() ); + } + + result.appendArray( "values" , b.obj() ); + return true; + } + } disinctCmd; + + class MRCmd : public PublicGridCommand { + public: + MRCmd() : PublicGridCommand( "mapreduce" ){} + + string getTmpName( const string& coll ){ + static int inc = 1; + stringstream ss; + ss << "tmp.mrs." << coll << "_" << time(0) << "_" << inc++; + return ss.str(); + } + + BSONObj fixForShards( const BSONObj& orig , const string& output ){ + BSONObjBuilder b; + BSONObjIterator i( orig ); + while ( i.more() ){ + BSONElement e = i.next(); + string fn = e.fieldName(); + if ( fn == "map" || + fn == "mapreduce" || + fn == "reduce" || + fn == "query" || + fn == "sort" || + fn == "verbose" ){ + b.append( e ); + } + else if ( fn == "keeptemp" || + fn == "out" || + fn == "finalize" ){ + // we don't want to copy these + } + else { + uassert( 10177 , (string)"don't know mr field: " + fn , 0 ); + } + } + b.append( "out" , output ); + return b.obj(); + } + + bool run(const char *ns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + Timer t; + + string dbName = getDBName( ns ); + string collection = cmdObj.firstElement().valuestrsafe(); + string fullns = dbName + "." + collection; + + DBConfig * conf = grid.getDBConfig( dbName , false ); + + if ( ! conf || ! conf->isShardingEnabled() || ! conf->isSharded( fullns ) ){ + return passthrough( conf , cmdObj , result ); + } + + BSONObjBuilder timingBuilder; + + ChunkManager * cm = conf->getChunkManager( fullns ); + + BSONObj q; + if ( cmdObj["query"].type() == Object ){ + q = cmdObj["query"].embeddedObjectUserCheck(); + } + + vector<Chunk*> chunks; + cm->getChunksForQuery( chunks , q ); + + const string shardedOutputCollection = getTmpName( collection ); + + BSONObj shardedCommand = fixForShards( cmdObj , shardedOutputCollection ); + + BSONObjBuilder finalCmd; + finalCmd.append( "mapreduce.shardedfinish" , cmdObj ); + finalCmd.append( "shardedOutputCollection" , shardedOutputCollection ); + + list< shared_ptr<Future::CommandResult> > futures; + + for ( vector<Chunk*>::iterator i = chunks.begin() ; i != chunks.end() ; i++ ){ + Chunk * c = *i; + futures.push_back( Future::spawnCommand( c->getShard() , dbName , shardedCommand ) ); + } + + BSONObjBuilder shardresults; + for ( list< shared_ptr<Future::CommandResult> >::iterator i=futures.begin(); i!=futures.end(); i++ ){ + shared_ptr<Future::CommandResult> res = *i; + if ( ! res->join() ){ + errmsg = "mongod mr failed: "; + errmsg += res->result().toString(); + return 0; + } + shardresults.append( res->getServer() , res->result() ); + } + + finalCmd.append( "shards" , shardresults.obj() ); + timingBuilder.append( "shards" , t.millis() ); + + Timer t2; + ScopedDbConnection conn( conf->getPrimary() ); + BSONObj finalResult; + if ( ! conn->runCommand( dbName , finalCmd.obj() , finalResult ) ){ + errmsg = "final reduce failed: "; + errmsg += finalResult.toString(); + return 0; + } + timingBuilder.append( "final" , t2.millis() ); + + result.appendElements( finalResult ); + result.append( "timeMillis" , t.millis() ); + result.append( "timing" , timingBuilder.obj() ); + + return 1; + } + } mrCmd; + } +} diff --git a/s/config.cpp b/s/config.cpp new file mode 100644 index 0000000..0bfb5a3 --- /dev/null +++ b/s/config.cpp @@ -0,0 +1,535 @@ +// config.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "../util/message.h" +#include "../util/unittest.h" +#include "../client/connpool.h" +#include "../client/model.h" +#include "../db/pdfile.h" +#include "../db/cmdline.h" + +#include "server.h" +#include "config.h" +#include "chunk.h" + +namespace mongo { + + int ConfigServer::VERSION = 2; + + /* --- DBConfig --- */ + + string DBConfig::modelServer() { + return configServer.modelServer(); + } + + bool DBConfig::isSharded( const string& ns ){ + if ( ! _shardingEnabled ) + return false; + return _sharded.find( ns ) != _sharded.end(); + } + + string DBConfig::getShard( const string& ns ){ + if ( isSharded( ns ) ) + return ""; + + uassert( 10178 , "no primary!" , _primary.size() ); + return _primary; + } + + void DBConfig::enableSharding(){ + _shardingEnabled = true; + } + + ChunkManager* DBConfig::shardCollection( const string& ns , ShardKeyPattern fieldsAndOrder , bool unique ){ + if ( ! _shardingEnabled ) + throw UserException( 8042 , "db doesn't have sharding enabled" ); + + ChunkManager * info = _shards[ns]; + if ( info ) + return info; + + if ( isSharded( ns ) ) + throw UserException( 8043 , "already sharded" ); + + log() << "enable sharding on: " << ns << " with shard key: " << fieldsAndOrder << endl; + _sharded[ns] = CollectionInfo( fieldsAndOrder , unique ); + + info = new ChunkManager( this , ns , fieldsAndOrder , unique ); + _shards[ns] = info; + return info; + + } + + bool DBConfig::removeSharding( const string& ns ){ + if ( ! _shardingEnabled ){ + cout << "AAAA" << endl; + return false; + } + + ChunkManager * info = _shards[ns]; + map<string,CollectionInfo>::iterator i = _sharded.find( ns ); + + if ( info == 0 && i == _sharded.end() ){ + cout << "BBBB" << endl; + return false; + } + uassert( 10179 , "_sharded but no info" , info ); + uassert( 10180 , "info but no sharded" , i != _sharded.end() ); + + _sharded.erase( i ); + _shards.erase( ns ); // TODO: clean this up, maybe switch to shared_ptr + return true; + } + + ChunkManager* DBConfig::getChunkManager( const string& ns , bool reload ){ + ChunkManager* m = _shards[ns]; + if ( m && ! reload ) + return m; + + uassert( 10181 , (string)"not sharded:" + ns , isSharded( ns ) ); + if ( m && reload ) + log() << "reloading shard info for: " << ns << endl; + m = new ChunkManager( this , ns , _sharded[ ns ].key , _sharded[ns].unique ); + _shards[ns] = m; + return m; + } + + void DBConfig::serialize(BSONObjBuilder& to){ + to.append("name", _name); + to.appendBool("partitioned", _shardingEnabled ); + to.append("primary", _primary ); + + if ( _sharded.size() > 0 ){ + BSONObjBuilder a; + for ( map<string,CollectionInfo>::reverse_iterator i=_sharded.rbegin(); i != _sharded.rend(); i++){ + BSONObjBuilder temp; + temp.append( "key" , i->second.key.key() ); + temp.appendBool( "unique" , i->second.unique ); + a.append( i->first.c_str() , temp.obj() ); + } + to.append( "sharded" , a.obj() ); + } + } + + void DBConfig::unserialize(const BSONObj& from){ + _name = from.getStringField("name"); + _shardingEnabled = from.getBoolField("partitioned"); + _primary = from.getStringField("primary"); + + _sharded.clear(); + BSONObj sharded = from.getObjectField( "sharded" ); + if ( ! sharded.isEmpty() ){ + BSONObjIterator i(sharded); + while ( i.more() ){ + BSONElement e = i.next(); + uassert( 10182 , "sharded things have to be objects" , e.type() == Object ); + BSONObj c = e.embeddedObject(); + uassert( 10183 , "key has to be an object" , c["key"].type() == Object ); + _sharded[e.fieldName()] = CollectionInfo( c["key"].embeddedObject() , + c["unique"].trueValue() ); + } + } + } + + void DBConfig::save( bool check ){ + Model::save( check ); + for ( map<string,ChunkManager*>::iterator i=_shards.begin(); i != _shards.end(); i++) + i->second->save(); + } + + bool DBConfig::reload(){ + // TODO: i don't think is 100% correct + return doload(); + } + + bool DBConfig::doload(){ + BSONObjBuilder b; + b.append("name", _name.c_str()); + BSONObj q = b.done(); + return load(q); + } + + bool DBConfig::dropDatabase( string& errmsg ){ + /** + * 1) make sure everything is up + * 2) update config server + * 3) drop and reset sharded collections + * 4) drop and reset primary + * 5) drop everywhere to clean up loose ends + */ + + log() << "DBConfig::dropDatabase: " << _name << endl; + + // 1 + if ( ! configServer.allUp( errmsg ) ){ + log(1) << "\t DBConfig::dropDatabase not all up" << endl; + return 0; + } + + // 2 + grid.removeDB( _name ); + remove( true ); + if ( ! configServer.allUp( errmsg ) ){ + log() << "error removing from config server even after checking!" << endl; + return 0; + } + log(1) << "\t removed entry from config server for: " << _name << endl; + + set<string> allServers; + + // 3 + while ( true ){ + int num; + if ( ! _dropShardedCollections( num , allServers , errmsg ) ) + return 0; + log() << " DBConfig::dropDatabase: " << _name << " dropped sharded collections: " << num << endl; + if ( num == 0 ) + break; + } + + // 4 + { + ScopedDbConnection conn( _primary ); + BSONObj res; + if ( ! conn->dropDatabase( _name , &res ) ){ + errmsg = res.toString(); + return 0; + } + conn.done(); + } + + // 5 + for ( set<string>::iterator i=allServers.begin(); i!=allServers.end(); i++ ){ + string s = *i; + ScopedDbConnection conn( s ); + BSONObj res; + if ( ! conn->dropDatabase( _name , &res ) ){ + errmsg = res.toString(); + return 0; + } + conn.done(); + } + + log(1) << "\t dropped primary db for: " << _name << endl; + + return true; + } + + bool DBConfig::_dropShardedCollections( int& num, set<string>& allServers , string& errmsg ){ + num = 0; + set<string> seen; + while ( true ){ + map<string,ChunkManager*>::iterator i = _shards.begin(); + + if ( i == _shards.end() ) + break; + + if ( seen.count( i->first ) ){ + errmsg = "seen a collection twice!"; + return false; + } + + seen.insert( i->first ); + log(1) << "\t dropping sharded collection: " << i->first << endl; + + i->second->getAllServers( allServers ); + i->second->drop(); + + num++; + uassert( 10184 , "_dropShardedCollections too many collections - bailing" , num < 100000 ); + log(2) << "\t\t dropped " << num << " so far" << endl; + } + return true; + } + + /* --- Grid --- */ + + string Grid::pickShardForNewDB(){ + ScopedDbConnection conn( configServer.getPrimary() ); + + // TODO: this is temporary + + vector<string> all; + auto_ptr<DBClientCursor> c = conn->query( "config.shards" , Query() ); + while ( c->more() ){ + BSONObj s = c->next(); + all.push_back( s["host"].valuestrsafe() ); + // look at s["maxSize"] if exists + } + conn.done(); + + if ( all.size() == 0 ) + return ""; + + return all[ rand() % all.size() ]; + } + + bool Grid::knowAboutShard( string name ) const{ + ScopedDbConnection conn( configServer.getPrimary() ); + BSONObj shard = conn->findOne( "config.shards" , BSON( "host" << name ) ); + conn.done(); + return ! shard.isEmpty(); + } + + DBConfig* Grid::getDBConfig( string database , bool create ){ + { + string::size_type i = database.find( "." ); + if ( i != string::npos ) + database = database.substr( 0 , i ); + } + + if ( database == "config" ) + return &configServer; + + boostlock l( _lock ); + + DBConfig*& cc = _databases[database]; + if ( cc == 0 ){ + cc = new DBConfig( database ); + if ( ! cc->doload() ){ + if ( create ){ + // note here that cc->primary == 0. + log() << "couldn't find database [" << database << "] in config db" << endl; + + if ( database == "admin" ) + cc->_primary = configServer.getPrimary(); + else + cc->_primary = pickShardForNewDB(); + + if ( cc->_primary.size() ){ + cc->save(); + log() << "\t put [" << database << "] on: " << cc->_primary << endl; + } + else { + log() << "\t can't find a shard to put new db on" << endl; + uassert( 10185 , "can't find a shard to put new db on" , 0 ); + } + } + else { + cc = 0; + } + } + + } + + return cc; + } + + void Grid::removeDB( string database ){ + uassert( 10186 , "removeDB expects db name" , database.find( '.' ) == string::npos ); + boostlock l( _lock ); + _databases.erase( database ); + + } + + unsigned long long Grid::getNextOpTime() const { + ScopedDbConnection conn( configServer.getPrimary() ); + + BSONObj result; + massert( 10421 , "getoptime failed" , conn->simpleCommand( "admin" , &result , "getoptime" ) ); + conn.done(); + + return result["optime"]._numberLong(); + } + + /* --- ConfigServer ---- */ + + ConfigServer::ConfigServer() { + _shardingEnabled = false; + _primary = ""; + _name = "grid"; + } + + ConfigServer::~ConfigServer() { + } + + bool ConfigServer::init( vector<string> configHosts ){ + uassert( 10187 , "need configdbs" , configHosts.size() ); + + string hn = getHostName(); + if ( hn.empty() ) { + sleepsecs(5); + dbexit( EXIT_BADOPTIONS ); + } + ourHostname = hn; + + set<string> hosts; + for ( size_t i=0; i<configHosts.size(); i++ ){ + string host = configHosts[i]; + hosts.insert( getHost( host , false ) ); + configHosts[i] = getHost( host , true ); + } + + for ( set<string>::iterator i=hosts.begin(); i!=hosts.end(); i++ ){ + string host = *i; + bool ok = false; + for ( int x=0; x<10; x++ ){ + if ( ! hostbyname( host.c_str() ).empty() ){ + ok = true; + break; + } + log() << "can't resolve DNS for [" << host << "] sleeping and trying " << (10-x) << " more times" << endl; + sleepsecs( 10 ); + } + if ( ! ok ) + return false; + } + + uassert( 10188 , "can only hand 1 config db right now" , configHosts.size() == 1 ); + _primary = configHosts[0]; + + return true; + } + + bool ConfigServer::allUp(){ + string errmsg; + return allUp( errmsg ); + } + + bool ConfigServer::allUp( string& errmsg ){ + try { + ScopedDbConnection conn( _primary ); + conn->getLastError(); + conn.done(); + return true; + } + catch ( DBException& ){ + log() << "ConfigServer::allUp : " << _primary << " seems down!" << endl; + errmsg = _primary + " seems down"; + return false; + } + + } + + int ConfigServer::dbConfigVersion(){ + ScopedDbConnection conn( _primary ); + int version = dbConfigVersion( conn.conn() ); + conn.done(); + return version; + } + + int ConfigServer::dbConfigVersion( DBClientBase& conn ){ + auto_ptr<DBClientCursor> c = conn.query( "config.version" , BSONObj() ); + int version = 0; + if ( c->more() ){ + BSONObj o = c->next(); + version = o["version"].numberInt(); + uassert( 10189 , "should only have 1 thing in config.version" , ! c->more() ); + } + else { + if ( conn.count( "config.shard" ) || conn.count( "config.databases" ) ){ + version = 1; + } + } + + return version; + } + + int ConfigServer::checkConfigVersion(){ + int cur = dbConfigVersion(); + if ( cur == VERSION ) + return 0; + + if ( cur == 0 ){ + ScopedDbConnection conn( _primary ); + conn->insert( "config.version" , BSON( "version" << VERSION ) ); + pool.flush(); + assert( VERSION == dbConfigVersion( conn.conn() ) ); + conn.done(); + return 0; + } + + log() << "don't know how to upgrade " << cur << " to " << VERSION << endl; + return -8; + } + + string ConfigServer::getHost( string name , bool withPort ){ + if ( name.find( ":" ) ){ + if ( withPort ) + return name; + return name.substr( 0 , name.find( ":" ) ); + } + + if ( withPort ){ + stringstream ss; + ss << name << ":" << CmdLine::ConfigServerPort; + return ss.str(); + } + + return name; + } + + ConfigServer configServer; + Grid grid; + + + class DBConfigUnitTest : public UnitTest { + public: + void testInOut( DBConfig& c , BSONObj o ){ + c.unserialize( o ); + BSONObjBuilder b; + c.serialize( b ); + + BSONObj out = b.obj(); + + if ( o.toString() == out.toString() ) + return; + + log() << "DBConfig serialization broken\n" + << "in : " << o.toString() << "\n" + << "out : " << out.toString() + << endl; + assert(0); + } + + void a(){ + BSONObjBuilder b; + b << "name" << "abc"; + b.appendBool( "partitioned" , true ); + b << "primary" << "myserver"; + + DBConfig c; + testInOut( c , b.obj() ); + } + + void b(){ + BSONObjBuilder b; + b << "name" << "abc"; + b.appendBool( "partitioned" , true ); + b << "primary" << "myserver"; + + BSONObjBuilder a; + a << "abc.foo" << fromjson( "{ 'key' : { 'a' : 1 } , 'unique' : false }" ); + a << "abc.bar" << fromjson( "{ 'key' : { 'kb' : -1 } , 'unique' : true }" ); + + b.appendArray( "sharded" , a.obj() ); + + DBConfig c; + testInOut( c , b.obj() ); + assert( c.isSharded( "abc.foo" ) ); + assert( ! c.isSharded( "abc.food" ) ); + } + + void run(){ + a(); + b(); + } + + } dbConfigUnitTest; +} diff --git a/s/config.h b/s/config.h new file mode 100644 index 0000000..16aa67a --- /dev/null +++ b/s/config.h @@ -0,0 +1,195 @@ +// config.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file is things related to the "grid configuration": + - what machines make up the db component of our cloud + - where various ranges of things live +*/ + +#pragma once + +#include "../db/namespace.h" +#include "../client/dbclient.h" +#include "../client/model.h" +#include "shardkey.h" + +namespace mongo { + + class Grid; + class ConfigServer; + + extern ConfigServer configServer; + extern Grid grid; + + class ChunkManager; + + class CollectionInfo { + public: + CollectionInfo( ShardKeyPattern _key = BSONObj() , bool _unique = false ) : + key( _key ) , unique( _unique ){} + + ShardKeyPattern key; + bool unique; + }; + + /** + * top level grid configuration for an entire database + * TODO: use shared_ptr for ChunkManager + */ + class DBConfig : public Model { + public: + DBConfig( string name = "" ) : _name( name ) , _primary("") , _shardingEnabled(false){ } + + string getName(){ return _name; }; + + /** + * @return if anything in this db is partitioned or not + */ + bool isShardingEnabled(){ + return _shardingEnabled; + } + + void enableSharding(); + ChunkManager* shardCollection( const string& ns , ShardKeyPattern fieldsAndOrder , bool unique ); + + /** + * @return whether or not this partition is partitioned + */ + bool isSharded( const string& ns ); + + ChunkManager* getChunkManager( const string& ns , bool reload = false ); + + /** + * @return the correct for shard for the ns + * if the namespace is sharded, will return an empty string + */ + string getShard( const string& ns ); + + string getPrimary(){ + if ( _primary.size() == 0 ) + throw UserException( 8041 , (string)"no primary shard configured for db: " + _name ); + return _primary; + } + + void setPrimary( string s ){ + _primary = s; + } + + bool reload(); + + bool dropDatabase( string& errmsg ); + + virtual void save( bool check=true); + + virtual string modelServer(); + + // model stuff + + virtual const char * getNS(){ return "config.databases"; } + virtual void serialize(BSONObjBuilder& to); + virtual void unserialize(const BSONObj& from); + + protected: + + bool _dropShardedCollections( int& num, set<string>& allServers , string& errmsg ); + + bool doload(); + + /** + @return true if there was sharding info to remove + */ + bool removeSharding( const string& ns ); + + string _name; // e.g. "alleyinsider" + string _primary; // e.g. localhost , mongo.foo.com:9999 + bool _shardingEnabled; + + map<string,CollectionInfo> _sharded; // { "alleyinsider.blog.posts" : { ts : 1 } , ... ] - all ns that are sharded + map<string,ChunkManager*> _shards; // this will only have entries for things that have been looked at + + friend class Grid; + friend class ChunkManager; + }; + + /** + * stores meta-information about the grid + * TODO: used shard_ptr for DBConfig pointers + */ + class Grid { + public: + /** + gets the config the db. + will return an empty DBConfig if not in db already + */ + DBConfig * getDBConfig( string ns , bool create=true); + + /** + * removes db entry. + * on next getDBConfig call will fetch from db + */ + void removeDB( string db ); + + string pickShardForNewDB(); + + bool knowAboutShard( string name ) const; + + unsigned long long getNextOpTime() const; + private: + map<string,DBConfig*> _databases; + boost::mutex _lock; // TODO: change to r/w lock + }; + + class ConfigServer : public DBConfig { + public: + + ConfigServer(); + ~ConfigServer(); + + bool ok(){ + // TODO: check can connect + return _primary.size() > 0; + } + + virtual string modelServer(){ + uassert( 10190 , "ConfigServer not setup" , _primary.size() ); + return _primary; + } + + /** + call at startup, this will initiate connection to the grid db + */ + bool init( vector<string> configHosts ); + + bool allUp(); + bool allUp( string& errmsg ); + + int dbConfigVersion(); + int dbConfigVersion( DBClientBase& conn ); + + /** + * @return 0 = ok, otherwise error # + */ + int checkConfigVersion(); + + static int VERSION; + + private: + string getHost( string name , bool withPort ); + }; + +} // namespace mongo diff --git a/s/cursors.cpp b/s/cursors.cpp new file mode 100644 index 0000000..23b8eaf --- /dev/null +++ b/s/cursors.cpp @@ -0,0 +1,104 @@ +// cursors.cpp + +#include "stdafx.h" +#include "cursors.h" +#include "../client/connpool.h" +#include "../db/queryutil.h" + +namespace mongo { + + // -------- ShardedCursor ----------- + + ShardedClientCursor::ShardedClientCursor( QueryMessage& q , ClusteredCursor * cursor ){ + assert( cursor ); + _cursor = cursor; + + _skip = q.ntoskip; + _ntoreturn = q.ntoreturn; + + _totalSent = 0; + _done = false; + + do { + // TODO: only create _id when needed + _id = security.getNonce(); + } while ( _id == 0 ); + + } + + ShardedClientCursor::~ShardedClientCursor(){ + assert( _cursor ); + delete _cursor; + _cursor = 0; + } + + bool ShardedClientCursor::sendNextBatch( Request& r , int ntoreturn ){ + uassert( 10191 , "cursor already done" , ! _done ); + + int maxSize = 1024 * 1024; + if ( _totalSent > 0 ) + maxSize *= 3; + + BufBuilder b(32768); + + int num = 0; + bool sendMore = true; + + while ( _cursor->more() ){ + BSONObj o = _cursor->next(); + + b.append( (void*)o.objdata() , o.objsize() ); + num++; + + if ( b.len() > maxSize ){ + break; + } + + if ( num == ntoreturn ){ + // soft limit aka batch size + break; + } + + if ( ntoreturn != 0 && ( -1 * num + _totalSent ) == ntoreturn ){ + // hard limit - total to send + sendMore = false; + break; + } + } + + bool hasMore = sendMore && _cursor->more(); + log(6) << "\t hasMore:" << hasMore << " wouldSendMoreIfHad: " << sendMore << " id:" << _id << " totalSent: " << _totalSent << endl; + + replyToQuery( 0 , r.p() , r.m() , b.buf() , b.len() , num , _totalSent , hasMore ? _id : 0 ); + _totalSent += num; + _done = ! hasMore; + + return hasMore; + } + + + CursorCache::CursorCache(){ + } + + CursorCache::~CursorCache(){ + // TODO: delete old cursors? + } + + ShardedClientCursor* CursorCache::get( long long id ){ + map<long long,ShardedClientCursor*>::iterator i = _cursors.find( id ); + if ( i == _cursors.end() ){ + OCCASIONALLY log() << "Sharded CursorCache missing cursor id: " << id << endl; + return 0; + } + return i->second; + } + + void CursorCache::store( ShardedClientCursor * cursor ){ + _cursors[cursor->getId()] = cursor; + } + void CursorCache::remove( long long id ){ + _cursors.erase( id ); + } + + CursorCache cursorCache; +} diff --git a/s/cursors.h b/s/cursors.h new file mode 100644 index 0000000..b1ed4b0 --- /dev/null +++ b/s/cursors.h @@ -0,0 +1,56 @@ +// cursors.h + +#pragma once + +#include "../stdafx.h" + +#include "../db/jsobj.h" +#include "../db/dbmessage.h" +#include "../client/dbclient.h" +#include "../client/parallel.h" + +#include "request.h" + +namespace mongo { + + class ShardedClientCursor { + public: + ShardedClientCursor( QueryMessage& q , ClusteredCursor * cursor ); + virtual ~ShardedClientCursor(); + + long long getId(){ return _id; } + + /** + * @return whether there is more data left + */ + bool sendNextBatch( Request& r ){ return sendNextBatch( r , _ntoreturn ); } + bool sendNextBatch( Request& r , int ntoreturn ); + + protected: + + ClusteredCursor * _cursor; + + int _skip; + int _ntoreturn; + + int _totalSent; + bool _done; + + long long _id; + }; + + class CursorCache { + public: + CursorCache(); + ~CursorCache(); + + ShardedClientCursor * get( long long id ); + void store( ShardedClientCursor* cursor ); + void remove( long long id ); + + private: + map<long long,ShardedClientCursor*> _cursors; + }; + + extern CursorCache cursorCache; +} diff --git a/s/d_logic.cpp b/s/d_logic.cpp new file mode 100644 index 0000000..cc627eb --- /dev/null +++ b/s/d_logic.cpp @@ -0,0 +1,542 @@ +// d_logic.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/** + these are commands that live in mongod + mostly around shard management and checking + */ + +#include "stdafx.h" +#include <map> +#include <string> + +#include "../db/commands.h" +#include "../db/jsobj.h" +#include "../db/dbmessage.h" + +#include "../client/connpool.h" + +#include "../util/queue.h" + +using namespace std; + +namespace mongo { + + typedef map<string,unsigned long long> NSVersions; + + NSVersions globalVersions; + boost::thread_specific_ptr<NSVersions> clientShardVersions; + + string shardConfigServer; + + boost::thread_specific_ptr<OID> clientServerIds; + map< string , BlockingQueue<BSONObj>* > clientQueues; + + unsigned long long getVersion( BSONElement e , string& errmsg ){ + if ( e.eoo() ){ + errmsg = "no version"; + return 0; + } + + if ( e.isNumber() ) + return (unsigned long long)e.number(); + + if ( e.type() == Date || e.type() == Timestamp ) + return e._numberLong(); + + + errmsg = "version is not a numberic type"; + return 0; + } + + class MongodShardCommand : public Command { + public: + MongodShardCommand( const char * n ) : Command( n ){ + } + virtual bool slaveOk(){ + return false; + } + virtual bool adminOnly() { + return true; + } + }; + + class WriteBackCommand : public MongodShardCommand { + public: + WriteBackCommand() : MongodShardCommand( "writebacklisten" ){} + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + BSONElement e = cmdObj.firstElement(); + if ( e.type() != jstOID ){ + errmsg = "need oid as first value"; + return 0; + } + + const OID id = e.__oid(); + + dbtemprelease unlock; + + if ( ! clientQueues[id.str()] ) + clientQueues[id.str()] = new BlockingQueue<BSONObj>(); + + BSONObj z = clientQueues[id.str()]->blockingPop(); + log(1) << "WriteBackCommand got : " << z << endl; + + result.append( "data" , z ); + + return true; + } + } writeBackCommand; + + // setShardVersion( ns ) + + class SetShardVersion : public MongodShardCommand { + public: + SetShardVersion() : MongodShardCommand("setShardVersion"){} + + virtual void help( stringstream& help ) const { + help << " example: { setShardVersion : 'alleyinsider.foo' , version : 1 , configdb : '' } "; + } + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + + bool authoritative = cmdObj.getBoolField( "authoritative" ); + + string configdb = cmdObj["configdb"].valuestrsafe(); + { // configdb checking + if ( configdb.size() == 0 ){ + errmsg = "no configdb"; + return false; + } + + if ( shardConfigServer.size() == 0 ){ + if ( ! authoritative ){ + result.appendBool( "need_authoritative" , true ); + errmsg = "first setShardVersion"; + return false; + } + shardConfigServer = configdb; + } + else if ( shardConfigServer != configdb ){ + errmsg = "specified a different configdb!"; + return false; + } + } + + { // setting up ids + if ( cmdObj["serverID"].type() != jstOID ){ + // TODO: fix this + //errmsg = "need serverID to be an OID"; + //return 0; + } + else { + OID clientId = cmdObj["serverID"].__oid(); + if ( ! clientServerIds.get() ){ + string s = clientId.str(); + + OID * nid = new OID(); + nid->init( s ); + clientServerIds.reset( nid ); + + if ( ! clientQueues[s] ) + clientQueues[s] = new BlockingQueue<BSONObj>(); + } + else if ( clientId != *clientServerIds.get() ){ + errmsg = "server id has changed!"; + return 0; + } + } + } + + unsigned long long version = getVersion( cmdObj["version"] , errmsg ); + if ( errmsg.size() ){ + return false; + } + + NSVersions * versions = clientShardVersions.get(); + + if ( ! versions ){ + log(1) << "entering shard mode for connection" << endl; + versions = new NSVersions(); + clientShardVersions.reset( versions ); + } + + string ns = cmdObj["setShardVersion"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "need to speciy fully namespace"; + return false; + } + + unsigned long long& oldVersion = (*versions)[ns]; + unsigned long long& globalVersion = globalVersions[ns]; + + if ( version == 0 && globalVersion == 0 ){ + // this connection is cleaning itself + oldVersion = 0; + return 1; + } + + if ( version == 0 && globalVersion > 0 ){ + if ( ! authoritative ){ + result.appendBool( "need_authoritative" , true ); + result.appendTimestamp( "globalVersion" , globalVersion ); + result.appendTimestamp( "oldVersion" , oldVersion ); + errmsg = "dropping needs to be authoritative"; + return 0; + } + log() << "wiping data for: " << ns << endl; + result.appendTimestamp( "beforeDrop" , globalVersion ); + // only setting global version on purpose + // need clients to re-find meta-data + globalVersion = 0; + oldVersion = 0; + return 1; + } + + if ( version < oldVersion ){ + errmsg = "you already have a newer version"; + result.appendTimestamp( "oldVersion" , oldVersion ); + result.appendTimestamp( "newVersion" , version ); + return false; + } + + if ( version < globalVersion ){ + errmsg = "going to older version for global"; + return false; + } + + if ( globalVersion == 0 && ! cmdObj.getBoolField( "authoritative" ) ){ + // need authoritative for first look + result.appendBool( "need_authoritative" , true ); + result.append( "ns" , ns ); + errmsg = "first time for this ns"; + return false; + } + + result.appendTimestamp( "oldVersion" , oldVersion ); + oldVersion = version; + globalVersion = version; + + result.append( "ok" , 1 ); + return 1; + } + + } setShardVersion; + + class GetShardVersion : public MongodShardCommand { + public: + GetShardVersion() : MongodShardCommand("getShardVersion"){} + + virtual void help( stringstream& help ) const { + help << " example: { getShardVersion : 'alleyinsider.foo' } "; + } + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + string ns = cmdObj["getShardVersion"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "need to speciy fully namespace"; + return false; + } + + result.append( "configServer" , shardConfigServer.c_str() ); + + result.appendTimestamp( "global" , globalVersions[ns] ); + if ( clientShardVersions.get() ) + result.appendTimestamp( "mine" , (*clientShardVersions.get())[ns] ); + else + result.appendTimestamp( "mine" , 0 ); + + return true; + } + + } getShardVersion; + + class MoveShardStartCommand : public MongodShardCommand { + public: + MoveShardStartCommand() : MongodShardCommand( "movechunk.start" ){} + virtual void help( stringstream& help ) const { + help << "should not be calling this directly" << endl; + } + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + // so i have to start clone, tell caller its ok to make change + // at this point the caller locks me, and updates config db + // then finish calls finish, and then deletes data when cursors are done + + string ns = cmdObj["movechunk.start"].valuestrsafe(); + string to = cmdObj["to"].valuestrsafe(); + string from = cmdObj["from"].valuestrsafe(); // my public address, a tad redundant, but safe + BSONObj filter = cmdObj.getObjectField( "filter" ); + + if ( ns.size() == 0 ){ + errmsg = "need to specify namespace in command"; + return false; + } + + if ( to.size() == 0 ){ + errmsg = "need to specify server to move shard to"; + return false; + } + if ( from.size() == 0 ){ + errmsg = "need to specify server to move shard from (redundat i know)"; + return false; + } + + if ( filter.isEmpty() ){ + errmsg = "need to specify a filter"; + return false; + } + + log() << "got movechunk.start: " << cmdObj << endl; + + + BSONObj res; + bool ok; + + { + dbtemprelease unlock; + + ScopedDbConnection conn( to ); + ok = conn->runCommand( "admin" , + BSON( "startCloneCollection" << ns << + "from" << from << + "query" << filter + ) , + res ); + conn.done(); + } + + log() << " movechunk.start res: " << res << endl; + + if ( ok ){ + result.append( res["finishToken"] ); + } + else { + errmsg = "startCloneCollection failed: "; + errmsg += res["errmsg"].valuestrsafe(); + } + return ok; + } + + } moveShardStartCmd; + + class MoveShardFinishCommand : public MongodShardCommand { + public: + MoveShardFinishCommand() : MongodShardCommand( "movechunk.finish" ){} + virtual void help( stringstream& help ) const { + help << "should not be calling this directly" << endl; + } + + bool run(const char *cmdns, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool){ + // see MoveShardStartCommand::run + + string ns = cmdObj["movechunk.finish"].valuestrsafe(); + if ( ns.size() == 0 ){ + errmsg = "need ns as cmd value"; + return false; + } + + string to = cmdObj["to"].valuestrsafe(); + if ( to.size() == 0 ){ + errmsg = "need to specify server to move shard to"; + return false; + } + + + unsigned long long newVersion = getVersion( cmdObj["newVersion"] , errmsg ); + if ( newVersion == 0 ){ + errmsg = "have to specify new version number"; + return false; + } + + BSONObj finishToken = cmdObj.getObjectField( "finishToken" ); + if ( finishToken.isEmpty() ){ + errmsg = "need finishToken"; + return false; + } + + if ( ns != finishToken["collection"].valuestrsafe() ){ + errmsg = "namespaced don't match"; + return false; + } + + // now we're locked + globalVersions[ns] = newVersion; + NSVersions * versions = clientShardVersions.get(); + if ( ! versions ){ + versions = new NSVersions(); + clientShardVersions.reset( versions ); + } + (*versions)[ns] = newVersion; + + BSONObj res; + bool ok; + + { + dbtemprelease unlock; + + ScopedDbConnection conn( to ); + ok = conn->runCommand( "admin" , + BSON( "finishCloneCollection" << finishToken ) , + res ); + conn.done(); + } + + if ( ! ok ){ + // uh oh + errmsg = "finishCloneCollection failed!"; + result << "finishError" << res; + return false; + } + + // wait until cursors are clean + cout << "WARNING: deleting data before ensuring no more cursors TODO" << endl; + + dbtemprelease unlock; + + DBDirectClient client; + BSONObj removeFilter = finishToken.getObjectField( "query" ); + client.remove( ns , removeFilter ); + + return true; + } + + } moveShardFinishCmd; + + bool haveLocalShardingInfo( const string& ns ){ + if ( shardConfigServer.empty() ) + return false; + + + unsigned long long version = globalVersions[ns]; + if ( version == 0 ) + return false; + + NSVersions * versions = clientShardVersions.get(); + if ( ! versions ) + return false; + + return true; + } + + /** + * @ return true if not in sharded mode + or if version for this client is ok + */ + bool shardVersionOk( const string& ns , string& errmsg ){ + if ( shardConfigServer.empty() ){ + return true; + } + + NSVersions::iterator i = globalVersions.find( ns ); + if ( i == globalVersions.end() ) + return true; + + NSVersions * versions = clientShardVersions.get(); + if ( ! versions ){ + // this means the client has nothing sharded + // so this allows direct connections to do whatever they want + // which i think is the correct behavior + return true; + } + + unsigned long long clientVersion = (*versions)[ns]; + unsigned long long version = i->second; + + if ( version == 0 && clientVersion > 0 ){ + stringstream ss; + ss << "version: " << version << " clientVersion: " << clientVersion; + errmsg = ss.str(); + return false; + } + + if ( clientVersion >= version ) + return true; + + + if ( clientVersion == 0 ){ + errmsg = "client in sharded mode, but doesn't have version set for this collection"; + return false; + } + + errmsg = (string)"your version is too old ns: " + ns; + return false; + } + + + bool handlePossibleShardedMessage( Message &m, DbResponse &dbresponse ){ + + if ( shardConfigServer.empty() ){ + return false; + } + + int op = m.data->operation(); + if ( op < 2000 || op >= 3000 ) + return false; + + + const char *ns = m.data->_data + 4; + string errmsg; + if ( shardVersionOk( ns , errmsg ) ){ + return false; + } + + log() << "shardVersionOk failed ns:" << ns << " " << errmsg << endl; + + if ( doesOpGetAResponse( op ) ){ + BufBuilder b( 32768 ); + b.skip( sizeof( QueryResult ) ); + { + BSONObj obj = BSON( "$err" << errmsg ); + b.append( obj.objdata() , obj.objsize() ); + } + + QueryResult *qr = (QueryResult*)b.buf(); + qr->_resultFlags() = QueryResult::ResultFlag_ErrSet | QueryResult::ResultFlag_ShardConfigStale; + qr->len = b.len(); + qr->setOperation( opReply ); + qr->cursorId = 0; + qr->startingFrom = 0; + qr->nReturned = 1; + b.decouple(); + + Message * resp = new Message(); + resp->setData( qr , true ); + + dbresponse.response = resp; + dbresponse.responseTo = m.data->id; + return true; + } + + OID * clientID = clientServerIds.get(); + massert( 10422 , "write with bad shard config and no server id!" , clientID ); + + log() << "got write with an old config - writing back" << endl; + + BSONObjBuilder b; + b.appendBool( "writeBack" , true ); + b.append( "ns" , ns ); + b.appendBinData( "msg" , m.data->len , bdtCustom , (char*)(m.data) ); + log() << "writing back msg with len: " << m.data->len << " op: " << m.data->_operation << endl; + clientQueues[clientID->str()]->push( b.obj() ); + + return true; + } + +} diff --git a/s/d_logic.h b/s/d_logic.h new file mode 100644 index 0000000..3e483c4 --- /dev/null +++ b/s/d_logic.h @@ -0,0 +1,23 @@ +// d_logic.h + +#pragma once + +#include "../stdafx.h" + +namespace mongo { + + /** + * @return true if we have any shard info for the ns + */ + bool haveLocalShardingInfo( const string& ns ); + + /** + * @return true if the current threads shard version is ok, or not in sharded version + */ + bool shardVersionOk( const string& ns , string& errmsg ); + + /** + * @return true if we took care of the message and nothing else should be done + */ + bool handlePossibleShardedMessage( Message &m, DbResponse &dbresponse ); +} diff --git a/s/d_util.cpp b/s/d_util.cpp new file mode 100644 index 0000000..8c30d2e --- /dev/null +++ b/s/d_util.cpp @@ -0,0 +1,36 @@ +// util.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/** + these are commands that live in mongod + mostly around shard management and checking + */ + +#include "stdafx.h" +#include "util.h" + +using namespace std; + +namespace mongo { + + void checkShardVersion( DBClientBase & conn , const string& ns , bool authoritative ){ + // no-op in mongod + } + +} diff --git a/s/dbgrid.vcproj b/s/dbgrid.vcproj new file mode 100644 index 0000000..2c8ef85 --- /dev/null +++ b/s/dbgrid.vcproj @@ -0,0 +1,660 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="mongos"
+ ProjectGUID="{E03717ED-69B4-4D21-BC55-DF6690B585C6}"
+ RootNamespace="dbgrid"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"C:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="USE_ASIO;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0\""
+ PreprocessorDefinitions="USE_ASIO;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;PCRE_STATIC"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="release_nojni|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"c:\Program Files\boost\boost_1_35_0\""
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;PCRE_STATIC"
+ RuntimeLibrary="2"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4355"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""c:\Program Files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug Recstore|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""..\pcre-7.4";"C:\Program Files\boost\boost_1_35_0""
+ PreprocessorDefinitions="USE_ASIO;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="stdafx.h"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ DisableSpecificWarnings="4355;4800"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""c:\program files\boost\boost_1_35_0\lib""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\chunk.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\commands_admin.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\commands_public.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\config.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\cursors.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\queryutil.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\request.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\server.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\shardkey.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\strategy.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\strategy_shard.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\strategy_single.cpp"
+ >
+ </File>
+ <Filter
+ Name="Shared Source Files"
+ >
+ <File
+ RelativePath="..\util\assert_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\base64.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\commands.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\debug_util.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\jsobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\json.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\lasterror.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\md5.c"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\util\md5main.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="2"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\util\message.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\message_server_asio.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="release_nojni|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug Recstore|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\db\nonce.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\parallel.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\sock.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\thread_pool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\util\util.cpp"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\gridconfig.h"
+ >
+ </File>
+ <File
+ RelativePath=".\griddatabase.h"
+ >
+ </File>
+ <File
+ RelativePath=".\shard.h"
+ >
+ </File>
+ <File
+ RelativePath=".\strategy.h"
+ >
+ </File>
+ <Filter
+ Name="Header Shared"
+ >
+ <File
+ RelativePath="..\util\background.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\commands.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\connpool.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\db\dbmessage.h"
+ >
+ </File>
+ <File
+ RelativePath="..\util\goodies.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\jsobj.h"
+ >
+ </File>
+ <File
+ RelativePath="..\db\json.h"
+ >
+ </File>
+ <File
+ RelativePath="..\stdafx.h"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ <Filter
+ Name="libs_etc"
+ >
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\config\auto_link.hpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\boostw\boost_1_34_1\boost\version.hpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="client"
+ >
+ <File
+ RelativePath="..\client\connpool.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\dbclient.h"
+ >
+ </File>
+ <File
+ RelativePath="..\client\model.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\client\model.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/s/dbgrid.vcxproj b/s/dbgrid.vcxproj new file mode 100644 index 0000000..e997055 --- /dev/null +++ b/s/dbgrid.vcxproj @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug Recstore|Win32"> + <Configuration>Debug Recstore</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectName>mongos</ProjectName> + <ProjectGuid>{E03717ED-69B4-4D21-BC55-DF6690B585C6}</ProjectGuid> + <RootNamespace>dbgrid</RootNamespace> + <Keyword>Win32Proj</Keyword> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + <WholeProgramOptimization>true</WholeProgramOptimization> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.21006.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(SolutionDir)$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">$(Configuration)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">true</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\pcre-7.4;C:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>USE_ASIO;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader>Use</PrecompiledHeader> + <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>c:\program files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>USE_ASIO;WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>c:\Program Files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\pcre-7.4;C:\Program Files\boost\boost_1_41_0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>USE_ASIO;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader>Use</PrecompiledHeader> + <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4355;4800;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalLibraryDirectories>c:\program files\boost\boost_1_41_0\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="chunk.cpp" /> + <ClCompile Include="commands_admin.cpp" /> + <ClCompile Include="commands_public.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="cursors.cpp" /> + <ClCompile Include="..\db\queryutil.cpp" /> + <ClCompile Include="request.cpp" /> + <ClCompile Include="server.cpp" /> + <ClCompile Include="shardkey.cpp" /> + <ClCompile Include="..\stdafx.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> + </ClCompile> + <ClCompile Include="strategy.cpp" /> + <ClCompile Include="strategy_shard.cpp" /> + <ClCompile Include="strategy_single.cpp" /> + <ClCompile Include="..\util\assert_util.cpp" /> + <ClCompile Include="..\util\background.cpp" /> + <ClCompile Include="..\util\base64.cpp" /> + <ClCompile Include="..\db\commands.cpp" /> + <ClCompile Include="..\util\debug_util.cpp" /> + <ClCompile Include="..\db\jsobj.cpp" /> + <ClCompile Include="..\db\json.cpp" /> + <ClCompile Include="..\db\lasterror.cpp" /> + <ClCompile Include="..\util\md5.c"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'"> + </PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + </PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + </PrecompiledHeader> + </ClCompile> + <ClCompile Include="..\util\md5main.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader> + </ClCompile> + <ClCompile Include="..\util\message.cpp" /> + <ClCompile Include="..\util\message_server_asio.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug Recstore|Win32'"> + </PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + </PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + </PrecompiledHeader> + </ClCompile> + <ClCompile Include="..\db\nonce.cpp" /> + <ClCompile Include="..\client\parallel.cpp" /> + <ClCompile Include="..\util\sock.cpp" /> + <ClCompile Include="..\util\util.cpp" /> + <ClCompile Include="..\client\connpool.cpp" /> + <ClCompile Include="..\client\dbclient.cpp" /> + <ClCompile Include="..\client\model.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="gridconfig.h" /> + <ClInclude Include="griddatabase.h" /> + <ClInclude Include="shard.h" /> + <ClInclude Include="strategy.h" /> + <ClInclude Include="..\util\background.h" /> + <ClInclude Include="..\db\commands.h" /> + <ClInclude Include="..\db\dbmessage.h" /> + <ClInclude Include="..\util\goodies.h" /> + <ClInclude Include="..\db\jsobj.h" /> + <ClInclude Include="..\db\json.h" /> + <ClInclude Include="..\stdafx.h" /> + <ClInclude Include="..\client\connpool.h" /> + <ClInclude Include="..\client\dbclient.h" /> + <ClInclude Include="..\client\model.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> diff --git a/s/request.cpp b/s/request.cpp new file mode 100644 index 0000000..8bebd64 --- /dev/null +++ b/s/request.cpp @@ -0,0 +1,175 @@ +/* dbgrid/request.cpp + + Top level handling of requests (operations such as query, insert, ...) +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "server.h" +#include "../db/commands.h" +#include "../db/dbmessage.h" +#include "../client/connpool.h" + +#include "request.h" +#include "config.h" +#include "chunk.h" + +namespace mongo { + + Request::Request( Message& m, AbstractMessagingPort* p ) : + _m(m) , _d( m ) , _p(p){ + + assert( _d.getns() ); + _id = _m.data->id; + + _clientId = p ? p->remotePort() << 16 : 0; + _clientInfo = ClientInfo::get( _clientId ); + _clientInfo->newRequest(); + + reset(); + } + + void Request::reset( bool reload ){ + _config = grid.getDBConfig( getns() ); + if ( reload ) + uassert( 10192 , "db config reload failed!" , _config->reload() ); + + if ( _config->isSharded( getns() ) ){ + _chunkManager = _config->getChunkManager( getns() , reload ); + uassert( 10193 , (string)"no shard info for: " + getns() , _chunkManager ); + } + else { + _chunkManager = 0; + } + + _m.data->id = _id; + + } + + string Request::singleServerName(){ + if ( _chunkManager ){ + if ( _chunkManager->numChunks() > 1 ) + throw UserException( 8060 , "can't call singleServerName on a sharded collection" ); + return _chunkManager->findChunk( _chunkManager->getShardKey().globalMin() ).getShard(); + } + string s = _config->getShard( getns() ); + uassert( 10194 , "can't call singleServerName on a sharded collection!" , s.size() > 0 ); + return s; + } + + void Request::process( int attempt ){ + + log(2) << "Request::process ns: " << getns() << " msg id:" << (int)(_m.data->id) << " attempt: " << attempt << endl; + + int op = _m.data->operation(); + assert( op > dbMsg ); + + Strategy * s = SINGLE; + + _d.markSet(); + + if ( _chunkManager ){ + s = SHARDED; + } + + if ( op == dbQuery ) { + try { + s->queryOp( *this ); + } + catch ( StaleConfigException& staleConfig ){ + log() << staleConfig.what() << " attempt: " << attempt << endl; + uassert( 10195 , "too many attempts to update config, failing" , attempt < 5 ); + + sleepsecs( attempt ); + reset( true ); + _d.markReset(); + process( attempt + 1 ); + return; + } + } + else if ( op == dbGetMore ) { + s->getMore( *this ); + } + else { + s->writeOp( op, *this ); + } + } + + + ClientInfo::ClientInfo( int clientId ) : _id( clientId ){ + _cur = &_a; + _prev = &_b; + newRequest(); + } + + ClientInfo::~ClientInfo(){ + boostlock lk( _clientsLock ); + ClientCache::iterator i = _clients.find( _id ); + if ( i != _clients.end() ){ + _clients.erase( i ); + } + } + + void ClientInfo::addShard( const string& shard ){ + _cur->insert( shard ); + } + + void ClientInfo::newRequest(){ + _lastAccess = (int) time(0); + + set<string> * temp = _cur; + _cur = _prev; + _prev = temp; + _cur->clear(); + } + + void ClientInfo::disconnect(){ + _lastAccess = 0; + } + + ClientInfo * ClientInfo::get( int clientId , bool create ){ + + if ( ! clientId ) + clientId = getClientId(); + + if ( ! clientId ){ + ClientInfo * info = _tlInfo.get(); + if ( ! info ){ + info = new ClientInfo( 0 ); + _tlInfo.reset( info ); + } + info->newRequest(); + return info; + } + + boostlock lk( _clientsLock ); + ClientCache::iterator i = _clients.find( clientId ); + if ( i != _clients.end() ) + return i->second; + if ( ! create ) + return 0; + ClientInfo * info = new ClientInfo( clientId ); + _clients[clientId] = info; + return info; + } + + map<int,ClientInfo*> ClientInfo::_clients; + boost::mutex ClientInfo::_clientsLock; + boost::thread_specific_ptr<ClientInfo> ClientInfo::_tlInfo; + +} // namespace mongo diff --git a/s/request.h b/s/request.h new file mode 100644 index 0000000..689216c --- /dev/null +++ b/s/request.h @@ -0,0 +1,120 @@ +// request.h + +#pragma once + +#include "../stdafx.h" +#include "../util/message.h" +#include "../db/dbmessage.h" +#include "config.h" +#include "util.h" + +namespace mongo { + + class ClientInfo; + + class Request : boost::noncopyable { + public: + Request( Message& m, AbstractMessagingPort* p ); + + // ---- message info ----- + + + const char * getns(){ + return _d.getns(); + } + int op(){ + return _m.data->operation(); + } + bool expectResponse(){ + return op() == dbQuery || op() == dbGetMore; + } + + MSGID id(){ + return _id; + } + + DBConfig * getConfig(){ + return _config; + } + bool isShardingEnabled(){ + return _config->isShardingEnabled(); + } + + ChunkManager * getChunkManager(){ + return _chunkManager; + } + + int getClientId(){ + return _clientId; + } + ClientInfo * getClientInfo(){ + return _clientInfo; + } + + // ---- remote location info ----- + + + string singleServerName(); + + const char * primaryName(){ + return _config->getPrimary().c_str(); + } + + // ---- low level access ---- + + void reply( Message & response ){ + _p->reply( _m , response , _id ); + } + + Message& m(){ return _m; } + DbMessage& d(){ return _d; } + AbstractMessagingPort* p(){ return _p; } + + void process( int attempt = 0 ); + + private: + + void reset( bool reload=false ); + + Message& _m; + DbMessage _d; + AbstractMessagingPort* _p; + + MSGID _id; + DBConfig * _config; + ChunkManager * _chunkManager; + + int _clientId; + ClientInfo * _clientInfo; + }; + + typedef map<int,ClientInfo*> ClientCache; + + class ClientInfo { + public: + ClientInfo( int clientId ); + ~ClientInfo(); + + void addShard( const string& shard ); + set<string> * getPrev() const { return _prev; }; + + void newRequest(); + void disconnect(); + + static ClientInfo * get( int clientId = 0 , bool create = true ); + + private: + int _id; + set<string> _a; + set<string> _b; + set<string> * _cur; + set<string> * _prev; + int _lastAccess; + + static boost::mutex _clientsLock; + static ClientCache _clients; + static boost::thread_specific_ptr<ClientInfo> _tlInfo; + }; +} + +#include "strategy.h" diff --git a/s/s_only.cpp b/s/s_only.cpp new file mode 100644 index 0000000..d692ff2 --- /dev/null +++ b/s/s_only.cpp @@ -0,0 +1,29 @@ +// s_only.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../stdafx.h" +#include "../client/dbclient.h" +#include "../db/dbhelpers.h" + +namespace mongo { + + auto_ptr<CursorIterator> Helpers::find( const char *ns , BSONObj query , bool requireIndex ){ + uassert( 10196 , "Helpers::find can't be used in mongos" , 0 ); + auto_ptr<CursorIterator> i; + return i; + } +} diff --git a/s/server.cpp b/s/server.cpp new file mode 100644 index 0000000..4868caf --- /dev/null +++ b/s/server.cpp @@ -0,0 +1,202 @@ +// server.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "../util/message.h" +#include "../util/unittest.h" +#include "../client/connpool.h" +#include "../util/message_server.h" + +#include "server.h" +#include "request.h" +#include "config.h" +#include "chunk.h" + +namespace mongo { + + Database *database = 0; + string ourHostname; + OID serverID; + bool dbexitCalled = false; + CmdLine cmdLine; + + bool inShutdown(){ + return dbexitCalled; + } + + string getDbContext() { + return "?"; + } + + bool haveLocalShardingInfo( const string& ns ){ + assert( 0 ); + return false; + } + + void usage( char * argv[] ){ + out() << argv[0] << " usage:\n\n"; + out() << " -v+ verbose\n"; + out() << " --port <portno>\n"; + out() << " --configdb <configdbname> [<configdbname>...]\n"; + out() << endl; + } + + class ShardingConnectionHook : public DBConnectionHook { + public: + virtual void onCreate( DBClientBase * conn ){ + conn->simpleCommand( "admin" , 0 , "switchtoclienterrors" ); + } + virtual void onHandedOut( DBClientBase * conn ){ + ClientInfo::get()->addShard( conn->getServerAddress() ); + } + } shardingConnectionHook; + + class ShardedMessageHandler : public MessageHandler { + public: + virtual ~ShardedMessageHandler(){} + virtual void process( Message& m , AbstractMessagingPort* p ){ + Request r( m , p ); + if ( logLevel > 5 ){ + log(5) << "client id: " << hex << r.getClientId() << "\t" << r.getns() << "\t" << dec << r.op() << endl; + } + try { + setClientId( r.getClientId() ); + r.process(); + } + catch ( DBException& e ){ + m.data->id = r.id(); + log() << "UserException: " << e.what() << endl; + if ( r.expectResponse() ){ + BSONObj err = BSON( "$err" << e.what() ); + replyToQuery( QueryResult::ResultFlag_ErrSet, p , m , err ); + } + } + } + }; + + void init(){ + serverID.init(); + setupSIGTRAPforGDB(); + } + + void start() { + log() << "waiting for connections on port " << cmdLine.port << endl; + //DbGridListener l(port); + //l.listen(); + ShardedMessageHandler handler; + MessageServer * server = createServer( cmdLine.port , &handler ); + server->run(); + } + + DBClientBase *createDirectClient(){ + uassert( 10197 , "createDirectClient not implemented for sharding yet" , 0 ); + return 0; + } + +} // namespace mongo + +using namespace mongo; + +int main(int argc, char* argv[], char *envp[] ) { + + bool justTests = false; + vector<string> configdbs; + + for (int i = 1; i < argc; i++) { + if ( argv[i] == 0 ) continue; + string s = argv[i]; + if ( s == "--port" ) { + cmdLine.port = atoi(argv[++i]); + } + else if ( s == "--configdb" ) { + + while ( ++i < argc ) + configdbs.push_back(argv[i]); + + if ( configdbs.size() == 0 ) { + out() << "error: no args for --configdb\n"; + return 4; + } + + if ( configdbs.size() > 2 ) { + out() << "error: --configdb does not support more than 2 parameters yet\n"; + return 5; + } + } + else if ( s.find( "-v" ) == 0 ){ + logLevel = s.size() - 1; + } + else if ( s == "--test" ) { + justTests = true; + logLevel = 5; + } + else { + usage( argv ); + return 3; + } + } + + if ( justTests ){ + UnitTest::runTests(); + cout << "tests passed" << endl; + return 0; + } + + pool.addHook( &shardingConnectionHook ); + + if ( argc <= 1 ) { + usage( argv ); + return 3; + } + + bool ok = cmdLine.port != 0 && configdbs.size(); + + if ( !ok ) { + usage( argv ); + return 1; + } + + log() << argv[0] << " v0.3- (alpha 3t) starting (--help for usage)" << endl; + printGitVersion(); + printSysInfo(); + + if ( ! configServer.init( configdbs ) ){ + cout << "couldn't connectd to config db" << endl; + return 7; + } + + assert( configServer.ok() ); + + int configError = configServer.checkConfigVersion(); + if ( configError ){ + cout << "config server error: " << configError << endl; + return configError; + } + + init(); + start(); + dbexit( EXIT_CLEAN ); + return 0; +} + +#undef exit +void mongo::dbexit( ExitCode rc, const char *why) { + dbexitCalled = true; + log() << "dbexit: " << why << " rc:" << rc << endl; + ::exit(rc); +} diff --git a/s/server.h b/s/server.h new file mode 100644 index 0000000..067afeb --- /dev/null +++ b/s/server.h @@ -0,0 +1,30 @@ +// server.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> +#include "../util/message.h" +#include "../db/jsobj.h" + +namespace mongo { + + extern std::string ourHostname; + extern OID serverID; + + // from request.cpp + void processRequest(Message& m, MessagingPort& p); +} diff --git a/s/shardkey.cpp b/s/shardkey.cpp new file mode 100644 index 0000000..15cf7b9 --- /dev/null +++ b/s/shardkey.cpp @@ -0,0 +1,320 @@ +// shardkey.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "chunk.h" +#include "../db/jsobj.h" +#include "../util/unittest.h" + +/** + TODO: this only works with numbers right now + this is very temporary, need to make work with anything +*/ + +namespace mongo { + void minForPat(BSONObjBuilder& out, const BSONObj& pat){ + BSONElement e = pat.firstElement(); + if (e.type() == Object){ + BSONObjBuilder sub; + minForPat(sub, e.embeddedObject()); + out.append(e.fieldName(), sub.obj()); + } else { + out.appendMinKey(e.fieldName()); + } + } + + void maxForPat(BSONObjBuilder& out, const BSONObj& pat){ + BSONElement e = pat.firstElement(); + if (e.type() == Object){ + BSONObjBuilder sub; + maxForPat(sub, e.embeddedObject()); + out.append(e.fieldName(), sub.obj()); + } else { + out.appendMaxKey(e.fieldName()); + } + } + + ShardKeyPattern::ShardKeyPattern( BSONObj p ) : pattern( p.getOwned() ) { + pattern.getFieldNames(patternfields); + + BSONObjBuilder min; + minForPat(min, pattern); + gMin = min.obj(); + + BSONObjBuilder max; + maxForPat(max, pattern); + gMax = max.obj(); + } + + int ShardKeyPattern::compare( const BSONObj& lObject , const BSONObj& rObject ) { + BSONObj L = extractKey(lObject); + uassert( 10198 , "left object doesn't have shard key", !L.isEmpty()); + BSONObj R = extractKey(rObject); + uassert( 10199 , "right object doesn't have shard key", !R.isEmpty()); + return L.woCompare(R); + } + + bool ShardKeyPattern::hasShardKey( const BSONObj& obj ) { + /* this is written s.t. if obj has lots of fields, if the shard key fields are early, + it is fast. so a bit more work to try to be semi-fast. + */ + + for(set<string>::iterator it = patternfields.begin(); it != patternfields.end(); ++it){ + if(obj.getFieldDotted(it->c_str()).eoo()) + return false; + } + return true; + } + + /** @return true if shard s is relevant for query q. + + Example: + q: { x : 3 } + *this: { x : 1 } + s: x:2..x:7 + -> true + */ + + bool ShardKeyPattern::relevant(const BSONObj& query, const BSONObj& L, const BSONObj& R) { + BSONObj q = extractKey( query ); + if( q.isEmpty() ) + return true; + + BSONElement e = q.firstElement(); + assert( !e.eoo() ) ; + + if( e.type() == RegEx ) { + /* todo: if starts with ^, we could be smarter here */ + return true; + } + + if( e.type() == Object ) { + BSONObjIterator j(e.embeddedObject()); + BSONElement LE = L.firstElement(); // todo compound keys + BSONElement RE = R.firstElement(); // todo compound keys + while( 1 ) { + BSONElement f = j.next(); + if( f.eoo() ) + break; + int op = f.getGtLtOp(); + switch( op ) { + case BSONObj::LT: + if( f.woCompare(LE, false) <= 0 ) + return false; + break; + case BSONObj::LTE: + if( f.woCompare(LE, false) < 0 ) + return false; + break; + case BSONObj::GT: + case BSONObj::GTE: + if( f.woCompare(RE, false) >= 0 ) + return false; + break; + case BSONObj::opIN: + case BSONObj::NE: + case BSONObj::opSIZE: + massert( 10423 , "not implemented yet relevant()", false); + case BSONObj::Equality: + goto normal; + default: + massert( 10424 , "bad operator in relevant()?", false); + } + } + return true; + } +normal: + return L.woCompare(q) <= 0 && R.woCompare(q) > 0; + } + + bool ShardKeyPattern::relevantForQuery( const BSONObj& query , Chunk * chunk ){ + massert( 10425 , "not done for compound patterns", patternfields.size() == 1); + + bool rel = relevant(query, chunk->getMin(), chunk->getMax()); + if( ! hasShardKey( query ) ) + assert(rel); + + return rel; + } + + /** + returns a query that filters results only for the range desired, i.e. returns + { $gte : keyval(min), $lt : keyval(max) } + */ + void ShardKeyPattern::getFilter( BSONObjBuilder& b , const BSONObj& min, const BSONObj& max ){ + massert( 10426 , "not done for compound patterns", patternfields.size() == 1); + BSONObjBuilder temp; + temp.appendAs( extractKey(min).firstElement(), "$gte" ); + temp.appendAs( extractKey(max).firstElement(), "$lt" ); + + b.append( patternfields.begin()->c_str(), temp.obj() ); + } + + /** + Example + sort: { ts: -1 } + *this: { ts:1 } + -> -1 + + @return + 0 if sort either doesn't have all the fields or has extra fields + < 0 if sort is descending + > 1 if sort is ascending + */ + int ShardKeyPattern::canOrder( const BSONObj& sort ){ + // e.g.: + // sort { a : 1 , b : -1 } + // pattern { a : -1, b : 1, c : 1 } + // -> -1 + + int dir = 0; + + BSONObjIterator s(sort); + BSONObjIterator p(pattern); + while( 1 ) { + BSONElement e = s.next(); + if( e.eoo() ) + break; + if( !p.moreWithEOO() ) + return 0; + BSONElement ep = p.next(); + bool same = e == ep; + if( !same ) { + if( strcmp(e.fieldName(), ep.fieldName()) != 0 ) + return 0; + // same name, but opposite direction + if( dir == -1 ) + ; // ok + else if( dir == 1 ) + return 0; // wrong direction for a 2nd field + else // dir == 0, initial pass + dir = -1; + } + else { + // fields are the same + if( dir == -1 ) + return 0; // wrong direction + dir = 1; + } + } + + return dir; + } + + string ShardKeyPattern::toString() const { + return pattern.toString(); + } + + /* things to test for compound : + x hasshardkey + _ getFilter (hard?) + _ relevantForQuery + x canOrder + \ middle (deprecating?) + */ + class ShardKeyUnitTest : public UnitTest { + public: + void hasshardkeytest() { + BSONObj x = fromjson("{ zid : \"abcdefg\", num: 1.0, name: \"eliot\" }"); + ShardKeyPattern k( BSON( "num" << 1 ) ); + assert( k.hasShardKey(x) ); + assert( !k.hasShardKey( fromjson("{foo:'a'}") ) ); + + // try compound key + { + ShardKeyPattern k( fromjson("{a:1,b:-1,c:1}") ); + assert( k.hasShardKey( fromjson("{foo:'a',a:'b',c:'z',b:9,k:99}") ) ); + assert( !k.hasShardKey( fromjson("{foo:'a',a:'b',c:'z',bb:9,k:99}") ) ); + assert( !k.hasShardKey( fromjson("{k:99}") ) ); + } + + } + void rfq() { + ShardKeyPattern k( BSON( "key" << 1 ) ); + BSONObj q = BSON( "key" << 3 ); + Chunk c(0); + BSONObj z = fromjson("{ ns : \"alleyinsider.fs.chunks\" , min : {key:2} , max : {key:20} , server : \"localhost:30001\" }"); + c.unserialize(z); + assert( k.relevantForQuery(q, &c) ); + assert( k.relevantForQuery(fromjson("{foo:9,key:4}"), &c) ); + assert( !k.relevantForQuery(fromjson("{foo:9,key:43}"), &c) ); + assert( k.relevantForQuery(fromjson("{foo:9,key:{$gt:10}}"), &c) ); + assert( !k.relevantForQuery(fromjson("{foo:9,key:{$gt:22}}"), &c) ); + assert( k.relevantForQuery(fromjson("{foo:9}"), &c) ); + } + void getfilt() { + ShardKeyPattern k( BSON( "key" << 1 ) ); + BSONObjBuilder b; + k.getFilter(b, fromjson("{z:3,key:30}"), fromjson("{key:90}")); + BSONObj x = fromjson("{ key: { $gte: 30, $lt: 90 } }"); + assert( x.woEqual(b.obj()) ); + } + void testCanOrder() { + ShardKeyPattern k( fromjson("{a:1,b:-1,c:1}") ); + assert( k.canOrder( fromjson("{a:1}") ) == 1 ); + assert( k.canOrder( fromjson("{a:-1}") ) == -1 ); + assert( k.canOrder( fromjson("{a:1,b:-1,c:1}") ) == 1 ); + assert( k.canOrder( fromjson("{a:1,b:1}") ) == 0 ); + assert( k.canOrder( fromjson("{a:-1,b:1}") ) == -1 ); + } + void extractkeytest() { + ShardKeyPattern k( fromjson("{a:1,b:-1,c:1}") ); + + BSONObj x = fromjson("{a:1,b:2,c:3}"); + assert( k.extractKey( fromjson("{a:1,b:2,c:3}") ).woEqual(x) ); + assert( k.extractKey( fromjson("{b:2,c:3,a:1}") ).woEqual(x) ); + } + void run(){ + extractkeytest(); + + ShardKeyPattern k( BSON( "key" << 1 ) ); + + BSONObj min = k.globalMin(); + +// cout << min.jsonString(TenGen) << endl; + + BSONObj max = k.globalMax(); + + BSONObj k1 = BSON( "key" << 5 ); + + assert( k.compare( min , max ) < 0 ); + assert( k.compare( min , k1 ) < 0 ); + assert( k.compare( max , min ) > 0 ); + assert( k.compare( min , min ) == 0 ); + + hasshardkeytest(); + assert( k.hasShardKey( k1 ) ); + assert( ! k.hasShardKey( BSON( "key2" << 1 ) ) ); + + BSONObj a = k1; + BSONObj b = BSON( "key" << 999 ); + + assert( k.compare(a,b) < 0 ); + + assert( k.canOrder( fromjson("{key:1}") ) == 1 ); + assert( k.canOrder( fromjson("{zz:1}") ) == 0 ); + assert( k.canOrder( fromjson("{key:-1}") ) == -1 ); + + testCanOrder(); + getfilt(); + rfq(); + // add middle multitype tests + } + } shardKeyTest; + +} // namespace mongo diff --git a/s/shardkey.h b/s/shardkey.h new file mode 100644 index 0000000..0c357f6 --- /dev/null +++ b/s/shardkey.h @@ -0,0 +1,128 @@ +// shardkey.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../client/dbclient.h" + +namespace mongo { + + class Chunk; + + /* A ShardKeyPattern is a pattern indicating what data to extract from the object to make the shard key from. + Analogous to an index key pattern. + */ + class ShardKeyPattern { + public: + ShardKeyPattern( BSONObj p = BSONObj() ); + + /** + global min is the lowest possible value for this key + e.g. { num : MinKey } + */ + BSONObj globalMin() const { return gMin; } + + /** + global max is the highest possible value for this key + */ + BSONObj globalMax() const { return gMax; } + + bool isGlobalMin( const BSONObj& k ){ + return k.woCompare( globalMin() ) == 0; + } + + bool isGlobalMax( const BSONObj& k ){ + return k.woCompare( globalMax() ) == 0; + } + + bool isGlobal( const BSONObj& k ){ + return isGlobalMin( k ) || isGlobalMax( k ); + } + + /** compare shard keys from the objects specified + l < r negative + l == r 0 + l > r positive + */ + int compare( const BSONObj& l , const BSONObj& r ); + + /** + @return whether or not obj has all fields in this shard key pattern + e.g. + ShardKey({num:1}).hasShardKey({ name:"joe", num:3 }) is true + */ + bool hasShardKey( const BSONObj& obj ); + + /** + returns a query that filters results only for the range desired, i.e. returns + { "field" : { $gte: keyval(min), $lt: keyval(max) } } + */ + void getFilter( BSONObjBuilder& b , const BSONObj& min, const BSONObj& max ); + + /** @return true if shard s is relevant for query q. + + Example: + q: { x : 3 } + *this: { x : 1 } + s: x:2..x:7 + -> true + */ + bool relevantForQuery( const BSONObj& q , Chunk * s ); + + /** + Returns if the given sort pattern can be ordered by the shard key pattern. + Example + sort: { ts: -1 } + *this: { ts:1 } + -> -1 + + @return + 0 if sort either doesn't have all the fields or has extra fields + < 0 if sort is descending + > 1 if sort is ascending + */ + int canOrder( const BSONObj& sort ); + + BSONObj key() { return pattern; } + + string toString() const; + + BSONObj extractKey(const BSONObj& from) const; + + bool partOfShardKey(const string& key ) const { + return patternfields.count( key ) > 0; + } + + operator string() const { + return pattern.toString(); + } + private: + BSONObj pattern; + BSONObj gMin; + BSONObj gMax; + + /* question: better to have patternfields precomputed or not? depends on if we use copy contructor often. */ + set<string> patternfields; + bool relevant(const BSONObj& query, const BSONObj& L, const BSONObj& R); + }; + + inline BSONObj ShardKeyPattern::extractKey(const BSONObj& from) const { + return from.extractFields(pattern); + } + +} diff --git a/s/strategy.cpp b/s/strategy.cpp new file mode 100644 index 0000000..b485bd2 --- /dev/null +++ b/s/strategy.cpp @@ -0,0 +1,221 @@ +// stragegy.cpp + +#include "stdafx.h" +#include "request.h" +#include "../util/background.h" +#include "../client/connpool.h" +#include "../db/commands.h" +#include "server.h" + +namespace mongo { + + // ----- Strategy ------ + + void Strategy::doWrite( int op , Request& r , string server ){ + ScopedDbConnection dbcon( server ); + DBClientBase &_c = dbcon.conn(); + + /* TODO FIX - do not case and call DBClientBase::say() */ + DBClientConnection&c = dynamic_cast<DBClientConnection&>(_c); + c.port().say( r.m() ); + + dbcon.done(); + } + + void Strategy::doQuery( Request& r , string server ){ + try{ + ScopedDbConnection dbcon( server ); + DBClientBase &_c = dbcon.conn(); + + checkShardVersion( _c , r.getns() ); + + // TODO: This will not work with Paired connections. Fix. + DBClientConnection&c = dynamic_cast<DBClientConnection&>(_c); + Message response; + bool ok = c.port().call( r.m(), response); + + { + QueryResult *qr = (QueryResult *) response.data; + if ( qr->resultFlags() & QueryResult::ResultFlag_ShardConfigStale ){ + dbcon.done(); + throw StaleConfigException( r.getns() , "Strategy::doQuery" ); + } + } + + uassert( 10200 , "mongos: error calling db", ok); + r.reply( response ); + dbcon.done(); + } + catch ( AssertionException& e ) { + BSONObjBuilder err; + err.append("$err", string("mongos: ") + (e.msg.empty() ? "assertion during query" : e.msg)); + BSONObj errObj = err.done(); + replyToQuery(QueryResult::ResultFlag_ErrSet, r.p() , r.m() , errObj); + } + } + + void Strategy::insert( string server , const char * ns , const BSONObj& obj ){ + ScopedDbConnection dbcon( server ); + checkShardVersion( dbcon.conn() , ns ); + dbcon->insert( ns , obj ); + dbcon.done(); + } + + map<DBClientBase*,unsigned long long> checkShardVersionLastSequence; + + class WriteBackListener : public BackgroundJob { + protected: + + WriteBackListener( const string& addr ) : _addr( addr ){ + cout << "creating WriteBackListener for: " << addr << endl; + } + + void run(){ + int secsToSleep = 0; + while ( 1 ){ + try { + ScopedDbConnection conn( _addr ); + + BSONObj result; + + { + BSONObjBuilder cmd; + cmd.appendOID( "writebacklisten" , &serverID ); + if ( ! conn->runCommand( "admin" , cmd.obj() , result ) ){ + log() << "writebacklisten command failed! " << result << endl; + conn.done(); + continue; + } + + } + + log(1) << "writebacklisten result: " << result << endl; + + BSONObj data = result.getObjectField( "data" ); + if ( data.getBoolField( "writeBack" ) ){ + string ns = data["ns"].valuestrsafe(); + + int len; + + Message m( (void*)data["msg"].binData( len ) , false ); + massert( 10427 , "invalid writeback message" , m.data->valid() ); + + grid.getDBConfig( ns )->getChunkManager( ns , true ); + + Request r( m , 0 ); + r.process(); + } + else { + log() << "unknown writeBack result: " << result << endl; + } + + conn.done(); + secsToSleep = 0; + } + catch ( std::exception e ){ + log() << "WriteBackListener exception : " << e.what() << endl; + } + catch ( ... ){ + log() << "WriteBackListener uncaught exception!" << endl; + } + secsToSleep++; + sleepsecs(secsToSleep); + if ( secsToSleep > 10 ) + secsToSleep = 0; + } + } + + private: + string _addr; + static map<string,WriteBackListener*> _cache; + + public: + static void init( DBClientBase& conn ){ + WriteBackListener*& l = _cache[conn.getServerAddress()]; + if ( l ) + return; + l = new WriteBackListener( conn.getServerAddress() ); + l->go(); + } + + }; + + map<string,WriteBackListener*> WriteBackListener::_cache; + + + void checkShardVersion( DBClientBase& conn , const string& ns , bool authoritative ){ + // TODO: cache, optimize, etc... + + WriteBackListener::init( conn ); + + DBConfig * conf = grid.getDBConfig( ns ); + if ( ! conf ) + return; + + ShardChunkVersion version = 0; + unsigned long long officialSequenceNumber = 0; + + if ( conf->isSharded( ns ) ){ + ChunkManager * manager = conf->getChunkManager( ns , authoritative ); + officialSequenceNumber = manager->getSequenceNumber(); + version = manager->getVersion( conn.getServerAddress() ); + } + + unsigned long long & sequenceNumber = checkShardVersionLastSequence[ &conn ]; + if ( officialSequenceNumber == sequenceNumber ) + return; + + log(2) << " have to set shard version for conn: " << &conn << " ns:" << ns << " my last seq: " << sequenceNumber << " current: " << officialSequenceNumber << endl; + + BSONObj result; + if ( setShardVersion( conn , ns , version , authoritative , result ) ){ + // success! + log(1) << " setShardVersion success!" << endl; + sequenceNumber = officialSequenceNumber; + return; + } + + log(1) << " setShardVersion failed!\n" << result << endl; + + if ( result.getBoolField( "need_authoritative" ) ) + massert( 10428 , "need_authoritative set but in authoritative mode already" , ! authoritative ); + + if ( ! authoritative ){ + checkShardVersion( conn , ns , 1 ); + return; + } + + log(1) << " setShardVersion failed: " << result << endl; + massert( 10429 , "setShardVersion failed!" , 0 ); + } + + bool setShardVersion( DBClientBase & conn , const string& ns , ShardChunkVersion version , bool authoritative , BSONObj& result ){ + + BSONObjBuilder cmdBuilder; + cmdBuilder.append( "setShardVersion" , ns.c_str() ); + cmdBuilder.append( "configdb" , configServer.modelServer() ); + cmdBuilder.appendTimestamp( "version" , version ); + cmdBuilder.appendOID( "serverID" , &serverID ); + if ( authoritative ) + cmdBuilder.appendBool( "authoritative" , 1 ); + BSONObj cmd = cmdBuilder.obj(); + + log(1) << " setShardVersion " << conn.getServerAddress() << " " << ns << " " << cmd << " " << &conn << endl; + + return conn.runCommand( "admin" , cmd , result ); + } + + bool lockNamespaceOnServer( const string& server , const string& ns ){ + ScopedDbConnection conn( server ); + bool res = lockNamespaceOnServer( conn.conn() , ns ); + conn.done(); + return res; + } + + bool lockNamespaceOnServer( DBClientBase& conn , const string& ns ){ + BSONObj lockResult; + return setShardVersion( conn , ns , grid.getNextOpTime() , true , lockResult ); + } + + +} diff --git a/s/strategy.h b/s/strategy.h new file mode 100644 index 0000000..e4b93b5 --- /dev/null +++ b/s/strategy.h @@ -0,0 +1,36 @@ +// strategy.h + +#pragma once + +#include "../stdafx.h" +#include "chunk.h" +#include "request.h" + +namespace mongo { + + class Strategy { + public: + Strategy(){} + virtual ~Strategy() {} + virtual void queryOp( Request& r ) = 0; + virtual void getMore( Request& r ) = 0; + virtual void writeOp( int op , Request& r ) = 0; + + protected: + void doWrite( int op , Request& r , string server ); + void doQuery( Request& r , string server ); + + void insert( string server , const char * ns , const BSONObj& obj ); + + }; + + extern Strategy * SINGLE; + extern Strategy * SHARDED; + + bool setShardVersion( DBClientBase & conn , const string& ns , ShardChunkVersion version , bool authoritative , BSONObj& result ); + + bool lockNamespaceOnServer( const string& server , const string& ns ); + bool lockNamespaceOnServer( DBClientBase& conn , const string& ns ); + +} + diff --git a/s/strategy_shard.cpp b/s/strategy_shard.cpp new file mode 100644 index 0000000..34cf226 --- /dev/null +++ b/s/strategy_shard.cpp @@ -0,0 +1,260 @@ +// strategy_sharded.cpp + +#include "stdafx.h" +#include "request.h" +#include "chunk.h" +#include "cursors.h" +#include "../client/connpool.h" +#include "../db/commands.h" + +// error codes 8010-8040 + +namespace mongo { + + class ShardStrategy : public Strategy { + + virtual void queryOp( Request& r ){ + QueryMessage q( r.d() ); + + log(3) << "shard query: " << q.ns << " " << q.query << endl; + + if ( q.ntoreturn == 1 && strstr(q.ns, ".$cmd") ) + throw UserException( 8010 , "something is wrong, shouldn't see a command here" ); + + ChunkManager * info = r.getChunkManager(); + assert( info ); + + Query query( q.query ); + + vector<Chunk*> shards; + info->getChunksForQuery( shards , query.getFilter() ); + + set<ServerAndQuery> servers; + map<string,int> serverCounts; + for ( vector<Chunk*>::iterator i = shards.begin(); i != shards.end(); i++ ){ + servers.insert( ServerAndQuery( (*i)->getShard() , (*i)->getFilter() ) ); + int& num = serverCounts[(*i)->getShard()]; + num++; + } + + if ( logLevel > 4 ){ + StringBuilder ss; + ss << " shard query servers: " << servers.size() << "\n"; + for ( set<ServerAndQuery>::iterator i = servers.begin(); i!=servers.end(); i++ ){ + const ServerAndQuery& s = *i; + ss << " " << s.toString() << "\n"; + } + log() << ss.str(); + } + + ClusteredCursor * cursor = 0; + + BSONObj sort = query.getSort(); + + if ( sort.isEmpty() ){ + // 1. no sort, can just hit them in serial + cursor = new SerialServerClusteredCursor( servers , q ); + } + else { + int shardKeyOrder = info->getShardKey().canOrder( sort ); + if ( shardKeyOrder ){ + // 2. sort on shard key, can do in serial intelligently + set<ServerAndQuery> buckets; + for ( vector<Chunk*>::iterator i = shards.begin(); i != shards.end(); i++ ){ + Chunk * s = *i; + buckets.insert( ServerAndQuery( s->getShard() , s->getFilter() , s->getMin() ) ); + } + cursor = new SerialServerClusteredCursor( buckets , q , shardKeyOrder ); + } + else { + // 3. sort on non-sharded key, pull back a portion from each server and iterate slowly + cursor = new ParallelSortClusteredCursor( servers , q , sort ); + } + } + + assert( cursor ); + + log(5) << " cursor type: " << cursor->type() << endl; + + ShardedClientCursor * cc = new ShardedClientCursor( q , cursor ); + if ( ! cc->sendNextBatch( r ) ){ + delete( cursor ); + return; + } + log(6) << "storing cursor : " << cc->getId() << endl; + cursorCache.store( cc ); + } + + virtual void getMore( Request& r ){ + int ntoreturn = r.d().pullInt(); + long long id = r.d().pullInt64(); + + log(6) << "want cursor : " << id << endl; + + ShardedClientCursor * cursor = cursorCache.get( id ); + if ( ! cursor ){ + log(6) << "\t invalid cursor :(" << endl; + replyToQuery( QueryResult::ResultFlag_CursorNotFound , r.p() , r.m() , 0 , 0 , 0 ); + return; + } + + if ( cursor->sendNextBatch( r , ntoreturn ) ){ + log(6) << "\t cursor finished: " << id << endl; + return; + } + + delete( cursor ); + cursorCache.remove( id ); + } + + void _insert( Request& r , DbMessage& d, ChunkManager* manager ){ + + while ( d.moreJSObjs() ){ + BSONObj o = d.nextJsObj(); + if ( ! manager->hasShardKey( o ) ){ + + bool bad = true; + + if ( manager->getShardKey().partOfShardKey( "_id" ) ){ + BSONObjBuilder b; + b.appendOID( "_id" , 0 , true ); + b.appendElements( o ); + o = b.obj(); + bad = ! manager->hasShardKey( o ); + } + + if ( bad ){ + log() << "tried to insert object without shard key: " << r.getns() << " " << o << endl; + throw UserException( 8011 , "tried to insert object without shard key" ); + } + + } + + Chunk& c = manager->findChunk( o ); + log(4) << " server:" << c.getShard() << " " << o << endl; + insert( c.getShard() , r.getns() , o ); + + c.splitIfShould( o.objsize() ); + } + } + + void _update( Request& r , DbMessage& d, ChunkManager* manager ){ + int flags = d.pullInt(); + + BSONObj query = d.nextJsObj(); + uassert( 10201 , "invalid update" , d.moreJSObjs() ); + BSONObj toupdate = d.nextJsObj(); + + BSONObj chunkFinder = query; + + bool upsert = flags & UpdateOption_Upsert; + bool multi = flags & UpdateOption_Multi; + + if ( multi ) + uassert( 10202 , "can't mix multi and upsert and sharding" , ! upsert ); + + if ( upsert && !(manager->hasShardKey(toupdate) || + (toupdate.firstElement().fieldName()[0] == '$' && manager->hasShardKey(query)))) + { + throw UserException( 8012 , "can't upsert something without shard key" ); + } + + bool save = false; + if ( ! manager->hasShardKey( query ) ){ + if ( multi ){ + } + else if ( query.nFields() != 1 || strcmp( query.firstElement().fieldName() , "_id" ) ){ + throw UserException( 8013 , "can't do update with query that doesn't have the shard key" ); + } + else { + save = true; + chunkFinder = toupdate; + } + } + + + if ( ! save ){ + if ( toupdate.firstElement().fieldName()[0] == '$' ){ + // TODO: check for $set, etc.. on shard key + } + else if ( manager->hasShardKey( toupdate ) && manager->getShardKey().compare( query , toupdate ) ){ + throw UserException( 8014 , "change would move shards!" ); + } + } + + if ( multi ){ + vector<Chunk*> chunks; + manager->getChunksForQuery( chunks , chunkFinder ); + set<string> seen; + for ( vector<Chunk*>::iterator i=chunks.begin(); i!=chunks.end(); i++){ + Chunk * c = *i; + if ( seen.count( c->getShard() ) ) + continue; + doWrite( dbUpdate , r , c->getShard() ); + seen.insert( c->getShard() ); + } + } + else { + Chunk& c = manager->findChunk( chunkFinder ); + doWrite( dbUpdate , r , c.getShard() ); + c.splitIfShould( d.msg().data->dataLen() ); + } + + } + + void _delete( Request& r , DbMessage& d, ChunkManager* manager ){ + + int flags = d.pullInt(); + bool justOne = flags & 1; + + uassert( 10203 , "bad delete message" , d.moreJSObjs() ); + BSONObj pattern = d.nextJsObj(); + + vector<Chunk*> chunks; + manager->getChunksForQuery( chunks , pattern ); + cout << "delete : " << pattern << " \t " << chunks.size() << " justOne: " << justOne << endl; + if ( chunks.size() == 1 ){ + doWrite( dbDelete , r , chunks[0]->getShard() ); + return; + } + + if ( justOne && ! pattern.hasField( "_id" ) ) + throw UserException( 8015 , "can only delete with a non-shard key pattern if can delete as many as we find" ); + + set<string> seen; + for ( vector<Chunk*>::iterator i=chunks.begin(); i!=chunks.end(); i++){ + Chunk * c = *i; + if ( seen.count( c->getShard() ) ) + continue; + seen.insert( c->getShard() ); + doWrite( dbDelete , r , c->getShard() ); + } + } + + virtual void writeOp( int op , Request& r ){ + const char *ns = r.getns(); + log(3) << "write: " << ns << endl; + + DbMessage& d = r.d(); + ChunkManager * info = r.getChunkManager(); + assert( info ); + + if ( op == dbInsert ){ + _insert( r , d , info ); + } + else if ( op == dbUpdate ){ + _update( r , d , info ); + } + else if ( op == dbDelete ){ + _delete( r , d , info ); + } + else { + log() << "sharding can't do write op: " << op << endl; + throw UserException( 8016 , "can't do this write op on sharded collection" ); + } + + } + }; + + Strategy * SHARDED = new ShardStrategy(); +} diff --git a/s/strategy_single.cpp b/s/strategy_single.cpp new file mode 100644 index 0000000..9cf8a63 --- /dev/null +++ b/s/strategy_single.cpp @@ -0,0 +1,131 @@ +// strategy_simple.cpp + +#include "stdafx.h" +#include "request.h" +#include "../client/connpool.h" +#include "../db/commands.h" + +namespace mongo { + + class SingleStrategy : public Strategy { + + public: + SingleStrategy(){ + _commandsSafeToPass.insert( "$eval" ); + _commandsSafeToPass.insert( "create" ); + } + + private: + virtual void queryOp( Request& r ){ + QueryMessage q( r.d() ); + + bool lateAssert = false; + + log(3) << "single query: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn << endl; + + try { + if ( ( q.ntoreturn == -1 || q.ntoreturn == 1 ) && strstr(q.ns, ".$cmd") ) { + BSONObjBuilder builder; + bool ok = Command::runAgainstRegistered(q.ns, q.query, builder); + if ( ok ) { + BSONObj x = builder.done(); + replyToQuery(0, r.p(), r.m(), x); + return; + } + + string commandName = q.query.firstElement().fieldName(); + if ( ! _commandsSafeToPass.count( commandName ) ) + log() << "passing through unknown command: " << commandName << " " << q.query << endl; + } + + lateAssert = true; + doQuery( r , r.singleServerName() ); + } + catch ( AssertionException& e ) { + assert( !lateAssert ); + BSONObjBuilder err; + err.append("$err", string("mongos: ") + (e.msg.empty() ? "assertion during query" : e.msg)); + BSONObj errObj = err.done(); + replyToQuery(QueryResult::ResultFlag_ErrSet, r.p() , r.m() , errObj); + return; + } + + } + + virtual void getMore( Request& r ){ + const char *ns = r.getns(); + + log(3) << "single getmore: " << ns << endl; + + ScopedDbConnection dbcon( r.singleServerName() ); + DBClientBase& _c = dbcon.conn(); + + // TODO + DBClientConnection &c = dynamic_cast<DBClientConnection&>(_c); + + Message response; + bool ok = c.port().call( r.m() , response); + uassert( 10204 , "dbgrid: getmore: error calling db", ok); + r.reply( response ); + + dbcon.done(); + + } + + void handleIndexWrite( int op , Request& r ){ + + DbMessage& d = r.d(); + + if ( op == dbInsert ){ + while( d.moreJSObjs() ){ + BSONObj o = d.nextJsObj(); + const char * ns = o["ns"].valuestr(); + if ( r.getConfig()->isSharded( ns ) ){ + uassert( 10205 , (string)"can't use unique indexes with sharding ns:" + ns + + " key: " + o["key"].embeddedObjectUserCheck().toString() , + IndexDetails::isIdIndexPattern( o["key"].embeddedObjectUserCheck() ) || + ! o["unique"].trueValue() ); + ChunkManager * cm = r.getConfig()->getChunkManager( ns ); + assert( cm ); + for ( int i=0; i<cm->numChunks();i++) + doWrite( op , r , cm->getChunk(i)->getShard() ); + } + else { + doWrite( op , r , r.singleServerName() ); + } + } + } + else if ( op == dbUpdate ){ + throw UserException( 8050 , "can't update system.indexes" ); + } + else if ( op == dbDelete ){ + // TODO + throw UserException( 8051 , "can't delete indexes on sharded collection yet" ); + } + else { + log() << "handleIndexWrite invalid write op: " << op << endl; + throw UserException( 8052 , "handleIndexWrite invalid write op" ); + } + + } + + virtual void writeOp( int op , Request& r ){ + const char *ns = r.getns(); + + if ( r.isShardingEnabled() && + strstr( ns , ".system.indexes" ) == strstr( ns , "." ) && + strstr( ns , "." ) ){ + log(1) << " .system.indexes write for: " << ns << endl; + handleIndexWrite( op , r ); + return; + } + + log(3) << "single write: " << ns << endl; + doWrite( op , r , r.singleServerName() ); + } + + set<string> _commandsSafeToPass; + }; + + Strategy * SINGLE = new SingleStrategy(); +} diff --git a/s/util.h b/s/util.h new file mode 100644 index 0000000..ba40a29 --- /dev/null +++ b/s/util.h @@ -0,0 +1,51 @@ +// util.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../stdafx.h" +#include "../client/dbclient.h" + +/** + some generic sharding utils that can be used in mongod or mongos + */ + +namespace mongo { + + /** + your config info for a given shard/chunk is out of date */ + class StaleConfigException : public std::exception { + public: + StaleConfigException( const string& ns , const string& msg){ + stringstream s; + s << "StaleConfigException ns: " << ns << " " << msg; + _msg = s.str(); + } + + virtual ~StaleConfigException() throw(){} + + virtual const char* what() const throw(){ + return _msg.c_str(); + } + private: + string _msg; + }; + + void checkShardVersion( DBClientBase & conn , const string& ns , bool authoritative = false ); + +} diff --git a/scripting/engine.cpp b/scripting/engine.cpp new file mode 100644 index 0000000..dc088fb --- /dev/null +++ b/scripting/engine.cpp @@ -0,0 +1,399 @@ +// engine.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "engine.h" +#include "../util/file.h" +#include "../client/dbclient.h" + +namespace mongo { + + long long Scope::_lastVersion = 1; + + int Scope::_numScopes = 0; + + Scope::Scope() : _localDBName("") , _loadedVersion(0){ + _numScopes++; + } + + Scope::~Scope(){ + _numScopes--; + } + + ScriptEngine::ScriptEngine() : _scopeInitCallback() { + } + + ScriptEngine::~ScriptEngine(){ + } + + void Scope::append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ){ + int t = type( scopeName ); + + switch ( t ){ + case Object: + builder.append( fieldName , getObject( scopeName ) ); + break; + case Array: + builder.appendArray( fieldName , getObject( scopeName ) ); + break; + case NumberDouble: + builder.append( fieldName , getNumber( scopeName ) ); + break; + case NumberInt: + builder.append( fieldName , getNumberInt( scopeName ) ); + break; + case NumberLong: + builder.append( fieldName , getNumberLongLong( scopeName ) ); + break; + case String: + builder.append( fieldName , getString( scopeName ).c_str() ); + break; + case Bool: + builder.appendBool( fieldName , getBoolean( scopeName ) ); + break; + case jstNULL: + case Undefined: + builder.appendNull( fieldName ); + break; + case Date: + // TODO: make signed + builder.appendDate( fieldName , Date_t((unsigned long long)getNumber( scopeName )) ); + break; + default: + stringstream temp; + temp << "can't append type from:"; + temp << t; + uassert( 10206 , temp.str() , 0 ); + } + + } + + int Scope::invoke( const char* code , const BSONObj& args, int timeoutMs ){ + ScriptingFunction func = createFunction( code ); + uassert( 10207 , "compile failed" , func ); + return invoke( func , args, timeoutMs ); + } + + bool Scope::execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ + + path p( filename ); + + if ( ! exists( p ) ){ + cout << "file [" << filename << "] doesn't exist" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + // iterate directories and recurse using all *.js files in the directory + if ( is_directory( p ) ){ + directory_iterator end; + bool empty = true; + for (directory_iterator it (p); it != end; it++){ + empty = false; + path sub (*it); + if (!endsWith(sub.string().c_str(), ".js")) + continue; + if (!execFile(sub.string().c_str(), printResult, reportError, assertOnError, timeoutMs)) + return false; + } + + if (empty){ + cout << "directory [" << filename << "] doesn't have any *.js files" << endl; + if ( assertOnError ) + assert( 0 ); + return false; + } + + return true; + } + + File f; + f.open( filename.c_str() , true ); + + fileofs L = f.len(); + assert( L <= 0x7ffffffe ); + char * data = (char*)malloc( (size_t) L+1 ); + data[L] = 0; + f.read( 0 , data , (size_t) L ); + + return exec( data , filename , printResult , reportError , assertOnError, timeoutMs ); + } + + void Scope::storedFuncMod(){ + _lastVersion++; + } + + void Scope::validateObjectIdString( const string &str ) { + massert( 10448 , "invalid object id: length", str.size() == 24 ); + + for ( string::size_type i=0; i<str.size(); i++ ){ + char c = str[i]; + if ( ( c >= '0' && c <= '9' ) || + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'F' ) ){ + continue; + } + massert( 10430 , "invalid object id: not hex", false ); + } + } + + void Scope::loadStored( bool ignoreNotConnected ){ + if ( _localDBName.size() == 0 ){ + if ( ignoreNotConnected ) + return; + uassert( 10208 , "need to have locallyConnected already" , _localDBName.size() ); + } + if ( _loadedVersion == _lastVersion ) + return; + + _loadedVersion = _lastVersion; + + string coll = _localDBName + ".system.js"; + + static DBClientBase * db = createDirectClient(); + auto_ptr<DBClientCursor> c = db->query( coll , Query() ); + while ( c->more() ){ + BSONObj o = c->next(); + + BSONElement n = o["_id"]; + BSONElement v = o["value"]; + + uassert( 10209 , "name has to be a string" , n.type() == String ); + uassert( 10210 , "value has to be set" , v.type() != EOO ); + + setElement( n.valuestr() , v ); + } + + } + + ScriptingFunction Scope::createFunction( const char * code ){ + if ( code[0] == '/' && code [1] == '*' ){ + code += 2; + while ( code[0] && code[1] ){ + if ( code[0] == '*' && code[1] == '/' ){ + code += 2; + break; + } + code++; + } + } + map<string,ScriptingFunction>::iterator i = _cachedFunctions.find( code ); + if ( i != _cachedFunctions.end() ) + return i->second; + ScriptingFunction f = _createFunction( code ); + _cachedFunctions[code] = f; + return f; + } + + typedef map< string , list<Scope*> > PoolToScopes; + + class ScopeCache { + public: + + ScopeCache(){ + _magic = 17; + } + + ~ScopeCache(){ + assert( _magic == 17 ); + _magic = 1; + + if ( inShutdown() ) + return; + + clear(); + } + + void done( const string& pool , Scope * s ){ + boostlock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + if ( l.size() > 10 ){ + delete s; + } + else { + l.push_back( s ); + s->reset(); + } + } + + Scope * get( const string& pool ){ + boostlock lk( _mutex ); + list<Scope*> & l = _pools[pool]; + if ( l.size() == 0 ) + return 0; + + Scope * s = l.back(); + l.pop_back(); + s->reset(); + return s; + } + + void clear(){ + set<Scope*> seen; + + for ( PoolToScopes::iterator i=_pools.begin() ; i != _pools.end(); i++ ){ + for ( list<Scope*>::iterator j=i->second.begin(); j != i->second.end(); j++ ){ + Scope * s = *j; + assert( ! seen.count( s ) ); + delete s; + seen.insert( s ); + } + } + + _pools.clear(); + } + + private: + PoolToScopes _pools; + boost::mutex _mutex; + int _magic; + }; + + thread_specific_ptr<ScopeCache> scopeCache; + + class PooledScope : public Scope { + public: + PooledScope( const string pool , Scope * real ) : _pool( pool ) , _real( real ){ + _real->loadStored( true ); + }; + virtual ~PooledScope(){ + ScopeCache * sc = scopeCache.get(); + if ( sc ){ + sc->done( _pool , _real ); + _real = 0; + } + else { + log() << "warning: scopeCache is empty!" << endl; + delete _real; + _real = 0; + } + } + + void reset(){ + _real->reset(); + } + void init( BSONObj * data ){ + _real->init( data ); + } + + void localConnect( const char * dbName ){ + _real->localConnect( dbName ); + } + void externalSetup(){ + _real->externalSetup(); + } + + double getNumber( const char *field ){ + return _real->getNumber( field ); + } + string getString( const char *field ){ + return _real->getString( field ); + } + bool getBoolean( const char *field ){ + return _real->getBoolean( field ); + } + BSONObj getObject( const char *field ){ + return _real->getObject( field ); + } + + int type( const char *field ){ + return _real->type( field ); + } + + void setElement( const char *field , const BSONElement& val ){ + _real->setElement( field , val ); + } + void setNumber( const char *field , double val ){ + _real->setNumber( field , val ); + } + void setString( const char *field , const char * val ){ + _real->setString( field , val ); + } + void setObject( const char *field , const BSONObj& obj , bool readOnly=true ){ + _real->setObject( field , obj , readOnly ); + } + void setBoolean( const char *field , bool val ){ + _real->setBoolean( field , val ); + } + void setThis( const BSONObj * obj ){ + _real->setThis( obj ); + } + + ScriptingFunction createFunction( const char * code ){ + return _real->createFunction( code ); + } + + ScriptingFunction _createFunction( const char * code ){ + return _real->createFunction( code ); + } + + /** + * @return 0 on success + */ + int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + return _real->invoke( func , args , timeoutMs , ignoreReturn ); + } + + string getError(){ + return _real->getError(); + } + + bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + return _real->exec( code , name , printResult , reportError , assertOnError , timeoutMs ); + } + bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ){ + return _real->execFile( filename , printResult , reportError , assertOnError , timeoutMs ); + } + + void injectNative( const char *field, NativeFunction func ){ + _real->injectNative( field , func ); + } + + void gc(){ + _real->gc(); + } + + private: + string _pool; + Scope * _real; + }; + + auto_ptr<Scope> ScriptEngine::getPooledScope( const string& pool ){ + if ( ! scopeCache.get() ){ + scopeCache.reset( new ScopeCache() ); + } + + Scope * s = scopeCache->get( pool ); + if ( ! s ){ + s = newScope(); + } + + auto_ptr<Scope> p; + p.reset( new PooledScope( pool , s ) ); + return p; + } + + void ScriptEngine::threadDone(){ + ScopeCache * sc = scopeCache.get(); + if ( sc ){ + sc->clear(); + } + } + + ScriptEngine * globalScriptEngine; +} diff --git a/scripting/engine.h b/scripting/engine.h new file mode 100644 index 0000000..99c88cf --- /dev/null +++ b/scripting/engine.h @@ -0,0 +1,154 @@ +// engine.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include "../db/jsobj.h" + +extern const char * jsconcatcode; // TODO: change name to mongoJSCode + +namespace mongo { + + typedef unsigned long long ScriptingFunction; + typedef BSONObj (*NativeFunction) ( const BSONObj &args ); + + class Scope : boost::noncopyable { + public: + Scope(); + virtual ~Scope(); + + virtual void reset() = 0; + virtual void init( BSONObj * data ) = 0; + void init( const char * data ){ + BSONObj o( data , 0 ); + init( &o ); + } + + virtual void localConnect( const char * dbName ) = 0; + virtual void externalSetup() = 0; + + virtual double getNumber( const char *field ) = 0; + virtual int getNumberInt( const char *field ){ return (int)getNumber( field ); } + virtual long long getNumberLongLong( const char *field ){ return (long long)getNumber( field ); } + virtual string getString( const char *field ) = 0; + virtual bool getBoolean( const char *field ) = 0; + virtual BSONObj getObject( const char *field ) = 0; + + virtual int type( const char *field ) = 0; + + void append( BSONObjBuilder & builder , const char * fieldName , const char * scopeName ); + + virtual void setElement( const char *field , const BSONElement& e ) = 0; + virtual void setNumber( const char *field , double val ) = 0; + virtual void setString( const char *field , const char * val ) = 0; + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly=true ) = 0; + virtual void setBoolean( const char *field , bool val ) = 0; + virtual void setThis( const BSONObj * obj ) = 0; + + virtual ScriptingFunction createFunction( const char * code ); + + /** + * @return 0 on success + */ + virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ) = 0; + void invokeSafe( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 ){ + int res = invoke( func , args , timeoutMs ); + if ( res == 0 ) + return; + throw UserException( 9004 , (string)"invoke failed: " + getError() ); + } + virtual string getError() = 0; + + int invoke( const char* code , const BSONObj& args, int timeoutMs = 0 ); + void invokeSafe( const char* code , const BSONObj& args, int timeoutMs = 0 ){ + if ( invoke( code , args , timeoutMs ) == 0 ) + return; + throw UserException( 9005 , (string)"invoke failed: " + getError() ); + } + + virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ) = 0; + virtual void execSetup( const string& code , const string& name = "setup" ){ + exec( code , name , false , true , true , 0 ); + } + virtual bool execFile( const string& filename , bool printResult , bool reportError , bool assertOnError, int timeoutMs = 0 ); + + virtual void injectNative( const char *field, NativeFunction func ) = 0; + + virtual void gc() = 0; + + void loadStored( bool ignoreNotConnected = false ); + + /** + if any changes are made to .system.js, call this + right now its just global - slightly inefficient, but a lot simpler + */ + static void storedFuncMod(); + + static int getNumScopes(){ + return _numScopes; + } + + static void validateObjectIdString( const string &str ); + + protected: + + virtual ScriptingFunction _createFunction( const char * code ) = 0; + + string _localDBName; + long long _loadedVersion; + static long long _lastVersion; + map<string,ScriptingFunction> _cachedFunctions; + + static int _numScopes; + }; + + class ScriptEngine : boost::noncopyable { + public: + ScriptEngine(); + virtual ~ScriptEngine(); + + virtual Scope * newScope() { + Scope *s = createScope(); + if ( s && _scopeInitCallback ) + _scopeInitCallback( *s ); + return s; + } + + virtual void runTest() = 0; + + virtual bool utf8Ok() const = 0; + + static void setup(); + + auto_ptr<Scope> getPooledScope( const string& pool ); + void threadDone(); + + struct Unlocker { virtual ~Unlocker() {} }; + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new Unlocker ); } + + void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInitCallback = func; } + + protected: + virtual Scope * createScope() = 0; + + private: + void ( *_scopeInitCallback )( Scope & ); + }; + + extern ScriptEngine * globalScriptEngine; +} diff --git a/scripting/engine_java.cpp b/scripting/engine_java.cpp new file mode 100644 index 0000000..0ed6f1d --- /dev/null +++ b/scripting/engine_java.cpp @@ -0,0 +1,763 @@ +// java.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "stdafx.h" +#include "engine_java.h" +#include <iostream> +#include <map> +#include <list> + +#include "../db/jsobj.h" +#include "../db/db.h" + +using namespace boost::filesystem; + +namespace mongo { + +//#define JNI_DEBUG 1 + +#ifdef JNI_DEBUG +#undef JNI_DEBUG +#define JNI_DEBUG(x) cout << x << endl +#else +#undef JNI_DEBUG +#define JNI_DEBUG(x) +#endif + +} // namespace mongo + + + +#include "../util/message.h" +#include "../db/db.h" + +using namespace std; + +namespace mongo { + +#if defined(_WIN32) + /* [dm] this being undefined without us adding it here means there is + no tss cleanup on windows for boost lib? + we don't care for now esp on windows only + + the boost source says: + + This function's sole purpose is to cause a link error in cases where + automatic tss cleanup is not implemented by Boost.Threads as a + reminder that user code is responsible for calling the necessary + functions at the appropriate times (and for implementing an a + tss_cleanup_implemented() function to eliminate the linker's + missing symbol error). + + If Boost.Threads later implements automatic tss cleanup in cases + where it currently doesn't (which is the plan), the duplicate + symbol error will warn the user that their custom solution is no + longer needed and can be removed. + */ + extern "C" void tss_cleanup_implemented(void) { + //out() << "tss_cleanup_implemented called" << endl; + } +#endif + + JavaJSImpl * JavaJS = 0; + extern string dbExecCommand; + +#if !defined(NOJNI) + + void myJNIClean( JNIEnv * env ) { + JavaJS->detach( env ); + } + +#if defined(_WIN32) + const char SYSTEM_COLON = ';'; +#else + const char SYSTEM_COLON = ':'; +#endif + + + void _addClassPath( const char * ed , stringstream & ss , const char * subdir ) { + path includeDir(ed); + includeDir /= subdir; + directory_iterator end; + try { + directory_iterator i(includeDir); + while ( i != end ) { + path p = *i; + ss << SYSTEM_COLON << p.string(); + i++; + } + } + catch (...) { + problem() << "exception looking for ed class path includeDir: " << includeDir.string() << endl; + sleepsecs(3); + dbexit( EXIT_JAVA ); + } + } + + + JavaJSImpl::JavaJSImpl(const char *appserverPath) { + _jvm = 0; + _mainEnv = 0; + _dbhook = 0; + + stringstream ss; + string edTemp; + + const char * ed = 0; + ss << "-Djava.class.path=."; + + if ( appserverPath ) { + ed = findEd(appserverPath); + assert( ed ); + + ss << SYSTEM_COLON << ed << "/build/"; + + _addClassPath( ed , ss , "include" ); + _addClassPath( ed , ss , "include/jython/" ); + _addClassPath( ed , ss , "include/jython/javalib" ); + } + else { + const string jars = findJars(); + _addClassPath( jars.c_str() , ss , "jars" ); + + edTemp += (string)jars + "/jars/mongojs-js.jar"; + ed = edTemp.c_str(); + } + + + +#if defined(_WIN32) + ss << SYSTEM_COLON << "C:\\Program Files\\Java\\jdk\\lib\\tools.jar"; +#else + ss << SYSTEM_COLON << "/opt/java/lib/tools.jar"; +#endif + + if ( getenv( "CLASSPATH" ) ) + ss << SYSTEM_COLON << getenv( "CLASSPATH" ); + + string s = ss.str(); + char * p = (char *)malloc( s.size() * 4 ); + strcpy( p , s.c_str() ); + char *q = p; +#if defined(_WIN32) + while ( *p ) { + if ( *p == '/' ) *p = '\\'; + p++; + } +#endif + + log(1) << "classpath: " << q << endl; + + JavaVMOption * options = new JavaVMOption[4]; + options[0].optionString = q; + options[1].optionString = (char*)"-Djava.awt.headless=true"; + options[2].optionString = (char*)"-Xmx300m"; + + // Prevent JVM from using async signals internally, since on linux the pre-installed handlers for these + // signals don't seem to be respected by JNI. + options[3].optionString = (char*)"-Xrs"; + // -Xcheck:jni + + _vmArgs = new JavaVMInitArgs(); + _vmArgs->version = JNI_VERSION_1_4; + _vmArgs->options = options; + _vmArgs->nOptions = 4; + _vmArgs->ignoreUnrecognized = JNI_FALSE; + + log(1) << "loading JVM" << endl; + jint res = JNI_CreateJavaVM( &_jvm, (void**)&_mainEnv, _vmArgs ); + + if ( res ) { + log() << "using classpath: " << q << endl; + log() + << " res : " << (unsigned) res << " " + << "_jvm : " << _jvm << " " + << "_env : " << _mainEnv << " " + << endl; + problem() << "Couldn't create JVM res:" << (int) res << " terminating" << endl; + log() << "(try --nojni if you do not require that functionality)" << endl; + exit(22); + } + jassert( res == 0 ); + jassert( _jvm > 0 ); + jassert( _mainEnv > 0 ); + + _envs = new boost::thread_specific_ptr<JNIEnv>( myJNIClean ); + assert( ! _envs->get() ); + _envs->reset( _mainEnv ); + + _dbhook = findClass( "ed/db/JSHook" ); + if ( _dbhook == 0 ) { + log() << "using classpath: " << q << endl; + printException(); + } + jassert( _dbhook ); + + if ( ed ) { + jmethodID init = _mainEnv->GetStaticMethodID( _dbhook , "init" , "(Ljava/lang/String;)V" ); + jassert( init ); + _mainEnv->CallStaticVoidMethod( _dbhook , init , _getEnv()->NewStringUTF( ed ) ); + } + + _dbjni = findClass( "ed/db/DBJni" ); + jassert( _dbjni ); + + _scopeCreate = _mainEnv->GetStaticMethodID( _dbhook , "scopeCreate" , "()J" ); + _scopeInit = _mainEnv->GetStaticMethodID( _dbhook , "scopeInit" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeSetThis = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetThis" , "(JLjava/nio/ByteBuffer;)Z" ); + _scopeReset = _mainEnv->GetStaticMethodID( _dbhook , "scopeReset" , "(J)Z" ); + _scopeFree = _mainEnv->GetStaticMethodID( _dbhook , "scopeFree" , "(J)V" ); + + _scopeGetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetNumber" , "(JLjava/lang/String;)D" ); + _scopeGetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetString" , "(JLjava/lang/String;)Ljava/lang/String;" ); + _scopeGetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetBoolean" , "(JLjava/lang/String;)Z" ); + _scopeGetType = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetType" , "(JLjava/lang/String;)B" ); + _scopeGetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeGetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)I" ); + _scopeGuessObjectSize = _mainEnv->GetStaticMethodID( _dbhook , "scopeGuessObjectSize" , "(JLjava/lang/String;)J" ); + + _scopeSetNumber = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetNumber" , "(JLjava/lang/String;D)Z" ); + _scopeSetBoolean = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetBoolean" , "(JLjava/lang/String;Z)Z" ); + _scopeSetString = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetString" , "(JLjava/lang/String;Ljava/lang/String;)Z" ); + _scopeSetObject = _mainEnv->GetStaticMethodID( _dbhook , "scopeSetObject" , "(JLjava/lang/String;Ljava/nio/ByteBuffer;)Z" ); + + _functionCreate = _mainEnv->GetStaticMethodID( _dbhook , "functionCreate" , "(Ljava/lang/String;)J" ); + _invoke = _mainEnv->GetStaticMethodID( _dbhook , "invoke" , "(JJ)I" ); + + jassert( _scopeCreate ); + jassert( _scopeInit ); + jassert( _scopeSetThis ); + jassert( _scopeReset ); + jassert( _scopeFree ); + + jassert( _scopeGetNumber ); + jassert( _scopeGetString ); + jassert( _scopeGetObject ); + jassert( _scopeGetBoolean ); + jassert( _scopeGetType ); + jassert( _scopeGuessObjectSize ); + + jassert( _scopeSetNumber ); + jassert( _scopeSetBoolean ); + jassert( _scopeSetString ); + jassert( _scopeSetObject ); + + jassert( _functionCreate ); + jassert( _invoke ); + + JNINativeMethod * nativeSay = new JNINativeMethod(); + nativeSay->name = (char*)"native_say"; + nativeSay->signature = (char*)"(Ljava/nio/ByteBuffer;)V"; + nativeSay->fnPtr = (void*)java_native_say; + _mainEnv->RegisterNatives( _dbjni , nativeSay , 1 ); + + + JNINativeMethod * nativeCall = new JNINativeMethod(); + nativeCall->name = (char*)"native_call"; + nativeCall->signature = (char*)"(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I"; + nativeCall->fnPtr = (void*)java_native_call; + _mainEnv->RegisterNatives( _dbjni , nativeCall , 1 ); + + } + + JavaJSImpl::~JavaJSImpl() { + if ( _jvm ) { + _jvm->DestroyJavaVM(); + cout << "Destroying JVM" << endl; + } + } + +// scope + + jlong JavaJSImpl::scopeCreate() { + return _getEnv()->CallStaticLongMethod( _dbhook , _scopeCreate ); + } + + jboolean JavaJSImpl::scopeReset( jlong id ) { + return _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeReset ); + } + + void JavaJSImpl::scopeFree( jlong id ) { + _getEnv()->CallStaticVoidMethod( _dbhook , _scopeFree , id ); + } + +// scope setters + + int JavaJSImpl::scopeSetBoolean( jlong id , const char * field , jboolean val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetNumber( jlong id , const char * field , double val ) { + jstring fieldString = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetNumber , id , fieldString , val ); + _getEnv()->DeleteLocalRef( fieldString ); + return res; + } + + int JavaJSImpl::scopeSetString( jlong id , const char * field , const char * val ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s2 = _getEnv()->NewStringUTF( val ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetString , id , s1 , s2 ); + _getEnv()->DeleteLocalRef( s1 ); + _getEnv()->DeleteLocalRef( s2 ); + return res; + } + + int JavaJSImpl::scopeSetObject( jlong id , const char * field , const BSONObj * obj ) { + jobject bb = 0; + if ( obj ) { + bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + } + + jstring s1 = _getEnv()->NewStringUTF( field ); + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetObject , id , s1 , bb ); + _getEnv()->DeleteLocalRef( s1 ); + if ( bb ) + _getEnv()->DeleteLocalRef( bb ); + + return res; + } + + int JavaJSImpl::scopeInit( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeInit , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + + int JavaJSImpl::scopeSetThis( jlong id , const BSONObj * obj ) { + if ( ! obj ) + return 0; + + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)(obj->objdata()) , (jlong)(obj->objsize()) ); + jassert( bb ); + + int res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeSetThis , id , bb ); + _getEnv()->DeleteLocalRef( bb ); + return res; + } + +// scope getters + + char JavaJSImpl::scopeGetType( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + int res =_getEnv()->CallStaticByteMethod( _dbhook , _scopeGetType , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + double JavaJSImpl::scopeGetNumber( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + double res = _getEnv()->CallStaticDoubleMethod( _dbhook , _scopeGetNumber , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + jboolean JavaJSImpl::scopeGetBoolean( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jboolean res = _getEnv()->CallStaticBooleanMethod( _dbhook , _scopeGetBoolean , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + return res; + } + + string JavaJSImpl::scopeGetString( jlong id , const char * field ) { + jstring s1 = _getEnv()->NewStringUTF( field ); + jstring s = (jstring)_getEnv()->CallStaticObjectMethod( _dbhook , _scopeGetString , id , s1 ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( ! s ) + return ""; + + const char * c = _getEnv()->GetStringUTFChars( s , 0 ); + string retStr(c); + _getEnv()->ReleaseStringUTFChars( s , c ); + return retStr; + } + + BSONObj JavaJSImpl::scopeGetObject( jlong id , const char * field ) + { + jstring s1 = _getEnv()->NewStringUTF( field ); + int guess = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGuessObjectSize , id , _getEnv()->NewStringUTF( field ) ); + _getEnv()->DeleteLocalRef( s1 ); + + if ( guess == 0 ) + return BSONObj(); + + char * buf = (char *) malloc(guess); + jobject bb = _getEnv()->NewDirectByteBuffer( (void*)buf , guess ); + jassert( bb ); + + int len = _getEnv()->CallStaticIntMethod( _dbhook , _scopeGetObject , id , _getEnv()->NewStringUTF( field ) , bb ); + _getEnv()->DeleteLocalRef( bb ); + jassert( len > 0 && len < guess ); + + BSONObj obj(buf, true); + assert( obj.objsize() <= guess ); + return obj; + } + +// other + + jlong JavaJSImpl::functionCreate( const char * code ) { + jstring s = _getEnv()->NewStringUTF( code ); + jassert( s ); + jlong id = _getEnv()->CallStaticLongMethod( _dbhook , _functionCreate , s ); + _getEnv()->DeleteLocalRef( s ); + return id; + } + + int JavaJSImpl::invoke( jlong scope , jlong function ) { + return _getEnv()->CallStaticIntMethod( _dbhook , _invoke , scope , function ); + } + +// --- fun run method + + void JavaJSImpl::run( const char * js ) { + jclass c = findClass( "ed/js/JS" ); + jassert( c ); + + jmethodID m = _getEnv()->GetStaticMethodID( c , "eval" , "(Ljava/lang/String;)Ljava/lang/Object;" ); + jassert( m ); + + jstring s = _getEnv()->NewStringUTF( js ); + log() << _getEnv()->CallStaticObjectMethod( c , m , s ) << endl; + _getEnv()->DeleteLocalRef( s ); + } + + void JavaJSImpl::printException() { + jthrowable exc = _getEnv()->ExceptionOccurred(); + if ( exc ) { + _getEnv()->ExceptionDescribe(); + _getEnv()->ExceptionClear(); + } + + } + + JNIEnv * JavaJSImpl::_getEnv() { + JNIEnv * env = _envs->get(); + if ( env ) + return env; + + int res = _jvm->AttachCurrentThread( (void**)&env , (void*)&_vmArgs ); + if ( res ) { + out() << "ERROR javajs attachcurrentthread fails res:" << res << '\n'; + assert(false); + } + + _envs->reset( env ); + return env; + } + + Scope * JavaJSImpl::createScope(){ + return new JavaScope(); + } + + void ScriptEngine::setup(){ + if ( ! JavaJS ){ + JavaJS = new JavaJSImpl(); + globalScriptEngine = JavaJS; + } + } + + void jasserted(const char *msg, const char *file, unsigned line) { + log() << "jassert failed " << msg << " " << file << " " << line << endl; + if ( JavaJS ) JavaJS->printException(); + throw AssertionException(); + } + + + const char* findEd(const char *path) { + +#if defined(_WIN32) + + if (!path) { + path = findEd(); + } + + // @TODO check validity + + return path; +#else + + if (!path) { + return findEd(); + } + + log() << "Appserver location specified : " << path << endl; + + if (!path) { + log() << " invalid appserver location : " << path << " : terminating - prepare for bus error" << endl; + return 0; + } + + DIR *testDir = opendir(path); + + if (testDir) { + log(1) << " found directory for appserver : " << path << endl; + closedir(testDir); + return path; + } + else { + log() << " ERROR : not a directory for specified appserver location : " << path << " - prepare for bus error" << endl; + return null; + } +#endif + } + + const char * findEd() { + +#if defined(_WIN32) + log() << "Appserver location will be WIN32 default : c:/l/ed/" << endl; + return "c:/l/ed"; +#else + + static list<const char*> possibleEdDirs; + if ( ! possibleEdDirs.size() ) { + possibleEdDirs.push_back( "../../ed/ed/" ); // this one for dwight dev box + possibleEdDirs.push_back( "../ed/" ); + possibleEdDirs.push_back( "../../ed/" ); + possibleEdDirs.push_back( "../babble/" ); + possibleEdDirs.push_back( "../../babble/" ); + } + + for ( list<const char*>::iterator i = possibleEdDirs.begin() ; i != possibleEdDirs.end(); i++ ) { + const char * temp = *i; + DIR * test = opendir( temp ); + if ( ! test ) + continue; + + closedir( test ); + log(1) << "found directory for appserver : " << temp << endl; + return temp; + } + + return 0; +#endif + }; + + const string findJars() { + + static list<string> possible; + if ( ! possible.size() ) { + possible.push_back( "./" ); + possible.push_back( "../" ); + + log(2) << "dbExecCommand: " << dbExecCommand << endl; + + string dbDir = dbExecCommand; +#ifdef WIN32 + if ( dbDir.find( "\\" ) != string::npos ){ + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "\\" ) ); + } + else { + dbDir = "."; + } +#else + if ( dbDir.find( "/" ) != string::npos ){ + dbDir = dbDir.substr( 0 , dbDir.find_last_of( "/" ) ); + } + else { + bool found = false; + + if ( getenv( "PATH" ) ){ + string s = getenv( "PATH" ); + s += ":"; + pcrecpp::StringPiece input( s ); + string dir; + pcrecpp::RE re("(.*?):"); + while ( re.Consume( &input, &dir ) ){ + string test = dir + "/" + dbExecCommand; + if ( boost::filesystem::exists( test ) ){ + while ( boost::filesystem::symbolic_link_exists( test ) ){ + char tmp[2048]; + int len = readlink( test.c_str() , tmp , 2048 ); + tmp[len] = 0; + log(5) << " symlink " << test << " -->> " << tmp << endl; + test = tmp; + + dir = test.substr( 0 , test.rfind( "/" ) ); + } + dbDir = dir; + found = true; + break; + } + } + } + + if ( ! found ) + dbDir = "."; + } +#endif + + log(2) << "dbDir [" << dbDir << "]" << endl; + possible.push_back( ( dbDir + "/../lib/mongo/" )); + possible.push_back( ( dbDir + "/../lib64/mongo/" )); + possible.push_back( ( dbDir + "/../lib32/mongo/" )); + possible.push_back( ( dbDir + "/" )); + possible.push_back( ( dbDir + "/lib64/mongo/" )); + possible.push_back( ( dbDir + "/lib32/mongo/" )); + } + + for ( list<string>::iterator i = possible.begin() ; i != possible.end(); i++ ) { + const string temp = *i; + const string jarDir = ((string)temp) + "jars/"; + + log(5) << "possible jarDir [" << jarDir << "]" << endl; + + path p(jarDir ); + if ( ! boost::filesystem::exists( p) ) + continue; + + log(1) << "found directory for jars : " << jarDir << endl; + return temp; + } + + problem() << "ERROR : can't find directory for jars - terminating" << endl; + exit(44); + return 0; + + }; + + +// --- + + JNIEXPORT void JNICALL java_native_say(JNIEnv * env , jclass, jobject outBuffer ) { + JNI_DEBUG( "native say called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + assert( ! out.doIFreeIt() ); + curNs = 0; + } + + JNIEXPORT jint JNICALL java_native_call(JNIEnv * env , jclass, jobject outBuffer , jobject inBuffer ) { + JNI_DEBUG( "native call called!" ); + + Message out( env->GetDirectBufferAddress( outBuffer ) , false ); + Message in; + + jniCallback( out , in ); + curNs = 0; + + JNI_DEBUG( "in.data : " << in.data ); + if ( in.data && in.data->len > 0 ) { + JNI_DEBUG( "copying data of len :" << in.data->len ); + assert( env->GetDirectBufferCapacity( inBuffer ) >= in.data->len ); + memcpy( env->GetDirectBufferAddress( inBuffer ) , in.data , in.data->len ); + + assert( ! out.doIFreeIt() ); + assert( in.doIFreeIt() ); + return in.data->len; + } + + return 0; + } + +// ---- + + void JavaJSImpl::runTest() { + + const int debug = 0; + + JavaJSImpl& JavaJS = *mongo::JavaJS; + + jlong scope = JavaJS.scopeCreate(); + jassert( scope ); + if ( debug ) out() << "got scope" << endl; + + + jlong func1 = JavaJS.functionCreate( "foo = 5.6; bar = \"eliot\"; abc = { foo : 517 }; " ); + jassert( ! JavaJS.invoke( scope , func1 ) ); + + + if ( debug ) out() << "func3 start" << endl; + jlong func3 = JavaJS.functionCreate( "function(){ z = true; } " ); + jassert( func3 ); + jassert( ! JavaJS.invoke( scope , func3 ) ); + jassert( JavaJS.scopeGetBoolean( scope , "z" ) ); + if ( debug ) out() << "func3 done" << endl; + + if ( debug ) out() << "going to get object" << endl; + BSONObj obj = JavaJS.scopeGetObject( scope , "abc" ); + if ( debug ) out() << "done getting object" << endl; + + if ( debug ) { + out() << "obj : " << obj.toString() << endl; + } + + { + time_t start = time(0); + for ( int i=0; i<5000; i++ ) { + JavaJS.scopeSetObject( scope , "obj" , &obj ); + } + time_t end = time(0); + + if ( debug ) + out() << "time : " << (unsigned) ( end - start ) << endl; + } + + if ( debug ) out() << "func4 start" << endl; + JavaJS.scopeSetObject( scope , "obj" , &obj ); + if ( debug ) out() << "\t here 1" << endl; + jlong func4 = JavaJS.functionCreate( "tojson( obj );" ); + if ( debug ) out() << "\t here 2" << endl; + jassert( ! JavaJS.invoke( scope , func4 ) ); + if ( debug ) out() << "func4 end" << endl; + + if ( debug ) out() << "func5 start" << endl; + jassert( JavaJS.scopeSetObject( scope , "c" , &obj ) ); + jlong func5 = JavaJS.functionCreate( "assert.eq( 517 , c.foo );" ); + jassert( func5 ); + jassert( ! JavaJS.invoke( scope , func5 ) ); + if ( debug ) out() << "func5 done" << endl; + + if ( debug ) out() << "func6 start" << endl; + for ( int i=0; i<100; i++ ) { + double val = i + 5; + JavaJS.scopeSetNumber( scope , "zzz" , val ); + jlong func6 = JavaJS.functionCreate( " xxx = zzz; " ); + jassert( ! JavaJS.invoke( scope , func6 ) ); + double n = JavaJS.scopeGetNumber( scope , "xxx" ); + jassert( val == n ); + } + if ( debug ) out() << "func6 done" << endl; + + jlong func7 = JavaJS.functionCreate( "return 11;" ); + jassert( ! JavaJS.invoke( scope , func7 ) ); + assert( 11 == JavaJS.scopeGetNumber( scope , "return" ) ); + + scope = JavaJS.scopeCreate(); + jlong func8 = JavaJS.functionCreate( "function(){ return 12; }" ); + jassert( ! JavaJS.invoke( scope , func8 ) ); + assert( 12 == JavaJS.scopeGetNumber( scope , "return" ) ); + + } + +#endif + +} // namespace mongo diff --git a/scripting/engine_java.h b/scripting/engine_java.h new file mode 100644 index 0000000..ae11cc1 --- /dev/null +++ b/scripting/engine_java.h @@ -0,0 +1,224 @@ +// engine_java.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains code to call into java (into the 10gen sandbox) from inside the database */ + +#pragma once + +#include "../stdafx.h" + +#include <jni.h> +#include <boost/thread/tss.hpp> +#include <errno.h> +#include <sys/types.h> + +#if !defined(_WIN32) +#include <dirent.h> +#endif + +#include "../db/jsobj.h" + +#include "engine.h" + +namespace mongo { + + void jasserted(const char *msg, const char *file, unsigned line); +#define jassert(_Expression) if ( ! ( _Expression ) ){ jasserted(#_Expression, __FILE__, __LINE__); } + + const char * findEd(); + const char * findEd(const char *); + const string findJars(); + + class BSONObj; + + class JavaJSImpl : public ScriptEngine { + public: + JavaJSImpl(const char * = 0); + ~JavaJSImpl(); + + jlong scopeCreate(); + int scopeInit( jlong id , const BSONObj * obj ); + int scopeSetThis( jlong id , const BSONObj * obj ); + jboolean scopeReset( jlong id ); + void scopeFree( jlong id ); + + double scopeGetNumber( jlong id , const char * field ); + string scopeGetString( jlong id , const char * field ); + jboolean scopeGetBoolean( jlong id , const char * field ); + BSONObj scopeGetObject( jlong id , const char * field ); + char scopeGetType( jlong id , const char * field ); + + int scopeSetNumber( jlong id , const char * field , double val ); + int scopeSetString( jlong id , const char * field , const char * val ); + int scopeSetObject( jlong id , const char * field , const BSONObj * obj ); + int scopeSetBoolean( jlong id , const char * field , jboolean val ); + + jlong functionCreate( const char * code ); + + /* return values: + public static final int NO_SCOPE = -1; + public static final int NO_FUNCTION = -2; + public static final int INVOKE_ERROR = -3; + public static final int INVOKE_SUCCESS = 0; + */ + int invoke( jlong scope , jlong function ); + + void printException(); + + void run( const char * js ); + + void detach( JNIEnv * env ) { + _jvm->DetachCurrentThread(); + } + + Scope * createScope(); + + void runTest(); + private: + + jobject create( const char * name ) { + jclass c = findClass( name ); + if ( ! c ) + return 0; + + jmethodID cons = _getEnv()->GetMethodID( c , "<init>" , "()V" ); + if ( ! cons ) + return 0; + + return _getEnv()->NewObject( c , cons ); + } + + jclass findClass( const char * name ) { + return _getEnv()->FindClass( name ); + } + + + private: + + JNIEnv * _getEnv(); + + JavaVM * _jvm; + JNIEnv * _mainEnv; + JavaVMInitArgs * _vmArgs; + + boost::thread_specific_ptr<JNIEnv> * _envs; + + jclass _dbhook; + jclass _dbjni; + + jmethodID _scopeCreate; + jmethodID _scopeInit; + jmethodID _scopeSetThis; + jmethodID _scopeReset; + jmethodID _scopeFree; + + jmethodID _scopeGetNumber; + jmethodID _scopeGetString; + jmethodID _scopeGetObject; + jmethodID _scopeGetBoolean; + jmethodID _scopeGuessObjectSize; + jmethodID _scopeGetType; + + jmethodID _scopeSetNumber; + jmethodID _scopeSetString; + jmethodID _scopeSetObject; + jmethodID _scopeSetBoolean; + + jmethodID _functionCreate; + + jmethodID _invoke; + + }; + + extern JavaJSImpl *JavaJS; + +// a javascript "scope" + class JavaScope : public Scope { + public: + JavaScope() { + s = JavaJS->scopeCreate(); + } + virtual ~JavaScope() { + JavaJS->scopeFree(s); + s = 0; + } + void reset() { + JavaJS->scopeReset(s); + } + + void init( BSONObj * o ) { + JavaJS->scopeInit( s , o ); + } + + void localConnect( const char * dbName ){ + setString("$client", dbName ); + } + + double getNumber(const char *field) { + return JavaJS->scopeGetNumber(s,field); + } + string getString(const char *field) { + return JavaJS->scopeGetString(s,field); + } + bool getBoolean(const char *field) { + return JavaJS->scopeGetBoolean(s,field); + } + BSONObj getObject(const char *field ) { + return JavaJS->scopeGetObject(s,field); + } + int type(const char *field ) { + return JavaJS->scopeGetType(s,field); + } + + void setThis( const BSONObj * obj ){ + JavaJS->scopeSetThis( s , obj ); + } + + void setNumber(const char *field, double val ) { + JavaJS->scopeSetNumber(s,field,val); + } + void setString(const char *field, const char * val ) { + JavaJS->scopeSetString(s,field,val); + } + void setObject(const char *field, const BSONObj& obj , bool readOnly ) { + uassert( 10211 , "only readOnly setObject supported in java" , readOnly ); + JavaJS->scopeSetObject(s,field,&obj); + } + void setBoolean(const char *field, bool val ) { + JavaJS->scopeSetBoolean(s,field,val); + } + + ScriptingFunction createFunction( const char * code ){ + return JavaJS->functionCreate( code ); + } + + int invoke( ScriptingFunction function , const BSONObj& args ){ + setObject( "args" , args , true ); + return JavaJS->invoke(s,function); + } + + string getError(){ + return getString( "error" ); + } + + jlong s; + }; + + JNIEXPORT void JNICALL java_native_say(JNIEnv *, jclass, jobject outBuffer ); + JNIEXPORT jint JNICALL java_native_call(JNIEnv *, jclass, jobject outBuffer , jobject inBuffer ); + +} // namespace mongo diff --git a/scripting/engine_none.cpp b/scripting/engine_none.cpp new file mode 100644 index 0000000..2320d0e --- /dev/null +++ b/scripting/engine_none.cpp @@ -0,0 +1,24 @@ +// engine_none.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "engine.h" + +namespace mongo { + void ScriptEngine::setup(){ + // noop + } +} diff --git a/scripting/engine_spidermonkey.cpp b/scripting/engine_spidermonkey.cpp new file mode 100644 index 0000000..d75a734 --- /dev/null +++ b/scripting/engine_spidermonkey.cpp @@ -0,0 +1,1464 @@ +// engine_spidermonkey.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "engine_spidermonkey.h" + +#include "../client/dbclient.h" + +#ifndef _WIN32 +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert xassert +#endif + +#define smuassert( cx , msg , val ) \ + if ( ! ( val ) ){ \ + JS_ReportError( cx , msg ); \ + return JS_FALSE; \ + } + +namespace mongo { + + string trim( string s ){ + while ( s.size() && isspace( s[0] ) ) + s = s.substr( 1 ); + + while ( s.size() && isspace( s[s.size()-1] ) ) + s = s.substr( 0 , s.size() - 1 ); + + return s; + } + + boost::thread_specific_ptr<SMScope> currentScope( dontDeleteScope ); + boost::recursive_mutex smmutex; +#define smlock recursive_boostlock ___lk( smmutex ); + +#define GETHOLDER(x,o) ((BSONHolder*)JS_GetPrivate( x , o )) + + class BSONFieldIterator; + + class BSONHolder { + public: + + BSONHolder( BSONObj obj ){ + _obj = obj.getOwned(); + _inResolve = false; + _modified = false; + _magic = 17; + } + + ~BSONHolder(){ + _magic = 18; + } + + void check(){ + uassert( 10212 , "holder magic value is wrong" , _magic == 17 && _obj.isValid() ); + } + + BSONFieldIterator * it(); + + BSONObj _obj; + bool _inResolve; + char _magic; + list<string> _extra; + set<string> _removed; + bool _modified; + }; + + class BSONFieldIterator { + public: + + BSONFieldIterator( BSONHolder * holder ){ + + set<string> added; + + BSONObjIterator it( holder->_obj ); + while ( it.more() ){ + BSONElement e = it.next(); + if ( holder->_removed.count( e.fieldName() ) ) + continue; + _names.push_back( e.fieldName() ); + added.insert( e.fieldName() ); + } + + for ( list<string>::iterator i = holder->_extra.begin(); i != holder->_extra.end(); i++ ){ + if ( ! added.count( *i ) ) + _names.push_back( *i ); + } + + _it = _names.begin(); + } + + bool more(){ + return _it != _names.end(); + } + + string next(){ + string s = *_it; + _it++; + return s; + } + + private: + list<string> _names; + list<string>::iterator _it; + }; + + BSONFieldIterator * BSONHolder::it(){ + return new BSONFieldIterator( this ); + } + + + class Convertor : boost::noncopyable { + public: + Convertor( JSContext * cx ){ + _context = cx; + } + + string toString( JSString * so ){ + jschar * s = JS_GetStringChars( so ); + size_t srclen = JS_GetStringLength( so ); + if( srclen == 0 ) + return ""; + + size_t len = srclen * 6; // we only need *3, but see note on len below + char * dst = (char*)malloc( len ); + + len /= 2; + // doc re weird JS_EncodeCharacters api claims len expected in 16bit + // units, but experiments suggest 8bit units expected. We allocate + // enough memory that either will work. + + assert( JS_EncodeCharacters( _context , s , srclen , dst , &len) ); + + string ss( dst , len ); + free( dst ); + if ( !JS_CStringsAreUTF8() ) + for( string::const_iterator i = ss.begin(); i != ss.end(); ++i ) + uassert( 10213 , "non ascii character detected", (unsigned char)(*i) <= 127 ); + return ss; + } + + string toString( jsval v ){ + return toString( JS_ValueToString( _context , v ) ); + } + + double toNumber( jsval v ){ + double d; + uassert( 10214 , "not a number" , JS_ValueToNumber( _context , v , &d ) ); + return d; + } + + bool toBoolean( jsval v ){ + JSBool b; + assert( JS_ValueToBoolean( _context, v , &b ) ); + return b; + } + + OID toOID( jsval v ){ + JSContext * cx = _context; + assert( JSVAL_IS_OID( v ) ); + + JSObject * o = JSVAL_TO_OBJECT( v ); + OID oid; + oid.init( getString( o , "str" ) ); + return oid; + } + + BSONObj toObject( JSObject * o ){ + if ( ! o ) + return BSONObj(); + + if ( JS_InstanceOf( _context , o , &bson_ro_class , 0 ) ){ + BSONHolder * holder = GETHOLDER( _context , o ); + assert( holder ); + return holder->_obj.getOwned(); + } + + BSONObj orig; + if ( JS_InstanceOf( _context , o , &bson_class , 0 ) ){ + BSONHolder * holder = GETHOLDER(_context,o); + assert( holder ); + if ( ! holder->_modified ){ + return holder->_obj; + } + orig = holder->_obj; + } + + BSONObjBuilder b; + + if ( ! appendSpecialDBObject( this , b , "value" , OBJECT_TO_JSVAL( o ) , o ) ){ + + jsval theid = getProperty( o , "_id" ); + if ( ! JSVAL_IS_VOID( theid ) ){ + append( b , "_id" , theid ); + } + + JSIdArray * properties = JS_Enumerate( _context , o ); + assert( properties ); + + for ( jsint i=0; i<properties->length; i++ ){ + jsid id = properties->vector[i]; + jsval nameval; + assert( JS_IdToValue( _context ,id , &nameval ) ); + string name = toString( nameval ); + if ( name == "_id" ) + continue; + + append( b , name , getProperty( o , name.c_str() ) , orig[name].type() ); + } + + JS_DestroyIdArray( _context , properties ); + } + + return b.obj(); + } + + BSONObj toObject( jsval v ){ + if ( JSVAL_IS_NULL( v ) || + JSVAL_IS_VOID( v ) ) + return BSONObj(); + + uassert( 10215 , "not an object" , JSVAL_IS_OBJECT( v ) ); + return toObject( JSVAL_TO_OBJECT( v ) ); + } + + string getFunctionCode( JSFunction * func ){ + return toString( JS_DecompileFunction( _context , func , 0 ) ); + } + + string getFunctionCode( jsval v ){ + uassert( 10216 , "not a function" , JS_TypeOfValue( _context , v ) == JSTYPE_FUNCTION ); + return getFunctionCode( JS_ValueToFunction( _context , v ) ); + } + + void appendRegex( BSONObjBuilder& b , const string& name , string s ){ + assert( s[0] == '/' ); + s = s.substr(1); + string::size_type end = s.rfind( '/' ); + b.appendRegex( name.c_str() , s.substr( 0 , end ).c_str() , s.substr( end + 1 ).c_str() ); + } + + void append( BSONObjBuilder& b , string name , jsval val , BSONType oldType = EOO ){ + //cout << "name: " << name << "\t" << typeString( val ) << " oldType: " << oldType << endl; + switch ( JS_TypeOfValue( _context , val ) ){ + + case JSTYPE_VOID: b.appendUndefined( name.c_str() ); break; + case JSTYPE_NULL: b.appendNull( name.c_str() ); break; + + case JSTYPE_NUMBER: { + double d = toNumber( val ); + if ( oldType == NumberInt && ((int)d) == d ) + b.append( name.c_str() , (int)d ); + else + b.append( name.c_str() , d ); + break; + } + case JSTYPE_STRING: b.append( name.c_str() , toString( val ) ); break; + case JSTYPE_BOOLEAN: b.appendBool( name.c_str() , toBoolean( val ) ); break; + + case JSTYPE_OBJECT: { + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( ! o || o == JSVAL_NULL ){ + b.appendNull( name.c_str() ); + } + else if ( ! appendSpecialDBObject( this , b , name , val , o ) ){ + BSONObj sub = toObject( o ); + if ( JS_IsArrayObject( _context , o ) ){ + b.appendArray( name.c_str() , sub ); + } + else { + b.append( name.c_str() , sub ); + } + } + break; + } + + case JSTYPE_FUNCTION: { + string s = toString(val); + if ( s[0] == '/' ){ + appendRegex( b , name , s ); + } + else { + b.appendCode( name.c_str() , getFunctionCode( val ).c_str() ); + } + break; + } + + default: uassert( 10217 , (string)"can't append field. name:" + name + " type: " + typeString( val ) , 0 ); + } + } + + // ---------- to spider monkey --------- + + bool hasFunctionIdentifier( const string& code ){ + if ( code.size() < 9 || code.find( "function" ) != 0 ) + return false; + + return code[8] == ' ' || code[8] == '('; + } + + bool isSimpleStatement( const string& code ){ + if ( code.find( "return" ) != string::npos ) + return false; + + if ( code.find( ";" ) != string::npos && + code.find( ";" ) != code.rfind( ";" ) ) + return false; + + if ( code.find( "for(" ) != string::npos || + code.find( "for (" ) != string::npos || + code.find( "while (" ) != string::npos || + code.find( "while(" ) != string::npos ) + return false; + + return true; + } + + void addRoot( JSFunction * f , const char * name ); + + JSFunction * compileFunction( const char * code, JSObject * assoc = 0 ){ + const char * gcName = "unknown"; + JSFunction * f = _compileFunction( code , assoc , gcName ); + //addRoot( f , gcName ); + return f; + } + + JSFunction * _compileFunction( const char * raw , JSObject * assoc , const char *& gcName ){ + if ( ! assoc ) + assoc = JS_GetGlobalObject( _context ); + + while (isspace(*raw)) { + raw++; + } + + stringstream fname; + fname << "cf_"; + static int fnum = 1; + fname << "_" << fnum++ << "_"; + + + if ( ! hasFunctionIdentifier( raw ) ){ + string s = raw; + if ( isSimpleStatement( s ) ){ + s = "return " + s; + } + gcName = "cf anon"; + fname << "anon"; + return JS_CompileFunction( _context , assoc , fname.str().c_str() , 0 , 0 , s.c_str() , strlen( s.c_str() ) , "nofile_a" , 0 ); + } + + string code = raw; + + size_t start = code.find( '(' ); + assert( start != string::npos ); + + fname << "_f_" << trim( code.substr( 9 , start - 9 ) ); + + code = code.substr( start + 1 ); + size_t end = code.find( ')' ); + assert( end != string::npos ); + + string paramString = trim( code.substr( 0 , end ) ); + code = code.substr( end + 1 ); + + vector<string> params; + while ( paramString.size() ){ + size_t c = paramString.find( ',' ); + if ( c == string::npos ){ + params.push_back( paramString ); + break; + } + params.push_back( trim( paramString.substr( 0 , c ) ) ); + paramString = trim( paramString.substr( c + 1 ) ); + paramString = trim( paramString ); + } + + const char ** paramArray = new const char*[params.size()]; + for ( size_t i=0; i<params.size(); i++ ) + paramArray[i] = params[i].c_str(); + + JSFunction * func = JS_CompileFunction( _context , assoc , fname.str().c_str() , params.size() , paramArray , code.c_str() , strlen( code.c_str() ) , "nofile_b" , 0 ); + delete paramArray; + if ( ! func ){ + cout << "compile failed for: " << raw << endl; + return 0; + } + gcName = "cf normal"; + return func; + } + + + jsval toval( double d ){ + jsval val; + assert( JS_NewNumberValue( _context, d , &val ) ); + return val; + } + + jsval toval( const char * c ){ + JSString * s = JS_NewStringCopyZ( _context , c ); + if ( s ) + return STRING_TO_JSVAL( s ); + + // possibly unicode, try manual + + size_t len = strlen( c ); + size_t dstlen = len * 4; + jschar * dst = (jschar*)malloc( dstlen ); + + JSBool res = JS_DecodeBytes( _context , c , len , dst, &dstlen ); + if ( res ){ + s = JS_NewUCStringCopyN( _context , dst , dstlen ); + } + + free( dst ); + + if ( ! res ){ + cout << "decode failed. probably invalid utf-8 string [" << c << "]" << endl; + jsval v; + if ( JS_GetPendingException( _context , &v ) ) + cout << "\t why: " << toString( v ) << endl; + throw UserException( 9006 , "invalid utf8" ); + } + + assert( s ); + return STRING_TO_JSVAL( s ); + } + + JSObject * toJSObject( const BSONObj * obj , bool readOnly=false ){ + static string ref = "$ref"; + if ( ref == obj->firstElement().fieldName() ){ + JSObject * o = JS_NewObject( _context , &dbref_class , NULL, NULL); + assert( o ); + setProperty( o , "$ref" , toval( obj->firstElement() ) ); + setProperty( o , "$id" , toval( (*obj)["$id"] ) ); + return o; + } + JSObject * o = JS_NewObject( _context , readOnly ? &bson_ro_class : &bson_class , NULL, NULL); + assert( o ); + assert( JS_SetPrivate( _context , o , (void*)(new BSONHolder( obj->getOwned() ) ) ) ); + return o; + } + + jsval toval( const BSONObj* obj , bool readOnly=false ){ + JSObject * o = toJSObject( obj , readOnly ); + return OBJECT_TO_JSVAL( o ); + } + + jsval toval( const BSONElement& e ){ + + switch( e.type() ){ + case EOO: + case jstNULL: + case Undefined: + return JSVAL_NULL; + case NumberDouble: + case NumberInt: + case NumberLong: + return toval( e.number() ); + case Symbol: // TODO: should we make a special class for this + case String: + return toval( e.valuestr() ); + case Bool: + return e.boolean() ? JSVAL_TRUE : JSVAL_FALSE; + case Object:{ + BSONObj embed = e.embeddedObject().getOwned(); + return toval( &embed ); + } + case Array:{ + + BSONObj embed = e.embeddedObject().getOwned(); + + if ( embed.isEmpty() ){ + return OBJECT_TO_JSVAL( JS_NewArrayObject( _context , 0 , 0 ) ); + } + + int n = embed.nFields(); + + JSObject * array = JS_NewArrayObject( _context , n , 0 ); + assert( array ); + + jsval myarray = OBJECT_TO_JSVAL( array ); + + for ( int i=0; i<n; i++ ){ + jsval v = toval( embed[i] ); + assert( JS_SetElement( _context , array , i , &v ) ); + } + + return myarray; + } + case jstOID:{ + OID oid = e.__oid(); + JSObject * o = JS_NewObject( _context , &object_id_class , 0 , 0 ); + setProperty( o , "str" , toval( oid.str().c_str() ) ); + return OBJECT_TO_JSVAL( o ); + } + case RegEx:{ + const char * flags = e.regexFlags(); + uintN flagNumber = 0; + while ( *flags ){ + switch ( *flags ){ + case 'g': flagNumber |= JSREG_GLOB; break; + case 'i': flagNumber |= JSREG_FOLD; break; + case 'm': flagNumber |= JSREG_MULTILINE; break; + //case 'y': flagNumber |= JSREG_STICKY; break; + + default: + log() << "warning: unknown regex flag:" << *flags << endl; + } + flags++; + } + + JSObject * r = JS_NewRegExpObject( _context , (char*)e.regex() , strlen( e.regex() ) , flagNumber ); + assert( r ); + return OBJECT_TO_JSVAL( r ); + } + case Code:{ + JSFunction * func = compileFunction( e.valuestr() ); + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + } + case CodeWScope:{ + JSFunction * func = compileFunction( e.codeWScopeCode() ); + + BSONObj extraScope = e.codeWScopeObject(); + if ( ! extraScope.isEmpty() ){ + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + } + + return OBJECT_TO_JSVAL( JS_GetFunctionObject( func ) ); + } + case Date: + return OBJECT_TO_JSVAL( js_NewDateObjectMsec( _context , (jsdouble) e.date().millis ) ); + + case MinKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &minkey_class , 0 , 0 ) ); + + case MaxKey: + return OBJECT_TO_JSVAL( JS_NewObject( _context , &maxkey_class , 0 , 0 ) ); + + case Timestamp: { + JSObject * o = JS_NewObject( _context , ×tamp_class , 0 , 0 ); + setProperty( o , "t" , toval( (double)(e.timestampTime()) ) ); + setProperty( o , "i" , toval( (double)(e.timestampInc()) ) ); + return OBJECT_TO_JSVAL( o ); + } + + case DBRef: { + JSObject * o = JS_NewObject( _context , &dbpointer_class , 0 , 0 ); + setProperty( o , "ns" , toval( e.dbrefNS() ) ); + + JSObject * oid = JS_NewObject( _context , &object_id_class , 0 , 0 ); + setProperty( oid , "str" , toval( e.dbrefOID().str().c_str() ) ); + + setProperty( o , "id" , OBJECT_TO_JSVAL( oid ) ); + return OBJECT_TO_JSVAL( o ); + } + case BinData:{ + JSObject * o = JS_NewObject( _context , &bindata_class , 0 , 0 ); + int len; + void * data = (void*)e.binData( len ); + assert( JS_SetPrivate( _context , o , data ) ); + + setProperty( o , "len" , toval( len ) ); + setProperty( o , "type" , toval( (int)e.binDataType() ) ); + return OBJECT_TO_JSVAL( o ); + } + } + + cout << "toval: unknown type: " << e.type() << endl; + uassert( 10218 , "not done: toval" , 0 ); + return 0; + } + + // ------- object helpers ------ + + JSObject * getJSObject( JSObject * o , const char * name ){ + jsval v; + assert( JS_GetProperty( _context , o , name , &v ) ); + return JSVAL_TO_OBJECT( v ); + } + + JSObject * getGlobalObject( const char * name ){ + return getJSObject( JS_GetGlobalObject( _context ) , name ); + } + + JSObject * getGlobalPrototype( const char * name ){ + return getJSObject( getGlobalObject( name ) , "prototype" ); + } + + bool hasProperty( JSObject * o , const char * name ){ + JSBool res; + assert( JS_HasProperty( _context , o , name , & res ) ); + return res; + } + + jsval getProperty( JSObject * o , const char * field ){ + uassert( 10219 , "object passed to getPropery is null" , o ); + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + return v; + } + + void setProperty( JSObject * o , const char * field , jsval v ){ + assert( JS_SetProperty( _context , o , field , &v ) ); + } + + string typeString( jsval v ){ + JSType t = JS_TypeOfValue( _context , v ); + return JS_GetTypeName( _context , t ); + } + + bool getBoolean( JSObject * o , const char * field ){ + return toBoolean( getProperty( o , field ) ); + } + + double getNumber( JSObject * o , const char * field ){ + return toNumber( getProperty( o , field ) ); + } + + string getString( JSObject * o , const char * field ){ + return toString( getProperty( o , field ) ); + } + + JSClass * getClass( JSObject * o , const char * field ){ + jsval v; + assert( JS_GetProperty( _context , o , field , &v ) ); + if ( ! JSVAL_IS_OBJECT( v ) ) + return 0; + return JS_GET_CLASS( _context , JSVAL_TO_OBJECT( v ) ); + } + + JSContext * _context; + + + }; + + + void bson_finalize( JSContext * cx , JSObject * obj ){ + BSONHolder * o = GETHOLDER( cx , obj ); + if ( o ){ + delete o; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool bson_enumerate( JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp ){ + + BSONHolder * o = GETHOLDER( cx , obj ); + + if ( enum_op == JSENUMERATE_INIT ){ + if ( o ){ + BSONFieldIterator * it = o->it(); + *statep = PRIVATE_TO_JSVAL( it ); + } + else { + *statep = 0; + } + if ( idp ) + *idp = JSVAL_ZERO; + return JS_TRUE; + } + + BSONFieldIterator * it = (BSONFieldIterator*)JSVAL_TO_PRIVATE( *statep ); + if ( ! it ){ + *statep = 0; + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_NEXT ){ + if ( it->more() ){ + string name = it->next(); + Convertor c(cx); + assert( JS_ValueToId( cx , c.toval( name.c_str() ) , idp ) ); + } + else { + delete it; + *statep = 0; + } + return JS_TRUE; + } + + if ( enum_op == JSENUMERATE_DESTROY ){ + if ( it ) + delete it; + return JS_TRUE; + } + + uassert( 10220 , "don't know what to do with this op" , 0 ); + return JS_FALSE; + } + + JSBool noaccess( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // in init code still + return JS_TRUE; + } + if ( holder->_inResolve ) + return JS_TRUE; + JS_ReportError( cx , "doing write op on read only operation" ); + return JS_FALSE; + } + + JSClass bson_ro_class = { + "bson_ro_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + noaccess, noaccess, JS_PropertyStub, noaccess, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool bson_cons( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + cerr << "bson_cons : shouldn't be here!" << endl; + JS_ReportError( cx , "can't construct bson object" ); + return JS_FALSE; + } + + JSFunctionSpec bson_functions[] = { + { 0 } + }; + + JSBool bson_add_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // static init + return JS_TRUE; + } + if ( ! holder->_inResolve ){ + Convertor c(cx); + string name = c.toString( idval ); + if ( holder->_obj[name].eoo() ){ + holder->_extra.push_back( name ); + } + holder->_modified = true; + } + return JS_TRUE; + } + + + JSBool mark_modified( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.erase( c.toString( idval ) ); + return JS_TRUE; + } + + JSBool mark_modified_remove( JSContext *cx, JSObject *obj, jsval idval, jsval *vp){ + Convertor c(cx); + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( holder->_inResolve ) + return JS_TRUE; + holder->_modified = true; + holder->_removed.insert( c.toString( idval ) ); + return JS_TRUE; + } + + JSClass bson_class = { + "bson_object" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE , + bson_add_prop, mark_modified_remove, JS_PropertyStub, mark_modified, + (JSEnumerateOp)bson_enumerate, (JSResolveOp)(&resolveBSONField) , JS_ConvertStub, bson_finalize , + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + static JSClass global_class = { + "global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // --- global helpers --- + + JSBool native_print( JSContext * cx , JSObject * obj , uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + for ( uintN i=0; i<argc; i++ ){ + if ( i > 0 ) + cout << " "; + cout << c.toString( argv[i] ); + } + cout << endl; + return JS_TRUE; + } + + JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + Convertor c(cx); + + NativeFunction func = (NativeFunction)((long long)c.getNumber( obj , "x" ) ); + assert( func ); + + BSONObj a; + if ( argc > 0 ){ + BSONObjBuilder args; + for ( uintN i=0; i<argc; i++ ){ + c.append( args , args.numStr( i ) , argv[i] ); + } + + a = args.obj(); + } + BSONObj out = func( a ); + + if ( out.isEmpty() ){ + *rval = JSVAL_VOID; + } + else { + *rval = c.toval( out.firstElement() ); + } + + return JS_TRUE; + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ); + + JSBool native_gc( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + JS_GC( cx ); + return JS_TRUE; + } + + JSFunctionSpec globalHelpers[] = { + { "print" , &native_print , 0 , 0 , 0 } , + { "nativeHelper" , &native_helper , 1 , 0 , 0 } , + { "load" , &native_load , 1 , 0 , 0 } , + { "gc" , &native_gc , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // ----END global helpers ---- + + // Object helpers + + JSBool bson_get_size(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + if ( argc != 1 || !JSVAL_IS_OBJECT( argv[ 0 ] ) ) { + JS_ReportError( cx , "bsonsize requires one valid object" ); + return JS_FALSE; + } + + BSONHolder * o = GETHOLDER( cx , JSVAL_TO_OBJECT( argv[ 0 ] ) ); + double size = 0; + if ( o ){ + size = o->_obj.objsize(); + } + Convertor c(cx); + *rval = c.toval( size ); + return JS_TRUE; + } + + JSFunctionSpec objectHelpers[] = { + { "bsonsize" , &bson_get_size , 1 , 0 , 0 } , + { 0 , 0 , 0 , 0 , 0 } + }; + + // end Object helpers + + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + assert( JS_EnterLocalRootScope( cx ) ); + Convertor c( cx ); + + BSONHolder * holder = GETHOLDER( cx , obj ); + if ( ! holder ){ + // static init + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + holder->check(); + + string s = c.toString( id ); + + BSONElement e = holder->_obj[ s.c_str() ]; + + if ( e.type() == EOO || holder->_removed.count( s ) ){ + *objp = 0; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + jsval val = c.toval( e ); + + assert( ! holder->_inResolve ); + holder->_inResolve = true; + assert( JS_SetProperty( cx , obj , s.c_str() , &val ) ); + holder->_inResolve = false; + + if ( val != JSVAL_NULL && val != JSVAL_VOID && JSVAL_IS_OBJECT( val ) ){ + // TODO: this is a hack to get around sub objects being modified + JSObject * oo = JSVAL_TO_OBJECT( val ); + if ( JS_InstanceOf( cx , oo , &bson_class , 0 ) || + JS_IsArrayObject( cx , oo ) ){ + holder->_modified = true; + } + } + + *objp = obj; + JS_LeaveLocalRootScope( cx ); + return JS_TRUE; + } + + + class SMScope; + + class SMEngine : public ScriptEngine { + public: + + SMEngine(){ +#ifdef SM18 + JS_SetCStringsAreUTF8(); +#endif + + _runtime = JS_NewRuntime(8L * 1024L * 1024L); + uassert( 10221 , "JS_NewRuntime failed" , _runtime ); + + if ( ! utf8Ok() ){ + log() << "*** warning: spider monkey build without utf8 support. consider rebuilding with utf8 support" << endl; + } + + int x = 0; + assert( x = 1 ); + uassert( 10222 , "assert not being executed" , x == 1 ); + } + + ~SMEngine(){ + JS_DestroyRuntime( _runtime ); + JS_ShutDown(); + } + + Scope * createScope(); + + void runTest(); + + virtual bool utf8Ok() const { return JS_CStringsAreUTF8(); } + +#ifdef XULRUNNER + JSClass * _dateClass; + JSClass * _regexClass; +#endif + + + private: + JSRuntime * _runtime; + friend class SMScope; + }; + + SMEngine * globalSMEngine; + + + void ScriptEngine::setup(){ + globalSMEngine = new SMEngine(); + globalScriptEngine = globalSMEngine; + } + + + // ------ scope ------ + + + JSBool no_gc(JSContext *cx, JSGCStatus status){ + return JS_FALSE; + } + + JSBool yes_gc(JSContext *cx, JSGCStatus status){ + return JS_TRUE; + } + + class SMScope : public Scope { + public: + SMScope() : _this( 0 ) , _externalSetup( false ) , _localConnect( false ) { + smlock; + _context = JS_NewContext( globalSMEngine->_runtime , 8192 ); + _convertor = new Convertor( _context ); + massert( 10431 , "JS_NewContext failed" , _context ); + + JS_SetOptions( _context , JSOPTION_VAROBJFIX); + //JS_SetVersion( _context , JSVERSION_LATEST); TODO + JS_SetErrorReporter( _context , errorReporter ); + + _global = JS_NewObject( _context , &global_class, NULL, NULL); + massert( 10432 , "JS_NewObject failed for global" , _global ); + JS_SetGlobalObject( _context , _global ); + massert( 10433 , "js init failed" , JS_InitStandardClasses( _context , _global ) ); + + JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX ); + + JS_DefineFunctions( _context , _global , globalHelpers ); + + JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers ); + + //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem + + _postCreateHacks(); + } + + ~SMScope(){ + smlock; + uassert( 10223 , "deleted SMScope twice?" , _convertor ); + + for ( list<void*>::iterator i=_roots.begin(); i != _roots.end(); i++ ){ + JS_RemoveRoot( _context , *i ); + } + _roots.clear(); + + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( _convertor ){ + delete _convertor; + _convertor = 0; + } + + if ( _context ){ + JS_DestroyContext( _context ); + _context = 0; + } + + } + + void reset(){ + smlock; + assert( _convertor ); + return; + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + currentScope.reset( this ); + _error = ""; + } + + void addRoot( void * root , const char * name ){ + JS_AddNamedRoot( _context , root , name ); + _roots.push_back( root ); + } + + void init( BSONObj * data ){ + smlock; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ){ + BSONElement e = i.next(); + _convertor->setProperty( _global , e.fieldName() , _convertor->toval( e ) ); + _initFieldNames.insert( e.fieldName() ); + } + + } + + void externalSetup(){ + smlock; + uassert( 10224 , "already local connected" , ! _localConnect ); + if ( _externalSetup ) + return; + initMongoJS( this , _context , _global , false ); + _externalSetup = true; + } + + void localConnect( const char * dbName ){ + smlock; + uassert( 10225 , "already setup for external db" , ! _externalSetup ); + if ( _localConnect ){ + uassert( 10226 , "connected to different db" , _localDBName == dbName ); + return; + } + + initMongoJS( this , _context , _global , true ); + + exec( "_mongo = new Mongo();" ); + exec( ((string)"db = _mongo.getDB( \"" + dbName + "\" ); ").c_str() ); + + _localConnect = true; + _localDBName = dbName; + loadStored(); + } + + // ----- getters ------ + double getNumber( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + return _convertor->toNumber( val ); + } + + string getString( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + JSString * s = JS_ValueToString( _context , val ); + return _convertor->toString( s ); + } + + bool getBoolean( const char *field ){ + smlock; + return _convertor->getBoolean( _global , field ); + } + + BSONObj getObject( const char *field ){ + smlock; + return _convertor->toObject( _convertor->getProperty( _global , field ) ); + } + + JSObject * getJSObject( const char * field ){ + smlock; + return _convertor->getJSObject( _global , field ); + } + + int type( const char *field ){ + smlock; + jsval val; + assert( JS_GetProperty( _context , _global , field , &val ) ); + + switch ( JS_TypeOfValue( _context , val ) ){ + case JSTYPE_VOID: return Undefined; + case JSTYPE_NULL: return jstNULL; + case JSTYPE_OBJECT: { + if ( val == JSVAL_NULL ) + return jstNULL; + JSObject * o = JSVAL_TO_OBJECT( val ); + if ( JS_IsArrayObject( _context , o ) ) + return Array; + if ( isDate( _context , o ) ) + return Date; + return Object; + } + case JSTYPE_FUNCTION: return Code; + case JSTYPE_STRING: return String; + case JSTYPE_NUMBER: return NumberDouble; + case JSTYPE_BOOLEAN: return Bool; + default: + uassert( 10227 , "unknown type" , 0 ); + } + return 0; + } + + // ----- setters ------ + + void setElement( const char *field , const BSONElement& val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setNumber( const char *field , double val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setString( const char *field , const char * val ){ + smlock; + jsval v = _convertor->toval( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setObject( const char *field , const BSONObj& obj , bool readOnly ){ + smlock; + jsval v = _convertor->toval( &obj , readOnly ); + JS_SetProperty( _context , _global , field , &v ); + } + + void setBoolean( const char *field , bool val ){ + smlock; + jsval v = BOOLEAN_TO_JSVAL( val ); + assert( JS_SetProperty( _context , _global , field , &v ) ); + } + + void setThis( const BSONObj * obj ){ + smlock; + if ( _this ){ + JS_RemoveRoot( _context , &_this ); + _this = 0; + } + + if ( obj ){ + _this = _convertor->toJSObject( obj ); + JS_AddNamedRoot( _context , &_this , "scope this" ); + } + } + + // ---- functions ----- + + ScriptingFunction _createFunction( const char * code ){ + smlock; + precall(); + return (ScriptingFunction)_convertor->compileFunction( code ); + } + + struct TimeoutSpec { + boost::posix_time::ptime start; + boost::posix_time::time_duration timeout; + int count; + }; + + static JSBool _checkTimeout( JSContext *cx ){ + TimeoutSpec &spec = *(TimeoutSpec *)( JS_GetContextPrivate( cx ) ); + if ( ++spec.count % 1000 != 0 ) + return JS_TRUE; + boost::posix_time::time_duration elapsed = ( boost::posix_time::microsec_clock::local_time() - spec.start ); + if ( elapsed < spec.timeout ) { + return JS_TRUE; + } + JS_ReportError( cx, "Timeout exceeded" ); + return JS_FALSE; + + } + static JSBool checkTimeout( JSContext *cx, JSScript *script ){ + return _checkTimeout( cx ); + } + + + void installCheckTimeout( int timeoutMs ) { + if ( timeoutMs > 0 ) { + TimeoutSpec *spec = new TimeoutSpec; + spec->timeout = boost::posix_time::millisec( timeoutMs ); + spec->start = boost::posix_time::microsec_clock::local_time(); + spec->count = 0; + JS_SetContextPrivate( _context, (void*)spec ); +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context, _checkTimeout ); +#else + JS_SetBranchCallback( _context, checkTimeout ); +#endif + } + } + + void uninstallCheckTimeout( int timeoutMs ) { + if ( timeoutMs > 0 ) { +#if defined(SM181) && !defined(XULRUNNER190) + JS_SetOperationCallback( _context , 0 ); +#else + JS_SetBranchCallback( _context, 0 ); +#endif + delete (TimeoutSpec *)JS_GetContextPrivate( _context ); + JS_SetContextPrivate( _context, 0 ); + } + } + + void precall(){ + _error = ""; + currentScope.reset( this ); + } + + bool exec( const string& code , const string& name = "(anon)" , bool printResult = false , bool reportError = true , bool assertOnError = true, int timeoutMs = 0 ){ + smlock; + precall(); + + jsval ret = JSVAL_VOID; + + installCheckTimeout( timeoutMs ); + JSBool worked = JS_EvaluateScript( _context , _global , code.c_str() , strlen( code.c_str() ) , name.c_str() , 0 , &ret ); + uninstallCheckTimeout( timeoutMs ); + + if ( assertOnError ) + uassert( 10228 , name + " exec failed" , worked ); + + if ( reportError && ! _error.empty() ){ + // cout << "exec error: " << _error << endl; + // already printed in reportError, so... TODO + } + + if ( worked ) + _convertor->setProperty( _global , "__lastres__" , ret ); + + if ( worked && printResult && ! JSVAL_IS_VOID( ret ) ) + cout << _convertor->toString( ret ) << endl; + + return worked; + } + + int invoke( JSFunction * func , const BSONObj& args, int timeoutMs , bool ignoreReturn ){ + smlock; + precall(); + + assert( JS_EnterLocalRootScope( _context ) ); + + int nargs = args.nFields(); + scoped_array<jsval> smargsPtr( new jsval[nargs] ); + if ( nargs ){ + BSONObjIterator it( args ); + for ( int i=0; i<nargs; i++ ){ + smargsPtr[i] = _convertor->toval( it.next() ); + } + } + + if ( args.isEmpty() ){ + _convertor->setProperty( _global , "args" , JSVAL_NULL ); + } + else { + setObject( "args" , args , true ); // this is for backwards compatability + } + + JS_LeaveLocalRootScope( _context ); + + installCheckTimeout( timeoutMs ); + jsval rval; + JSBool ret = JS_CallFunction( _context , _this ? _this : _global , func , nargs , smargsPtr.get() , &rval ); + uninstallCheckTimeout( timeoutMs ); + + if ( !ret ) { + return -3; + } + + if ( ! ignoreReturn ){ + assert( JS_SetProperty( _context , _global , "return" , &rval ) ); + } + + return 0; + } + + int invoke( ScriptingFunction funcAddr , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = 0 ){ + return invoke( (JSFunction*)funcAddr , args , timeoutMs , ignoreReturn ); + } + + void gotError( string s ){ + _error = s; + } + + string getError(){ + return _error; + } + + void injectNative( const char *field, NativeFunction func ){ + smlock; + string name = field; + _convertor->setProperty( _global , (name + "_").c_str() , _convertor->toval( (double)(long long)func ) ); + + stringstream code; + code << field << "_" << " = { x : " << field << "_ }; "; + code << field << " = function(){ return nativeHelper.apply( " << field << "_ , arguments ); }"; + exec( code.str().c_str() ); + + } + + virtual void gc(){ + JS_GC( _context ); + } + + JSContext *SavedContext() const { return _context; } + + private: + + void _postCreateHacks(){ +#ifdef XULRUNNER + exec( "__x__ = new Date(1);" ); + globalSMEngine->_dateClass = _convertor->getClass( _global , "__x__" ); + exec( "__x__ = /abc/i" ); + globalSMEngine->_regexClass = _convertor->getClass( _global , "__x__" ); +#endif + } + + JSContext * _context; + Convertor * _convertor; + + JSObject * _global; + JSObject * _this; + + string _error; + list<void*> _roots; + + bool _externalSetup; + bool _localConnect; + + set<string> _initFieldNames; + + }; + + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ){ + stringstream ss; + ss << "JS Error: " << message; + + if ( report ){ + ss << " " << report->filename << ":" << report->lineno; + } + + log() << ss.str() << endl; + + if ( currentScope.get() ){ + currentScope->gotError( ss.str() ); + } + } + + JSBool native_load( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ){ + Convertor c(cx); + + Scope * s = currentScope.get(); + + for ( uintN i=0; i<argc; i++ ){ + string filename = c.toString( argv[i] ); + cout << "should load [" << filename << "]" << endl; + + if ( ! s->execFile( filename , false , true , false ) ){ + JS_ReportError( cx , ((string)"error loading file: " + filename ).c_str() ); + return JS_FALSE; + } + } + + return JS_TRUE; + } + + + + void SMEngine::runTest(){ + SMScope s; + + s.localConnect( "foo" ); + + s.exec( "assert( db.getMongo() )" ); + s.exec( "assert( db.bar , 'collection getting does not work' ); " ); + s.exec( "assert.eq( db._name , 'foo' );" ); + s.exec( "assert( _mongo == db.getMongo() ); " ); + s.exec( "assert( _mongo == db._mongo ); " ); + s.exec( "assert( typeof DB.bar == 'undefined' ); " ); + s.exec( "assert( typeof DB.prototype.bar == 'undefined' , 'resolution is happening on prototype, not object' ); " ); + + s.exec( "assert( db.bar ); " ); + s.exec( "assert( typeof db.addUser == 'function' )" ); + s.exec( "assert( db.addUser == DB.prototype.addUser )" ); + s.exec( "assert.eq( 'foo.bar' , db.bar._fullName ); " ); + s.exec( "db.bar.verify();" ); + + s.exec( "db.bar.silly.verify();" ); + s.exec( "assert.eq( 'foo.bar.silly' , db.bar.silly._fullName )" ); + s.exec( "assert.eq( 'function' , typeof _mongo.find , 'mongo.find is not a function' )" ); + + assert( (string)"abc" == trim( "abc" ) ); + assert( (string)"abc" == trim( " abc" ) ); + assert( (string)"abc" == trim( "abc " ) ); + assert( (string)"abc" == trim( " abc " ) ); + + } + + Scope * SMEngine::createScope(){ + return new SMScope(); + } + + void Convertor::addRoot( JSFunction * f , const char * name ){ + if ( ! f ) + return; + + SMScope * scope = currentScope.get(); + uassert( 10229 , "need a scope" , scope ); + + JSObject * o = JS_GetFunctionObject( f ); + assert( o ); + scope->addRoot( &o , name ); + } + +} + +#include "sm_db.cpp" diff --git a/scripting/engine_spidermonkey.h b/scripting/engine_spidermonkey.h new file mode 100644 index 0000000..8aeb56c --- /dev/null +++ b/scripting/engine_spidermonkey.h @@ -0,0 +1,116 @@ +// engine_spidermonkey.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "engine.h" + +// START inc hacking + +#if defined( MOZJS ) + +#define MOZILLA_1_8_BRANCH + +#include "mozjs/jsapi.h" +#include "mozjs/jsdate.h" +#include "mozjs/jsregexp.h" + +#warning if you are using an ubuntu version of spider monkey, we recommend installing spider monkey from source + +#elif defined( OLDJS ) + +#ifdef WIN32 +#include "jstypes.h" +#undef JS_PUBLIC_API +#undef JS_PUBLIC_DATA +#define JS_PUBLIC_API(t) t +#define JS_PUBLIC_DATA(t) t +#endif + +#include "jsapi.h" +#include "jsdate.h" +#include "jsregexp.h" + +#else + +#include "js/jsapi.h" +#include "js/jsobj.h" +#include "js/jsdate.h" +#include "js/jsregexp.h" + +#endif + +// END inc hacking + +// -- SM 1.6 hacks --- +#ifndef JSCLASS_GLOBAL_FLAGS + +#warning old version of spider monkey ( probably 1.6 ) you should upgrade to at least 1.7 + +#define JSCLASS_GLOBAL_FLAGS 0 + +JSBool JS_CStringsAreUTF8(){ + return false; +} + +#define SM16 + +#endif +// -- END SM 1.6 hacks --- + +#ifdef JSVAL_IS_TRACEABLE +#define SM18 +#endif + +#ifdef XULRUNNER +#define SM181 +#endif + +namespace mongo { + + class SMScope; + class Convertor; + + extern JSClass bson_class; + extern JSClass bson_ro_class; + + extern JSClass object_id_class; + extern JSClass dbpointer_class; + extern JSClass dbref_class; + extern JSClass bindata_class; + extern JSClass timestamp_class; + extern JSClass minkey_class; + extern JSClass maxkey_class; + + // internal things + void dontDeleteScope( SMScope * s ){} + void errorReporter( JSContext *cx, const char *message, JSErrorReport *report ); + extern boost::thread_specific_ptr<SMScope> currentScope; + + // bson + JSBool resolveBSONField( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ); + + + // mongo + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ); + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ); + +#define JSVAL_IS_OID(v) ( JSVAL_IS_OBJECT( v ) && JS_InstanceOf( cx , JSVAL_TO_OBJECT( v ) , &object_id_class , 0 ) ) + + bool isDate( JSContext * cx , JSObject * o ); + +} diff --git a/scripting/engine_v8.cpp b/scripting/engine_v8.cpp new file mode 100644 index 0000000..35f2eb8 --- /dev/null +++ b/scripting/engine_v8.cpp @@ -0,0 +1,436 @@ +//engine_v8.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "engine_v8.h" + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" + +#define V8_SIMPLE_HEADER Locker l; HandleScope handle_scope; Context::Scope context_scope( _context ); + +namespace mongo { + + // --- engine --- + + V8ScriptEngine::V8ScriptEngine() {} + + V8ScriptEngine::~V8ScriptEngine(){ + } + + void ScriptEngine::setup(){ + if ( !globalScriptEngine ){ + globalScriptEngine = new V8ScriptEngine(); + } + } + + // --- scope --- + + V8Scope::V8Scope( V8ScriptEngine * engine ) + : _engine( engine ) , + _connectState( NOT ){ + + Locker l; + HandleScope handleScope; + _context = Context::New(); + Context::Scope context_scope( _context ); + _global = Persistent< v8::Object >::New( _context->Global() ); + + _this = Persistent< v8::Object >::New( v8::Object::New() ); + + _global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)->GetFunction() ); + _global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)->GetFunction() ); + + _global->Set(v8::String::New("load"), + v8::FunctionTemplate::New(loadCallback, v8::External::New(this))->GetFunction() ); + + _wrapper = Persistent< v8::Function >::New( getObjectWrapperTemplate()->GetFunction() ); + + installDBTypes( _global ); + } + + V8Scope::~V8Scope(){ + Locker l; + Context::Scope context_scope( _context ); + _wrapper.Dispose(); + _this.Dispose(); + for( unsigned i = 0; i < _funcs.size(); ++i ) + _funcs[ i ].Dispose(); + _funcs.clear(); + _global.Dispose(); + _context.Dispose(); + } + + Handle< Value > V8Scope::nativeCallback( const Arguments &args ) { + Locker l; + HandleScope handle_scope; + Local< External > f = External::Cast( *args.Callee()->Get( v8::String::New( "_native_function" ) ) ); + NativeFunction function = (NativeFunction)(f->Value()); + BSONObjBuilder b; + for( int i = 0; i < args.Length(); ++i ) { + stringstream ss; + ss << i; + v8ToMongoElement( b, v8::String::New( "foo" ), ss.str(), args[ i ] ); + } + BSONObj nativeArgs = b.obj(); + BSONObj ret; + try { + ret = function( nativeArgs ); + } catch( const std::exception &e ) { + return v8::ThrowException(v8::String::New(e.what())); + } catch( ... ) { + return v8::ThrowException(v8::String::New("unknown exception")); + } + return handle_scope.Close( mongoToV8Element( ret.firstElement() ) ); + } + + Handle< Value > V8Scope::loadCallback( const Arguments &args ) { + Locker l; + HandleScope handle_scope; + Handle<External> field = Handle<External>::Cast(args.Data()); + void* ptr = field->Value(); + V8Scope* self = static_cast<V8Scope*>(ptr); + + Context::Scope context_scope(self->_context); + for (int i = 0; i < args.Length(); ++i) { + std::string filename(toSTLString(args[i])); + if (!self->execFile(filename, false , true , false)) { + return v8::ThrowException(v8::String::New((std::string("error loading file: ") + filename).c_str())); + } + } + return v8::True(); + } + + // ---- global stuff ---- + + void V8Scope::init( BSONObj * data ){ + Locker l; + if ( ! data ) + return; + + BSONObjIterator i( *data ); + while ( i.more() ){ + BSONElement e = i.next(); + setElement( e.fieldName() , e ); + } + } + + void V8Scope::setNumber( const char * field , double val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::Number::New( val ) ); + } + + void V8Scope::setString( const char * field , const char * val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::String::New( val ) ); + } + + void V8Scope::setBoolean( const char * field , bool val ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , v8::Boolean::New( val ) ); + } + + void V8Scope::setElement( const char *field , const BSONElement& e ){ + V8_SIMPLE_HEADER + _global->Set( v8::String::New( field ) , mongoToV8Element( e ) ); + } + + void V8Scope::setObject( const char *field , const BSONObj& obj , bool readOnly){ + V8_SIMPLE_HEADER + // Set() accepts a ReadOnly parameter, but this just prevents the field itself + // from being overwritten and doesn't protect the object stored in 'field'. + _global->Set( v8::String::New( field ) , mongoToV8( obj, false, readOnly) ); + } + + int V8Scope::type( const char *field ){ + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() ) + return jstNULL; + if ( v->IsUndefined() ) + return Undefined; + if ( v->IsString() ) + return String; + if ( v->IsFunction() ) + return Code; + if ( v->IsArray() ) + return Array; + if ( v->IsBoolean() ) + return Bool; + if ( v->IsInt32() ) + return NumberInt; + if ( v->IsNumber() ) + return NumberDouble; + if ( v->IsExternal() ){ + uassert( 10230 , "can't handle external yet" , 0 ); + return -1; + } + if ( v->IsDate() ) + return Date; + if ( v->IsObject() ) + return Object; + + throw UserException( 12509, (string)"don't know what this is: " + field ); + } + + v8::Handle<v8::Value> V8Scope::get( const char * field ){ + return _global->Get( v8::String::New( field ) ); + } + + double V8Scope::getNumber( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToNumber()->Value(); + } + + int V8Scope::getNumberInt( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToInt32()->Value(); + } + + long long V8Scope::getNumberLongLong( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToInteger()->Value(); + } + + string V8Scope::getString( const char *field ){ + V8_SIMPLE_HEADER + return toSTLString( get( field ) ); + } + + bool V8Scope::getBoolean( const char *field ){ + V8_SIMPLE_HEADER + return get( field )->ToBoolean()->Value(); + } + + BSONObj V8Scope::getObject( const char * field ){ + V8_SIMPLE_HEADER + Handle<Value> v = get( field ); + if ( v->IsNull() || v->IsUndefined() ) + return BSONObj(); + uassert( 10231 , "not an object" , v->IsObject() ); + return v8ToMongo( v->ToObject() ); + } + + // --- functions ----- + + Local< v8::Function > V8Scope::__createFunction( const char * raw ){ + for(; isspace( *raw ); ++raw ); // skip whitespace + string code = raw; + if ( code.find( "function" ) == string::npos ){ + if ( code.find( "\n" ) == string::npos && + code.find( "return" ) == string::npos && + ( code.find( ";" ) == string::npos || code.find( ";" ) == code.size() - 1 ) ){ + code = "return " + code; + } + code = "function(){ " + code + "}"; + } + + int num = _funcs.size() + 1; + + string fn; + { + stringstream ss; + ss << "_funcs" << num; + fn = ss.str(); + } + + code = fn + " = " + code; + + TryCatch try_catch; + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( fn.c_str() ) ); + if ( script.IsEmpty() ){ + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + Local<Value> result = script->Run(); + if ( result.IsEmpty() ){ + _error = (string)"compile error: " + toSTLString( &try_catch ); + log() << _error << endl; + return Local< v8::Function >(); + } + + return v8::Function::Cast( *_global->Get( v8::String::New( fn.c_str() ) ) ); + } + + ScriptingFunction V8Scope::_createFunction( const char * raw ){ + V8_SIMPLE_HEADER + Local< Value > ret = __createFunction( raw ); + if ( ret.IsEmpty() ) + return 0; + Persistent<Value> f = Persistent< Value >::New( ret ); + uassert( 10232, "not a func" , f->IsFunction() ); + int num = _funcs.size() + 1; + _funcs.push_back( f ); + return num; + } + + void V8Scope::setThis( const BSONObj * obj ){ + V8_SIMPLE_HEADER + if ( ! obj ){ + _this = Persistent< v8::Object >::New( v8::Object::New() ); + return; + } + + //_this = mongoToV8( *obj ); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::External::New( createWrapperHolder( obj , true , false ) ); + _this = Persistent< v8::Object >::New( _wrapper->NewInstance( 1, argv ) ); + } + + int V8Scope::invoke( ScriptingFunction func , const BSONObj& argsObject, int timeoutMs , bool ignoreReturn ){ + V8_SIMPLE_HEADER + Handle<Value> funcValue = _funcs[func-1]; + + TryCatch try_catch; + int nargs = argsObject.nFields(); + scoped_array< Handle<Value> > args; + if ( nargs ){ + args.reset( new Handle<Value>[nargs] ); + BSONObjIterator it( argsObject ); + for ( int i=0; i<nargs; i++ ){ + BSONElement next = it.next(); + args[i] = mongoToV8Element( next ); + } + setObject( "args", argsObject, true ); // for backwards compatibility + } else { + _global->Set( v8::String::New( "args" ), v8::Undefined() ); + } + Local<Value> result = ((v8::Function*)(*funcValue))->Call( _this , nargs , args.get() ); + + if ( result.IsEmpty() ){ + stringstream ss; + ss << "error in invoke: " << toSTLString( &try_catch ); + _error = ss.str(); + log() << _error << endl; + return 1; + } + + if ( ! ignoreReturn ){ + _global->Set( v8::String::New( "return" ) , result ); + } + + return 0; + } + + bool V8Scope::exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ){ + if ( timeoutMs ){ + static bool t = 1; + if ( t ){ + log() << "timeoutMs not support for v8 yet" << endl; + t = 0; + } + } + + V8_SIMPLE_HEADER + + TryCatch try_catch; + + Handle<Script> script = v8::Script::Compile( v8::String::New( code.c_str() ) , + v8::String::New( name.c_str() ) ); + if (script.IsEmpty()) { + stringstream ss; + ss << "compile error: " << toSTLString( &try_catch ); + _error = ss.str(); + if (reportError) + log() << _error << endl; + if ( assertOnError ) + uassert( 10233 , _error , 0 ); + return false; + } + + Handle<v8::Value> result = script->Run(); + if ( result.IsEmpty() ){ + _error = (string)"exec error: " + toSTLString( &try_catch ); + if ( reportError ) + log() << _error << endl; + if ( assertOnError ) + uassert( 10234 , _error , 0 ); + return false; + } + + _global->Set( v8::String::New( "__lastres__" ) , result ); + + if ( printResult && ! result->IsUndefined() ){ + cout << toSTLString( result ) << endl; + } + + return true; + } + + void V8Scope::injectNative( const char *field, NativeFunction func ){ + V8_SIMPLE_HEADER + + Handle< FunctionTemplate > f( v8::FunctionTemplate::New( nativeCallback ) ); + f->Set( v8::String::New( "_native_function" ), External::New( (void*)func ) ); + _global->Set( v8::String::New( field ), f->GetFunction() ); + } + + void V8Scope::gc() { + Locker l; + while( V8::IdleNotification() ); + } + + // ----- db access ----- + + void V8Scope::localConnect( const char * dbName ){ + V8_SIMPLE_HEADER + + if ( _connectState == EXTERNAL ) + throw UserException( 12510, "externalSetup already called, can't call externalSetup" ); + if ( _connectState == LOCAL ){ + if ( _localDBName == dbName ) + return; + throw UserException( 12511, "localConnect called with a different name previously" ); + } + + //_global->Set( v8::String::New( "Mongo" ) , _engine->_externalTemplate->GetFunction() ); + _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( true )->GetFunction() ); + exec( jsconcatcode , "localConnect 1" , false , true , true , 0 ); + exec( "_mongo = new Mongo();" , "local connect 2" , false , true , true , 0 ); + exec( (string)"db = _mongo.getDB(\"" + dbName + "\");" , "local connect 3" , false , true , true , 0 ); + _connectState = LOCAL; + _localDBName = dbName; + loadStored(); + } + + void V8Scope::externalSetup(){ + V8_SIMPLE_HEADER + if ( _connectState == EXTERNAL ) + return; + if ( _connectState == LOCAL ) + throw UserException( 12512, "localConnect already called, can't call externalSetup" ); + + installFork( _global, _context ); + _global->Set( v8::String::New( "Mongo" ) , getMongoFunctionTemplate( false )->GetFunction() ); + exec( jsconcatcode , "shell setup" , false , true , true , 0 ); + _connectState = EXTERNAL; + } + + // ----- internal ----- + + void V8Scope::reset(){ + _startCall(); + } + + void V8Scope::_startCall(){ + _error = ""; + } + +} // namespace mongo diff --git a/scripting/engine_v8.h b/scripting/engine_v8.h new file mode 100644 index 0000000..9d86d92 --- /dev/null +++ b/scripting/engine_v8.h @@ -0,0 +1,116 @@ +//engine_v8.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <vector> +#include "engine.h" +#include <v8.h> + +using namespace v8; + +namespace mongo { + + class V8ScriptEngine; + + class V8Scope : public Scope { + public: + + V8Scope( V8ScriptEngine * engine ); + ~V8Scope(); + + virtual void reset(); + virtual void init( BSONObj * data ); + + virtual void localConnect( const char * dbName ); + virtual void externalSetup(); + + v8::Handle<v8::Value> get( const char * field ); // caller must create context and handle scopes + virtual double getNumber( const char *field ); + virtual int getNumberInt( const char *field ); + virtual long long getNumberLongLong( const char *field ); + virtual string getString( const char *field ); + virtual bool getBoolean( const char *field ); + virtual BSONObj getObject( const char *field ); + + virtual int type( const char *field ); + + virtual void setNumber( const char *field , double val ); + virtual void setString( const char *field , const char * val ); + virtual void setBoolean( const char *field , bool val ); + virtual void setElement( const char *field , const BSONElement& e ); + virtual void setObject( const char *field , const BSONObj& obj , bool readOnly); + virtual void setThis( const BSONObj * obj ); + + virtual ScriptingFunction _createFunction( const char * code ); + Local< v8::Function > __createFunction( const char * code ); + virtual int invoke( ScriptingFunction func , const BSONObj& args, int timeoutMs = 0 , bool ignoreReturn = false ); + virtual bool exec( const string& code , const string& name , bool printResult , bool reportError , bool assertOnError, int timeoutMs ); + virtual string getError(){ return _error; } + + virtual void injectNative( const char *field, NativeFunction func ); + + void gc(); + + Handle< Context > context() const { return _context; } + + private: + void _startCall(); + + static Handle< Value > nativeCallback( const Arguments &args ); + + static Handle< Value > loadCallback( const Arguments &args ); + + V8ScriptEngine * _engine; + + Persistent<Context> _context; + Persistent<v8::Object> _global; + + string _error; + vector< Persistent<Value> > _funcs; + v8::Persistent<v8::Object> _this; + + v8::Persistent<v8::Function> _wrapper; + + enum ConnectState { NOT , LOCAL , EXTERNAL }; + ConnectState _connectState; + }; + + class V8ScriptEngine : public ScriptEngine { + public: + V8ScriptEngine(); + virtual ~V8ScriptEngine(); + + virtual Scope * createScope(){ return new V8Scope( this ); } + + virtual void runTest(){} + + bool utf8Ok() const { return true; } + + class V8Unlocker : public Unlocker { + v8::Unlocker u_; + }; + + virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< Unlocker >( new V8Unlocker ); } + + private: + friend class V8Scope; + }; + + + extern ScriptEngine * globalScriptEngine; +} diff --git a/scripting/sm_db.cpp b/scripting/sm_db.cpp new file mode 100644 index 0000000..72d8638 --- /dev/null +++ b/scripting/sm_db.cpp @@ -0,0 +1,854 @@ +// sm_db.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// hacked in right now from engine_spidermonkey.cpp + +#include "../client/syncclusterconnection.h" + +namespace mongo { + + bool haveLocalShardingInfo( const string& ns ); + + // ------------ some defs needed --------------- + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ); + + // ------------ utils ------------------ + + + bool isSpecialName( const string& name ){ + static set<string> names; + if ( names.size() == 0 ){ + names.insert( "tojson" ); + names.insert( "toJson" ); + names.insert( "toString" ); + } + + if ( name.length() == 0 ) + return false; + + if ( name[0] == '_' ) + return true; + + return names.count( name ) > 0; + } + + + // ------ cursor ------ + + class CursorHolder { + public: + CursorHolder( auto_ptr< DBClientCursor > &cursor, const shared_ptr< DBClientWithCommands > &connection ) : + connection_( connection ), + cursor_( cursor ) { + assert( cursor_.get() ); + } + DBClientCursor *get() const { return cursor_.get(); } + private: + shared_ptr< DBClientWithCommands > connection_; + auto_ptr< DBClientCursor > cursor_; + }; + + DBClientCursor *getCursor( JSContext *cx, JSObject *obj ) { + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + uassert( 10235 , "no cursor!" , holder ); + return holder->get(); + } + + JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + uassert( 10236 , "no args to internal_cursor_constructor" , argc == 0 ); + assert( JS_SetPrivate( cx , obj , 0 ) ); // just for safety + return JS_TRUE; + } + + void internal_cursor_finalize( JSContext * cx , JSObject * obj ){ + CursorHolder * holder = (CursorHolder*)JS_GetPrivate( cx , obj ); + if ( holder ){ + delete holder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + DBClientCursor *cursor = getCursor( cx, obj ); + *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE; + return JS_TRUE; + } + + JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + DBClientCursor *cursor = getCursor( cx, obj ); + if ( ! cursor->more() ){ + JS_ReportError( cx , "cursor at the end" ); + return JS_FALSE; + } + Convertor c(cx); + + BSONObj n = cursor->next(); + *rval = c.toval( &n ); + return JS_TRUE; + } + + + JSFunctionSpec internal_cursor_functions[] = { + { "hasNext" , internal_cursor_hasNext , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "next" , internal_cursor_next , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + JSClass internal_cursor_class = { + "InternalCursor" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, internal_cursor_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // ------ mongo stuff ------ + + JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + uassert( 10237 , "mongo_constructor not implemented yet" , 0 ); + throw -1; + } + + JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + shared_ptr< DBClientWithCommands > client( createDirectClient() ); + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( client ) ) ) ); + + jsval host = c.toval( "EMBEDDED" ); + assert( JS_SetProperty( cx , obj , "host" , &host ) ); + + return JS_TRUE; + } + + JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + uassert( 10238 , "0 or 1 args to Mongo" , argc <= 1 ); + + string host = "127.0.0.1"; + if ( argc > 0 ) + host = c.toString( argv[0] ); + + shared_ptr< DBClientWithCommands > conn; + + string errmsg; + if ( host.find( "," ) == string::npos ){ + DBClientConnection * c = new DBClientConnection( true ); + conn.reset( c ); + if ( ! c->connect( host , errmsg ) ){ + JS_ReportError( cx , ((string)"couldn't connect: " + errmsg).c_str() ); + return JS_FALSE; + } + } + else { // paired + int numCommas = 0; + for ( uint i=0; i<host.size(); i++ ) + if ( host[i] == ',' ) + numCommas++; + + assert( numCommas > 0 ); + + if ( numCommas == 1 ){ + DBClientPaired * c = new DBClientPaired(); + conn.reset( c ); + if ( ! c->connect( host ) ){ + JS_ReportError( cx , "couldn't connect to pair" ); + return JS_FALSE; + } + } + else if ( numCommas == 2 ){ + conn.reset( new SyncCluterConnection( host ) ); + } + else { + JS_ReportError( cx , "1 (paired) or 2(quorum) commas are allowed" ); + return JS_FALSE; + } + } + + + assert( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) ); + jsval host_val = c.toval( host.c_str() ); + assert( JS_SetProperty( cx , obj , "host" , &host_val ) ); + return JS_TRUE; + + } + + DBClientWithCommands *getConnection( JSContext *cx, JSObject *obj ) { + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + uassert( 10239 , "no connection!" , connHolder && connHolder->get() ); + return connHolder->get(); + } + + void mongo_finalize( JSContext * cx , JSObject * obj ){ + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + if ( connHolder ){ + delete connHolder; + assert( JS_SetPrivate( cx , obj , 0 ) ); + } + } + + JSClass mongo_class = { + "Mongo" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, mongo_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + uassert( 10240 , "mongo_find neesd 5 args" , argc == 5 ); + shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj ); + uassert( 10241 , "no connection!" , connHolder && connHolder->get() ); + DBClientWithCommands *conn = connHolder->get(); + + Convertor c( cx ); + + string ns = c.toString( argv[0] ); + + BSONObj q = c.toObject( argv[1] ); + BSONObj f = c.toObject( argv[2] ); + + int nToReturn = (int) c.toNumber( argv[3] ); + int nToSkip = (int) c.toNumber( argv[4] ); + bool slaveOk = c.getBoolean( obj , "slaveOk" ); + + try { + + auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? QueryOption_SlaveOk : 0 ); + if ( ! cursor.get() ){ + JS_ReportError( cx , "error doing query: failed" ); + return JS_FALSE; + } + JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 ); + assert( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) ); + *rval = OBJECT_TO_JSVAL( mycursor ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing query: unknown" ); + return JS_FALSE; + } + } + + JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_find needs at elast 3 args" , argc >= 3 ); + smuassert( cx , "2nd param to update has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + smuassert( cx , "3rd param to update has to be an object" , JSVAL_IS_OBJECT( argv[2] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_update" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10245 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + + bool upsert = argc > 3 && c.toBoolean( argv[3] ); + bool multi = argc > 4 && c.toBoolean( argv[4] ); + + try { + conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert , multi ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing update" ); + return JS_FALSE; + } + } + + JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_insert needs 2 args" , argc == 2 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_insert" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10248 , "no connection!" , conn ); + + + string ns = c.toString( argv[0] ); + BSONObj o = c.toObject( argv[1] ); + + // TODO: add _id + + try { + conn->insert( ns , o ); + return JS_TRUE; + } + catch ( std::exception& e ){ + stringstream ss; + ss << "error doing insert:" << e.what(); + string s = ss.str(); + JS_ReportError( cx , s.c_str() ); + return JS_FALSE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing insert" ); + return JS_FALSE; + } + } + + JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + smuassert( cx , "mongo_remove needs 2 arguments" , argc == 2 ); + smuassert( cx , "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); + + Convertor c( cx ); + if ( c.getBoolean( obj , "readOnly" ) ){ + JS_ReportError( cx , "js db in read only mode - mongo_remove" ); + return JS_FALSE; + } + + DBClientWithCommands * conn = getConnection( cx, obj ); + uassert( 10251 , "no connection!" , conn ); + + string ns = c.toString( argv[0] ); + BSONObj o = c.toObject( argv[1] ); + + try { + conn->remove( ns , o ); + return JS_TRUE; + } + catch ( ... ){ + JS_ReportError( cx , "error doing remove" ); + return JS_FALSE; + } + + } + + JSFunctionSpec mongo_functions[] = { + { "find" , mongo_find , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "update" , mongo_update , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "insert" , mongo_insert , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { "remove" , mongo_remove , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + + // ------------- db_collection ------------- + + JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx , "db_collection_constructor wrong args" , argc == 4 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); + assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); + assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); + + Convertor c(cx); + if ( haveLocalShardingInfo( c.toString( argv[3] ) ) ){ + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return JS_FALSE; + } + + return JS_TRUE; + } + + JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + if ( obj == c.getGlobalPrototype( "DBCollection" ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) + return JS_TRUE; + + string name = c.toString( c.getProperty( obj , "_shortName" ) ); + name += "."; + name += collname; + + jsval db = c.getProperty( obj , "_db" ); + if ( ! JSVAL_IS_OBJECT( db ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + *objp = obj; + return JS_TRUE; + } + + JSClass db_collection_class = { + "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ){ + Convertor c(cx); + + assert( c.hasProperty( db , "_mongo" ) ); + assert( c.hasProperty( db , "_name" ) ); + + JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); + c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); + c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); + c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); + + string name = c.toString( c.getProperty( db , "_name" ) ); + name += "." + shortName; + c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); + + if ( haveLocalShardingInfo( name ) ){ + JS_ReportError( cx , "can't use sharded collection from db.eval" ); + return 0; + } + + return coll; + } + + // -------------- DB --------------- + + + JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx, "wrong number of arguments to DB" , argc == 2 ); + assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) ); + + return JS_TRUE; + } + + JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + Convertor c( cx ); + + if ( obj == c.getGlobalPrototype( "DB" ) ) + return JS_TRUE; + + string collname = c.toString( id ); + + if ( isSpecialName( collname ) ) + return JS_TRUE; + + JSObject * proto = JS_GetPrototype( cx , obj ); + if ( proto && c.hasProperty( proto , collname.c_str() ) ) + return JS_TRUE; + + JSObject * coll = doCreateCollection( cx , obj , collname ); + if ( ! coll ) + return JS_FALSE; + c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); + + *objp = obj; + return JS_TRUE; + } + + JSClass db_class = { + "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&db_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + + // -------------- object id ------------- + + JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + OID oid; + if ( argc == 0 ){ + oid.init(); + } + else { + smuassert( cx , "object_id_constructor can't take more than 1 param" , argc == 1 ); + string s = c.toString( argv[0] ); + + try { + Scope::validateObjectIdString( s ); + } catch ( const MsgAssertionException &m ) { + static string error = m.toString(); + JS_ReportError( cx, error.c_str() ); + return JS_FALSE; + } + oid.init( s ); + } + + if ( ! JS_InstanceOf( cx , obj , &object_id_class , 0 ) ){ + obj = JS_NewObject( cx , &object_id_class , 0 , 0 ); + assert( obj ); + *rval = OBJECT_TO_JSVAL( obj ); + } + + jsval v = c.toval( oid.str().c_str() ); + assert( JS_SetProperty( cx , obj , "str" , &v ) ); + + return JS_TRUE; + } + + JSClass object_id_class = { + "ObjectId" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSBool object_id_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + Convertor c(cx); + return *rval = c.getProperty( obj , "str" ); + } + + JSFunctionSpec object_id_functions[] = { + { "toString" , object_id_tostring , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } , + { 0 } + }; + + // dbpointer + + JSBool dbpointer_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + if ( argc == 2 ){ + + if ( ! JSVAL_IS_OID( argv[1] ) ){ + JS_ReportError( cx , "2nd arg to DBPointer needs to be oid" ); + return JS_FALSE; + } + + assert( JS_SetProperty( cx , obj , "ns" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "id" , &(argv[1]) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBPointer needs 2 arguments" ); + return JS_FALSE; + } + } + + JSClass dbpointer_class = { + "DBPointer" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec dbpointer_functions[] = { + { 0 } + }; + + + JSBool dbref_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + Convertor c( cx ); + + if ( argc == 2 ){ + assert( JS_SetProperty( cx , obj , "$ref" , &(argv[0]) ) ); + assert( JS_SetProperty( cx , obj , "$id" , &(argv[1]) ) ); + return JS_TRUE; + } + else { + JS_ReportError( cx , "DBRef needs 2 arguments" ); + return JS_FALSE; + } + } + + JSClass dbref_class = { + "DBRef" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec dbref_functions[] = { + { 0 } + }; + + + // BinData + + + JSBool bindata_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + JS_ReportError( cx , "can't create a BinData yet" ); + return JS_FALSE; + } + + JSClass bindata_class = { + "BinData" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec bindata_functions[] = { + { 0 } + }; + + // Map + + bool specialMapString( const string& s ){ + return s == "put" || s == "get" || s == "_get" || s == "values" || s == "_data" || s == "constructor" ; + } + + JSBool map_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + if ( argc > 0 ){ + JS_ReportError( cx , "Map takes no arguments" ); + return JS_FALSE; + } + + JSObject * array = JS_NewObject( cx , 0 , 0 , 0 ); + assert( array ); + + jsval a = OBJECT_TO_JSVAL( array ); + JS_SetProperty( cx , obj , "_data" , &a ); + + return JS_TRUE; + } + + JSBool map_prop( JSContext *cx, JSObject *obj, jsval idval, jsval *vp ){ + Convertor c(cx); + if ( specialMapString( c.toString( idval ) ) ) + return JS_TRUE; + + log() << "illegal prop access: " << c.toString( idval ) << endl; + JS_ReportError( cx , "can't use array access with Map" ); + return JS_FALSE; + } + + JSClass map_class = { + "Map" , JSCLASS_HAS_PRIVATE , + map_prop, JS_PropertyStub, map_prop, map_prop, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSFunctionSpec map_functions[] = { + { 0 } + }; + + + // ----- + + JSClass timestamp_class = { + "Timestamp" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSClass minkey_class = { + "MinKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + JSClass maxkey_class = { + "MaxKey" , JSCLASS_HAS_PRIVATE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // dbquery + + JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ + smuassert( cx , "DDQuery needs at least 4 args" , argc >= 4 ); + + Convertor c(cx); + c.setProperty( obj , "_mongo" , argv[0] ); + c.setProperty( obj , "_db" , argv[1] ); + c.setProperty( obj , "_collection" , argv[2] ); + c.setProperty( obj , "_ns" , argv[3] ); + + if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) ) + c.setProperty( obj , "_query" , argv[4] ); + else + c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( JS_NewObject( cx , 0 , 0 , 0 ) ) ); + + if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) + c.setProperty( obj , "_fields" , argv[5] ); + else + c.setProperty( obj , "_fields" , JSVAL_NULL ); + + + if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) ) + c.setProperty( obj , "_limit" , argv[6] ); + else + c.setProperty( obj , "_limit" , JSVAL_ZERO ); + + if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) ) + c.setProperty( obj , "_skip" , argv[7] ); + else + c.setProperty( obj , "_skip" , JSVAL_ZERO ); + + c.setProperty( obj , "_cursor" , JSVAL_NULL ); + c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); + c.setProperty( obj , "_special" , JSVAL_FALSE ); + + return JS_TRUE; + } + + JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ + if ( flags & JSRESOLVE_ASSIGNING ) + return JS_TRUE; + + if ( ! JSVAL_IS_NUMBER( id ) ) + return JS_TRUE; + + jsval val = JSVAL_VOID; + assert( JS_CallFunctionName( cx , obj , "arrayAccess" , 1 , &id , &val ) ); + Convertor c(cx); + c.setProperty( obj , c.toString( id ).c_str() , val ); + *objp = obj; + return JS_TRUE; + } + + JSClass dbquery_class = { + "DBQuery" , JSCLASS_NEW_RESOLVE , + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, (JSResolveOp)(&dbquery_resolve) , JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + + // ---- other stuff ---- + + void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ){ + + assert( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , object_id_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 0 , 0 , dbref_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , ×tamp_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) ); + + assert( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + assert( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) ); + + scope->exec( jsconcatcode ); + } + + bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , jsval val , JSObject * o ){ + + if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ){ + OID oid; + oid.init( c->getString( o , "str" ) ); + b.append( name.c_str() , oid ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ){ + b.appendMinKey( name.c_str() ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ){ + b.appendMaxKey( name.c_str() ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ){ + b.appendTimestamp( name.c_str() , (unsigned long long)c->getNumber( o , "t" ) , (unsigned int )c->getNumber( o , "i" ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &dbpointer_class , 0 ) ){ + b.appendDBRef( name.c_str() , c->getString( o , "ns" ).c_str() , c->toOID( c->getProperty( o , "id" ) ) ); + return true; + } + + if ( JS_InstanceOf( c->_context , o , &bindata_class , 0 ) ){ + b.appendBinData( name.c_str() , + (int)(c->getNumber( o , "len" )) , (BinDataType)((char)(c->getNumber( o , "type" ) ) ) , + (char*)JS_GetPrivate( c->_context , o ) + 1 + ); + return true; + } + +#if defined( SM16 ) || defined( MOZJS ) +#warning dates do not work in your version of spider monkey + { + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + if ( d ){ + b.appendDate( name.c_str() , Date_t(d) ); + return true; + } + } +#elif defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o, globalSMEngine->_dateClass , 0 ) ){ + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + b.appendDate( name.c_str() , Date_t(d) ); + return true; + } +#else + if ( JS_InstanceOf( c->_context , o, &js_DateClass , 0 ) ){ + jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); + //TODO: make signed + b.appendDate( name.c_str() , Date_t((unsigned long long)d) ); + return true; + } +#endif + + + if ( JS_InstanceOf( c->_context , o , &dbquery_class , 0 ) || + JS_InstanceOf( c->_context , o , &mongo_class , 0 ) || + JS_InstanceOf( c->_context , o , &db_collection_class , 0 ) ){ + b.append( name.c_str() , c->toString( val ) ); + return true; + } + +#if defined( XULRUNNER ) + if ( JS_InstanceOf( c->_context , o , globalSMEngine->_regexClass , 0 ) ){ + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#elif defined( SM18 ) + if ( JS_InstanceOf( c->_context , o , &js_RegExpClass , 0 ) ){ + c->appendRegex( b , name , c->toString( val ) ); + return true; + } +#endif + + return false; + } + + bool isDate( JSContext * cx , JSObject * o ){ +#if defined( SM16 ) || defined( MOZJS ) || defined( XULRUNNER ) + return js_DateGetMsecSinceEpoch( cx , o ) != 0; +#else + return JS_InstanceOf( cx , o, &js_DateClass, 0 ); +#endif + } + +} diff --git a/scripting/v8_db.cpp b/scripting/v8_db.cpp new file mode 100644 index 0000000..6d859d4 --- /dev/null +++ b/scripting/v8_db.cpp @@ -0,0 +1,542 @@ +// v8_db.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "v8_wrapper.h" +#include "v8_utils.h" +#include "v8_db.h" +#include "engine.h" + +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define CONN_STRING (v8::String::New( "_conn" )) + +#define DDD(x) + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ){ + v8::Local<v8::FunctionTemplate> mongo = FunctionTemplate::New( local ? mongoConsLocal : mongoConsExternal ); + + v8::Local<v8::Template> proto = mongo->PrototypeTemplate(); + + proto->Set( v8::String::New( "find" ) , FunctionTemplate::New( mongoFind ) ); + proto->Set( v8::String::New( "insert" ) , FunctionTemplate::New( mongoInsert ) ); + proto->Set( v8::String::New( "remove" ) , FunctionTemplate::New( mongoRemove ) ); + proto->Set( v8::String::New( "update" ) , FunctionTemplate::New( mongoUpdate ) ); + + Local<FunctionTemplate> ic = FunctionTemplate::New( internalCursorCons ); + ic->PrototypeTemplate()->Set( v8::String::New("next") , FunctionTemplate::New( internalCursorNext ) ); + ic->PrototypeTemplate()->Set( v8::String::New("hasNext") , FunctionTemplate::New( internalCursorHasNext ) ); + proto->Set( v8::String::New( "internalCursor" ) , ic ); + + return mongo; + } + + void installDBTypes( Handle<ObjectTemplate>& global ){ + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DB") , db ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DBCollection") , dbCollection ); + + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + global->Set(v8::String::New("DBQuery") , dbQuery ); + + global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit ) ); + + global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit ) ); + + global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit ) ); + + global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit ) ); + + } + + void installDBTypes( Handle<v8::Object>& global ){ + v8::Local<v8::FunctionTemplate> db = FunctionTemplate::New( dbInit ); + db->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DB") , db->GetFunction() ); + + v8::Local<v8::FunctionTemplate> dbCollection = FunctionTemplate::New( collectionInit ); + dbCollection->InstanceTemplate()->SetNamedPropertyHandler( collectionFallback ); + global->Set(v8::String::New("DBCollection") , dbCollection->GetFunction() ); + + + v8::Local<v8::FunctionTemplate> dbQuery = FunctionTemplate::New( dbQueryInit ); + dbQuery->InstanceTemplate()->SetIndexedPropertyHandler( dbQueryIndexAccess ); + global->Set(v8::String::New("DBQuery") , dbQuery->GetFunction() ); + + global->Set( v8::String::New("ObjectId") , FunctionTemplate::New( objectIdInit )->GetFunction() ); + + global->Set( v8::String::New("DBRef") , FunctionTemplate::New( dbRefInit )->GetFunction() ); + + global->Set( v8::String::New("DBPointer") , FunctionTemplate::New( dbPointerInit )->GetFunction() ); + + global->Set( v8::String::New("BinData") , FunctionTemplate::New( binDataInit )->GetFunction() ); + + BSONObjBuilder b; + b.appendMaxKey( "" ); + b.appendMinKey( "" ); + BSONObj o = b.obj(); + BSONObjIterator i( o ); + global->Set( v8::String::New("MaxKey"), mongoToV8Element( i.next() ) ); + global->Set( v8::String::New("MinKey"), mongoToV8Element( i.next() ) ); + + global->Get( v8::String::New( "Object" ) )->ToObject()->Set( v8::String::New("bsonsize") , FunctionTemplate::New( bsonsize )->GetFunction() ); + } + + void destroyConnection( Persistent<Value> object, void* parameter){ + cout << "Yo ho ho" << endl; + } + + Handle<Value> mongoConsExternal(const Arguments& args){ + + char host[255]; + + if ( args.Length() > 0 && args[0]->IsString() ){ + assert( args[0]->ToString()->Utf8Length() < 250 ); + args[0]->ToString()->WriteAscii( host ); + } + else { + strcpy( host , "127.0.0.1" ); + } + + DBClientConnection * conn = new DBClientConnection( true ); + + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); + self.MakeWeak( conn , destroyConnection ); + + string errmsg; + if ( ! conn->connect( host , errmsg ) ){ + return v8::ThrowException( v8::String::New( "couldn't connect" ) ); + } + + // NOTE I don't believe the conn object will ever be freed. + args.This()->Set( CONN_STRING , External::New( conn ) ); + args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( v8::String::New( "host" ) , v8::String::New( host ) ); + + return v8::Undefined(); + } + + Handle<Value> mongoConsLocal(const Arguments& args){ + + if ( args.Length() > 0 ) + return v8::ThrowException( v8::String::New( "local Mongo constructor takes no args" ) ); + + DBClientBase * conn = createDirectClient(); + + Persistent<v8::Object> self = Persistent<v8::Object>::New( args.This() ); + self.MakeWeak( conn , destroyConnection ); + + // NOTE I don't believe the conn object will ever be freed. + args.This()->Set( CONN_STRING , External::New( conn ) ); + args.This()->Set( v8::String::New( "slaveOk" ) , Boolean::New( false ) ); + args.This()->Set( v8::String::New( "host" ) , v8::String::New( "EMBEDDED" ) ); + + return v8::Undefined(); + } + + + // --- + +#ifdef _WIN32 +#define GETNS char * ns = new char[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#else +#define GETNS char ns[args[0]->ToString()->Utf8Length()]; args[0]->ToString()->WriteUtf8( ns ); +#endif + + DBClientBase * getConnection( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( CONN_STRING )) ); + DBClientBase * conn = (DBClientBase*)(c->Value()); + assert( conn ); + return conn; + } + + // ---- real methods + + /** + 0 - namespace + 1 - query + 2 - fields + 3 - limit + 4 - skip + */ + Handle<Value> mongoFind(const Arguments& args){ + jsassert( args.Length() == 5 , "find needs 5 args" ); + jsassert( args[1]->IsObject() , "needs to be an object" ); + DBClientBase * conn = getConnection( args ); + GETNS; + + BSONObj q = v8ToMongo( args[1]->ToObject() ); + DDD( "query:" << q ); + + BSONObj fields; + bool haveFields = args[2]->IsObject() && args[2]->ToObject()->GetPropertyNames()->Length() > 0; + if ( haveFields ) + fields = v8ToMongo( args[2]->ToObject() ); + + Local<v8::Object> mongo = args.This(); + Local<v8::Value> slaveOkVal = mongo->Get( v8::String::New( "slaveOk" ) ); + jsassert( slaveOkVal->IsBoolean(), "slaveOk member invalid" ); + bool slaveOk = slaveOkVal->BooleanValue(); + + try { + auto_ptr<mongo::DBClientCursor> cursor; + int nToReturn = (int)(args[3]->ToNumber()->Value()); + int nToSkip = (int)(args[4]->ToNumber()->Value()); + { + v8::Unlocker u; + cursor = conn->query( ns, q , nToReturn , nToSkip , haveFields ? &fields : 0, slaveOk ? QueryOption_SlaveOk : 0 ); + } + v8::Function * cons = (v8::Function*)( *( mongo->Get( v8::String::New( "internalCursor" ) ) ) ); + assert( cons ); + Local<v8::Object> c = cons->NewInstance(); + + // NOTE I don't believe the cursor object will ever be freed. + c->Set( v8::String::New( "cursor" ) , External::New( cursor.release() ) ); + return c; + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on query" ) ); + } + } + + v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "insert needs 2 args" ); + jsassert( args[1]->IsObject() , "have to insert an object" ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + + if ( ! in->Has( v8::String::New( "_id" ) ) ){ + v8::Handle<v8::Value> argv[1]; + in->Set( v8::String::New( "_id" ) , getObjectIdCons()->NewInstance( 0 , argv ) ); + } + + BSONObj o = v8ToMongo( in ); + + DDD( "want to save : " << o.jsonString() ); + try { + v8::Unlocker u; + conn->insert( ns , o ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on insert" ) ); + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args){ + jsassert( args.Length() == 2 , "remove needs 2 args" ); + jsassert( args[1]->IsObject() , "have to remove an object template" ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> in = args[1]->ToObject(); + BSONObj o = v8ToMongo( in ); + + DDD( "want to remove : " << o.jsonString() ); + try { + v8::Unlocker u; + conn->remove( ns , o ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args){ + jsassert( args.Length() >= 3 , "update needs at least 3 args" ); + jsassert( args[1]->IsObject() , "1st param to update has to be an object" ); + jsassert( args[2]->IsObject() , "2nd param to update has to be an object" ); + + DBClientBase * conn = getConnection( args ); + GETNS; + + v8::Handle<v8::Object> q = args[1]->ToObject(); + v8::Handle<v8::Object> o = args[2]->ToObject(); + + bool upsert = args.Length() > 3 && args[3]->IsBoolean() && args[3]->ToBoolean()->Value(); + bool multi = args.Length() > 4 && args[4]->IsBoolean() && args[4]->ToBoolean()->Value(); + + try { + BSONObj q1 = v8ToMongo( q ); + BSONObj o1 = v8ToMongo( o ); + v8::Unlocker u; + conn->update( ns , q1 , o1 , upsert, multi ); + } + catch ( ... ){ + return v8::ThrowException( v8::String::New( "socket error on remove" ) ); + } + + return v8::Undefined(); + } + + + + + // --- cursor --- + + mongo::DBClientCursor * getCursor( const Arguments& args ){ + Local<External> c = External::Cast( *(args.This()->Get( v8::String::New( "cursor" ) ) ) ); + mongo::DBClientCursor * cursor = (mongo::DBClientCursor*)(c->Value()); + return cursor; + } + + v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args){ + return v8::Undefined(); + } + + v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return v8::Undefined(); + BSONObj o; + { + v8::Unlocker u; + o = cursor->next(); + } + return mongoToV8( o ); + } + + v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args){ + mongo::DBClientCursor * cursor = getCursor( args ); + if ( ! cursor ) + return Boolean::New( false ); + bool ret; + { + v8::Unlocker u; + ret = cursor->more(); + } + return Boolean::New( ret ); + } + + + // --- DB ---- + + v8::Handle<v8::Value> dbInit(const v8::Arguments& args){ + assert( args.Length() == 2 ); + + args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); + args.This()->Set( v8::String::New( "_name" ) , args[1] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ){ + assert( args.Length() == 4 ); + + args.This()->Set( v8::String::New( "_mongo" ) , args[0] ); + args.This()->Set( v8::String::New( "_db" ) , args[1] ); + args.This()->Set( v8::String::New( "_shortName" ) , args[2] ); + args.This()->Set( v8::String::New( "_fullName" ) , args[3] ); + + for ( int i=0; i<args.Length(); i++ ) + assert( ! args[i]->IsUndefined() ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ){ + + v8::Handle<v8::Object> t = args.This(); + + assert( args.Length() >= 4 ); + + t->Set( v8::String::New( "_mongo" ) , args[0] ); + t->Set( v8::String::New( "_db" ) , args[1] ); + t->Set( v8::String::New( "_collection" ) , args[2] ); + t->Set( v8::String::New( "_ns" ) , args[3] ); + + if ( args.Length() > 4 && args[4]->IsObject() ) + t->Set( v8::String::New( "_query" ) , args[4] ); + else + t->Set( v8::String::New( "_query" ) , v8::Object::New() ); + + if ( args.Length() > 5 && args[5]->IsObject() ) + t->Set( v8::String::New( "_fields" ) , args[5] ); + else + t->Set( v8::String::New( "_fields" ) , v8::Null() ); + + + if ( args.Length() > 6 && args[6]->IsNumber() ) + t->Set( v8::String::New( "_limit" ) , args[6] ); + else + t->Set( v8::String::New( "_limit" ) , Number::New( 0 ) ); + + if ( args.Length() > 7 && args[7]->IsNumber() ) + t->Set( v8::String::New( "_skip" ) , args[7] ); + else + t->Set( v8::String::New( "_skip" ) , Number::New( 0 ) ); + + t->Set( v8::String::New( "_cursor" ) , v8::Null() ); + t->Set( v8::String::New( "_numReturned" ) , v8::Number::New(0) ); + t->Set( v8::String::New( "_special" ) , Boolean::New(false) ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info) { + DDD( "collectionFallback [" << name << "]" ); + + v8::Handle<v8::Value> real = info.This()->GetPrototype()->ToObject()->Get( name ); + if ( ! real->IsUndefined() ) + return real; + + string sname = toSTLString( name ); + if ( sname[0] == '_' ){ + if ( ! ( info.This()->HasRealNamedProperty( name ) ) ) + return v8::Undefined(); + return info.This()->GetRealNamedPropertyInPrototypeChain( name ); + } + + v8::Handle<v8::Value> getCollection = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "getCollection" ) ); + assert( getCollection->IsFunction() ); + + v8::Function * f = (v8::Function*)(*getCollection); + v8::Handle<v8::Value> argv[1]; + argv[0] = name; + + return f->Call( info.This() , 1 , argv ); + } + + v8::Handle<v8::Value> dbQueryIndexAccess( unsigned int index , const v8::AccessorInfo& info ){ + v8::Handle<v8::Value> arrayAccess = info.This()->GetPrototype()->ToObject()->Get( v8::String::New( "arrayAccess" ) ); + assert( arrayAccess->IsFunction() ); + + v8::Function * f = (v8::Function*)(*arrayAccess); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::Number::New( index ); + + return f->Call( info.This() , 1 , argv ); + } + + v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ){ + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function * f = getObjectIdCons(); + it = f->NewInstance(); + } + + OID oid; + + if ( args.Length() == 0 ){ + oid.init(); + } + else { + string s = toSTLString( args[0] ); + try { + Scope::validateObjectIdString( s ); + } catch ( const MsgAssertionException &m ) { + string error = m.toString(); + return v8::ThrowException( v8::String::New( error.c_str() ) ); + } + oid.init( s ); + } + + it->Set( v8::String::New( "str" ) , v8::String::New( oid.str().c_str() ) ); + + return it; + } + + v8::Handle<v8::Value> dbRefInit( const v8::Arguments& args ) { + + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "DBRef needs 2 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "DBRef" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "$ref" ) , args[0] ); + it->Set( v8::String::New( "$id" ) , args[1] ); + + return it; + } + + v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ) { + + if (args.Length() != 2) { + return v8::ThrowException( v8::String::New( "DBPointer needs 2 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "DBPointer" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "ns" ) , args[0] ); + it->Set( v8::String::New( "id" ) , args[1] ); + it->SetHiddenValue( v8::String::New( "__DBPointer" ), v8::Number::New( 1 ) ); + + return it; + } + + v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ) { + + if (args.Length() != 3) { + return v8::ThrowException( v8::String::New( "BinData needs 3 arguments" ) ); + } + + v8::Handle<v8::Object> it = args.This(); + + if ( it->IsUndefined() || it == v8::Context::GetCurrent()->Global() ){ + v8::Function* f = getNamedCons( "BinData" ); + it = f->NewInstance(); + } + + it->Set( v8::String::New( "len" ) , args[0] ); + it->Set( v8::String::New( "type" ) , args[1] ); + it->Set( v8::String::New( "data" ), args[2] ); + it->SetHiddenValue( v8::String::New( "__BinData" ), v8::Number::New( 1 ) ); + + return it; + } + + v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ) { + + if (args.Length() != 1 || !args[ 0 ]->IsObject()) { + return v8::ThrowException( v8::String::New( "bonsisze needs 1 object" ) ); + } + + return v8::Number::New( v8ToMongo( args[ 0 ]->ToObject() ).objsize() ); + } +} diff --git a/scripting/v8_db.h b/scripting/v8_db.h new file mode 100644 index 0000000..c3f2ef1 --- /dev/null +++ b/scripting/v8_db.h @@ -0,0 +1,71 @@ +// v8_db.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include "../client/dbclient.h" + +namespace mongo { + + // These functions may depend on the caller creating a handle scope and context scope. + + v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate( bool local ); + void installDBTypes( v8::Handle<v8::ObjectTemplate>& global ); + void installDBTypes( v8::Handle<v8::Object>& global ); + + // the actual globals + + mongo::DBClientBase * getConnection( const v8::Arguments& args ); + + // Mongo members + v8::Handle<v8::Value> mongoConsLocal(const v8::Arguments& args); + v8::Handle<v8::Value> mongoConsExternal(const v8::Arguments& args); + + v8::Handle<v8::Value> mongoFind(const v8::Arguments& args); + v8::Handle<v8::Value> mongoInsert(const v8::Arguments& args); + v8::Handle<v8::Value> mongoRemove(const v8::Arguments& args); + v8::Handle<v8::Value> mongoUpdate(const v8::Arguments& args); + + + v8::Handle<v8::Value> internalCursorCons(const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorNext(const v8::Arguments& args); + v8::Handle<v8::Value> internalCursorHasNext(const v8::Arguments& args); + + // DB members + + v8::Handle<v8::Value> dbInit(const v8::Arguments& args); + v8::Handle<v8::Value> collectionInit( const v8::Arguments& args ); + v8::Handle<v8::Value> objectIdInit( const v8::Arguments& args ); + + v8::Handle<v8::Value> dbRefInit( const v8::Arguments& args ); + v8::Handle<v8::Value> dbPointerInit( const v8::Arguments& args ); + + v8::Handle<v8::Value> binDataInit( const v8::Arguments& args ); + + v8::Handle<v8::Value> dbQueryInit( const v8::Arguments& args ); + v8::Handle<v8::Value> dbQueryIndexAccess( uint32_t index , const v8::AccessorInfo& info ); + + v8::Handle<v8::Value> collectionFallback( v8::Local<v8::String> name, const v8::AccessorInfo &info); + + v8::Handle<v8::Value> bsonsize( const v8::Arguments& args ); + +} diff --git a/scripting/v8_utils.cpp b/scripting/v8_utils.cpp new file mode 100644 index 0000000..5e56245 --- /dev/null +++ b/scripting/v8_utils.cpp @@ -0,0 +1,305 @@ +// v8_utils.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "v8_utils.h" +#include <iostream> +#include <map> +#include <sstream> +#include <vector> +#include <sys/socket.h> +#include <netinet/in.h> +#include <boost/smart_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/xtime.hpp> +#include "engine_v8.h" + +using namespace std; +using namespace v8; + +namespace mongo { + + Handle<v8::Value> Print(const Arguments& args) { + bool first = true; + for (int i = 0; i < args.Length(); i++) { + HandleScope handle_scope; + if (first) { + first = false; + } else { + printf(" "); + } + v8::String::Utf8Value str(args[i]); + printf("%s", *str); + } + printf("\n"); + return v8::Undefined(); + } + + std::string toSTLString( const Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + const char * foo = *str; + std::string s(foo); + return s; + } + + std::string toSTLString( const v8::TryCatch * try_catch ){ + + stringstream ss; + + //while ( try_catch ){ // disabled for v8 bleeding edge + + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + ss << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + ss << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + ss << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + ss << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + ss << "^"; + + ss << endl; + } + + //try_catch = try_catch->next_; + //} + + return ss.str(); + } + + + std::ostream& operator<<( std::ostream &s, const Handle<v8::Value> & o ){ + v8::String::Utf8Value str(o); + s << *str; + return s; + } + + std::ostream& operator<<( std::ostream &s, const v8::TryCatch * try_catch ){ + HandleScope handle_scope; + v8::String::Utf8Value exception(try_catch->Exception()); + Handle<v8::Message> message = try_catch->Message(); + + if (message.IsEmpty()) { + s << *exception << endl; + } + else { + + v8::String::Utf8Value filename(message->GetScriptResourceName()); + int linenum = message->GetLineNumber(); + cout << *filename << ":" << linenum << " " << *exception << endl; + + v8::String::Utf8Value sourceline(message->GetSourceLine()); + cout << *sourceline << endl; + + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) + cout << " "; + + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) + cout << "^"; + + cout << endl; + } + + //if ( try_catch->next_ ) // disabled for v8 bleeding edge + // s << try_catch->next_; + + return s; + } + + + Handle<v8::Value> Version(const Arguments& args) { + HandleScope handle_scope; + return handle_scope.Close( v8::String::New(v8::V8::GetVersion()) ); + } + + void ReportException(v8::TryCatch* try_catch) { + cout << try_catch << endl; + } + + Handle< Context > baseContext_; + + class JSThreadConfig { + public: + JSThreadConfig( const Arguments &args, bool newScope = false ) : started_(), done_(), newScope_( newScope ) { + jsassert( args.Length() > 0, "need at least one argument" ); + jsassert( args[ 0 ]->IsFunction(), "first argument must be a function" ); + Local< v8::Function > f = v8::Function::Cast( *args[ 0 ] ); + f_ = Persistent< v8::Function >::New( f ); + for( int i = 1; i < args.Length(); ++i ) + args_.push_back( Persistent< Value >::New( args[ i ] ) ); + } + ~JSThreadConfig() { + f_.Dispose(); + for( vector< Persistent< Value > >::iterator i = args_.begin(); i != args_.end(); ++i ) + i->Dispose(); + returnData_.Dispose(); + } + void start() { + jsassert( !started_, "Thread already started" ); + JSThread jt( *this ); + thread_.reset( new boost::thread( jt ) ); + started_ = true; + } + void join() { + jsassert( started_ && !done_, "Thread not running" ); + Unlocker u; + thread_->join(); + done_ = true; + } + Local< Value > returnData() { + if ( !done_ ) + join(); + return Local< Value >::New( returnData_ ); + } + private: + class JSThread { + public: + JSThread( JSThreadConfig &config ) : config_( config ) {} + void operator()() { + Locker l; + HandleScope handle_scope; + Handle< Context > context; + Handle< v8::Function > fun; + auto_ptr< V8Scope > scope; + if ( config_.newScope_ ) { + scope.reset( dynamic_cast< V8Scope * >( globalScriptEngine->newScope() ) ); + context = scope->context(); + // A v8::Function tracks the context in which it was created, so we have to + // create a new function in the new context. + Context::Scope baseScope( baseContext_ ); + string fCode = toSTLString( config_.f_->ToString() ); + Context::Scope context_scope( context ); + fun = scope->__createFunction( fCode.c_str() ); + } else { + context = baseContext_; + Context::Scope context_scope( context ); + fun = config_.f_; + } + Context::Scope context_scope( context ); + boost::scoped_array< Local< Value > > argv( new Local< Value >[ config_.args_.size() ] ); + for( unsigned int i = 0; i < config_.args_.size(); ++i ) + argv[ i ] = Local< Value >::New( config_.args_[ i ] ); + TryCatch try_catch; + Handle< Value > ret = fun->Call( context->Global(), config_.args_.size(), argv.get() ); + if ( ret.IsEmpty() ) { + string e = toSTLString( &try_catch ); + log() << "js thread raised exception: " << e << endl; + // v8 probably does something sane if ret is empty, but not going to assume that for now + ret = v8::Undefined(); + } + config_.returnData_ = Persistent< Value >::New( ret ); + } + private: + JSThreadConfig &config_; + }; + + bool started_; + bool done_; + bool newScope_; + Persistent< v8::Function > f_; + vector< Persistent< Value > > args_; + auto_ptr< boost::thread > thread_; + Persistent< Value > returnData_; + }; + + Handle< Value > ThreadInit( const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args ) ) ); + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInit( const Arguments &args ) { + Handle<v8::Object> it = args.This(); + // NOTE I believe the passed JSThreadConfig will never be freed. If this + // policy is changed, JSThread may no longer be able to store JSThreadConfig + // by reference. + it->SetHiddenValue( v8::String::New( "_JSThreadConfig" ), External::New( new JSThreadConfig( args, true ) ) ); + return v8::Undefined(); + } + + JSThreadConfig *thisConfig( const Arguments &args ) { + Local< External > c = External::Cast( *(args.This()->GetHiddenValue( v8::String::New( "_JSThreadConfig" ) ) ) ); + JSThreadConfig *config = (JSThreadConfig *)( c->Value() ); + return config; + } + + Handle< Value > ThreadStart( const Arguments &args ) { + thisConfig( args )->start(); + return v8::Undefined(); + } + + Handle< Value > ThreadJoin( const Arguments &args ) { + thisConfig( args )->join(); + return v8::Undefined(); + } + + Handle< Value > ThreadReturnData( const Arguments &args ) { + HandleScope handle_scope; + return handle_scope.Close( thisConfig( args )->returnData() ); + } + + Handle< Value > ThreadInject( const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ThreadInit )->GetFunction() ); + o->Set( v8::String::New( "start" ) , FunctionTemplate::New( ThreadStart )->GetFunction() ); + o->Set( v8::String::New( "join" ) , FunctionTemplate::New( ThreadJoin )->GetFunction() ); + o->Set( v8::String::New( "returnData" ) , FunctionTemplate::New( ThreadReturnData )->GetFunction() ); + + return v8::Undefined(); + } + + Handle< Value > ScopedThreadInject( const Arguments &args ) { + jsassert( args.Length() == 1 , "threadInject takes exactly 1 argument" ); + jsassert( args[0]->IsObject() , "threadInject needs to be passed a prototype" ); + + Local<v8::Object> o = args[0]->ToObject(); + + o->Set( v8::String::New( "init" ) , FunctionTemplate::New( ScopedThreadInit )->GetFunction() ); + // inheritance takes care of other member functions + + return v8::Undefined(); + } + + void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ) { + if ( baseContext_.IsEmpty() ) // if this is the shell, first call will be with shell context, otherwise don't expect to use fork() anyway + baseContext_ = context; + global->Set( v8::String::New( "_threadInject" ), FunctionTemplate::New( ThreadInject )->GetFunction() ); + global->Set( v8::String::New( "_scopedThreadInject" ), FunctionTemplate::New( ScopedThreadInject )->GetFunction() ); + } + +} diff --git a/scripting/v8_utils.h b/scripting/v8_utils.h new file mode 100644 index 0000000..8218455 --- /dev/null +++ b/scripting/v8_utils.h @@ -0,0 +1,46 @@ +// v8_utils.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> + +namespace mongo { + + v8::Handle<v8::Value> Print(const v8::Arguments& args); + v8::Handle<v8::Value> Version(const v8::Arguments& args); + + void ReportException(v8::TryCatch* handler); + +#define jsassert(x,msg) assert(x) + + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::Value> & o ); + std::ostream& operator<<( std::ostream &s, const v8::Handle<v8::TryCatch> * try_catch ); + + std::string toSTLString( const v8::Handle<v8::Value> & o ); + std::string toSTLString( const v8::TryCatch * try_catch ); + + class V8Scope; + void installFork( v8::Handle< v8::Object > &global, v8::Handle< v8::Context > &context ); +} + diff --git a/scripting/v8_wrapper.cpp b/scripting/v8_wrapper.cpp new file mode 100644 index 0000000..29a70ba --- /dev/null +++ b/scripting/v8_wrapper.cpp @@ -0,0 +1,575 @@ +// v8_wrapper.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "v8_wrapper.h" +#include "v8_utils.h" + +#include <iostream> + +using namespace std; +using namespace v8; + +namespace mongo { + +#define CONN_STRING (v8::String::New( "_conn" )) + +#define DDD(x) + + Handle<Value> NamedReadOnlySet( Local<v8::String> property, Local<Value> value, const AccessorInfo& info ) { + cout << "cannot write to read-only object" << endl; + return value; + } + + Handle<Boolean> NamedReadOnlyDelete( Local<v8::String> property, const AccessorInfo& info ) { + cout << "cannot delete from read-only object" << endl; + return Boolean::New( false ); + } + + Handle<Value> IndexedReadOnlySet( uint32_t index, Local<Value> value, const AccessorInfo& info ) { + cout << "cannot write to read-only array" << endl; + return value; + } + + Handle<Boolean> IndexedReadOnlyDelete( uint32_t index, const AccessorInfo& info ) { + cout << "cannot delete from read-only array" << endl; + return Boolean::New( false ); + } + + Local< v8::Value > newFunction( const char *code ) { + stringstream codeSS; + codeSS << "____MontoToV8_newFunction_temp = " << code; + string codeStr = codeSS.str(); + Local< Script > compiled = Script::New( v8::String::New( codeStr.c_str() ) ); + Local< Value > ret = compiled->Run(); + return ret; + } + + Local< v8::Value > newId( const OID &id ) { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( id.str().c_str() ); + return idCons->NewInstance( 1 , argv ); + } + + Local<v8::Object> mongoToV8( const BSONObj& m , bool array, bool readOnly ){ + + // handle DBRef. needs to come first. isn't it? (metagoto) + static string ref = "$ref"; + if ( ref == m.firstElement().fieldName() ) { + const BSONElement& id = m["$id"]; + if (!id.eoo()) { // there's no check on $id exitence in sm implementation. risky ? + v8::Function* dbRef = getNamedCons( "DBRef" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = mongoToV8Element(m.firstElement()); + argv[1] = mongoToV8Element(m["$id"]); + return dbRef->NewInstance(2, argv); + } + } + + Local< v8::ObjectTemplate > readOnlyObjects; + // Hoping template construction is fast... + Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); + internalFieldObjects->SetInternalFieldCount( 1 ); + + Local<v8::Object> o; + if ( array ) { + // NOTE Looks like it's impossible to add interceptors to non array objects in v8. + o = v8::Array::New(); + } else if ( !readOnly ) { + o = v8::Object::New(); + } else { + // NOTE Our readOnly implemention relies on undocumented ObjectTemplate + // functionality that may be fragile, but it still seems like the best option + // for now -- fwiw, the v8 docs are pretty sparse. I've determined experimentally + // that when property handlers are set for an object template, they will attach + // to objects previously created by that template. To get this to work, though, + // it is necessary to initialize the template's property handlers before + // creating objects from the template (as I have in the following few lines + // of code). + // NOTE In my first attempt, I configured the permanent property handlers before + // constructiong the object and replaced the Set() calls below with ForceSet(). + // However, it turns out that ForceSet() only bypasses handlers for named + // properties and not for indexed properties. + readOnlyObjects = v8::ObjectTemplate::New(); + // NOTE This internal field will store type info for special db types. For + // regular objects the field is unnecessary - for simplicity I'm creating just + // one readOnlyObjects template for objects where the field is & isn't necessary, + // assuming that the overhead of an internal field is slight. + readOnlyObjects->SetInternalFieldCount( 1 ); + readOnlyObjects->SetNamedPropertyHandler( 0 ); + readOnlyObjects->SetIndexedPropertyHandler( 0 ); + o = readOnlyObjects->NewInstance(); + } + + mongo::BSONObj sub; + + for ( BSONObjIterator i(m); i.more(); ) { + const BSONElement& f = i.next(); + + Local<Value> v; + + switch ( f.type() ){ + + case mongo::Code: + o->Set( v8::String::New( f.fieldName() ), newFunction( f.valuestr() ) ); + break; + + case CodeWScope: + if ( f.codeWScopeObject().isEmpty() ) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + o->Set( v8::String::New( f.fieldName() ), newFunction( f.codeWScopeCode() ) ); + break; + + case mongo::String: + o->Set( v8::String::New( f.fieldName() ) , v8::String::New( f.valuestr() ) ); + break; + + case mongo::jstOID: { + v8::Function * idCons = getObjectIdCons(); + v8::Handle<v8::Value> argv[1]; + argv[0] = v8::String::New( f.__oid().str().c_str() ); + o->Set( v8::String::New( f.fieldName() ) , + idCons->NewInstance( 1 , argv ) ); + break; + } + + case mongo::NumberDouble: + case mongo::NumberInt: + case mongo::NumberLong: // may lose information here - just copying sm engine behavior + o->Set( v8::String::New( f.fieldName() ) , v8::Number::New( f.number() ) ); + break; + + case mongo::Array: + case mongo::Object: + sub = f.embeddedObject(); + o->Set( v8::String::New( f.fieldName() ) , mongoToV8( sub , f.type() == mongo::Array, readOnly ) ); + break; + + case mongo::Date: + o->Set( v8::String::New( f.fieldName() ) , v8::Date::New( f.date() ) ); + break; + + case mongo::Bool: + o->Set( v8::String::New( f.fieldName() ) , v8::Boolean::New( f.boolean() ) ); + break; + + case mongo::jstNULL: + o->Set( v8::String::New( f.fieldName() ) , v8::Null() ); + break; + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + o->Set( v8::String::New( f.fieldName() ) , regex->NewInstance( 2 , argv ) ); + break; + } + + case mongo::BinData: { + Local<v8::Object> b = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + + int len; + const char *data = f.binData( len ); + + v8::Function* binData = getNamedCons( "BinData" ); + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( len ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + o->Set( v8::String::New( f.fieldName() ), binData->NewInstance(3, argv) ); + break; + } + + case mongo::Timestamp: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + + sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + + o->Set( v8::String::New( f.fieldName() ) , sub ); + break; + } + + case mongo::MinKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( v8::String::New( f.fieldName() ) , sub ); + break; + } + + case mongo::MaxKey: { + Local<v8::Object> sub = readOnly ? readOnlyObjects->NewInstance() : internalFieldObjects->NewInstance(); + sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + o->Set( v8::String::New( f.fieldName() ) , sub ); + break; + } + + case mongo::Undefined: + o->Set( v8::String::New( f.fieldName() ), v8::Undefined() ); + break; + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + o->Set( v8::String::New( f.fieldName() ), dbPointer->NewInstance(2, argv) ); + break; + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + } + + if ( !array && readOnly ) { + readOnlyObjects->SetNamedPropertyHandler( 0, NamedReadOnlySet, 0, NamedReadOnlyDelete ); + readOnlyObjects->SetIndexedPropertyHandler( 0, IndexedReadOnlySet, 0, IndexedReadOnlyDelete ); + } + + return o; + } + + Handle<v8::Value> mongoToV8Element( const BSONElement &f ) { + Local< v8::ObjectTemplate > internalFieldObjects = v8::ObjectTemplate::New(); + internalFieldObjects->SetInternalFieldCount( 1 ); + + switch ( f.type() ){ + + case mongo::Code: + return newFunction( f.valuestr() ); + + case CodeWScope: + if ( f.codeWScopeObject().isEmpty() ) + log() << "warning: CodeWScope doesn't transfer to db.eval" << endl; + return newFunction( f.codeWScopeCode() ); + + case mongo::String: + return v8::String::New( f.valuestr() ); + + case mongo::jstOID: + return newId( f.__oid() ); + + case mongo::NumberDouble: + case mongo::NumberInt: + return v8::Number::New( f.number() ); + + case mongo::Array: + case mongo::Object: + return mongoToV8( f.embeddedObject() , f.type() == mongo::Array ); + + case mongo::Date: + return v8::Date::New( f.date() ); + + case mongo::Bool: + return v8::Boolean::New( f.boolean() ); + + case mongo::EOO: + case mongo::jstNULL: + return v8::Null(); + + case mongo::RegEx: { + v8::Function * regex = getNamedCons( "RegExp" ); + + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.regex() ); + argv[1] = v8::String::New( f.regexFlags() ); + + return regex->NewInstance( 2 , argv ); + break; + } + + case mongo::BinData: { + int len; + const char *data = f.binData( len ); + + v8::Function* binData = getNamedCons( "BinData" ); + v8::Handle<v8::Value> argv[3]; + argv[0] = v8::Number::New( len ); + argv[1] = v8::Number::New( f.binDataType() ); + argv[2] = v8::String::New( data, len ); + return binData->NewInstance( 3, argv ); + }; + + case mongo::Timestamp: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + + sub->Set( v8::String::New( "time" ) , v8::Date::New( f.timestampTime() ) ); + sub->Set( v8::String::New( "i" ) , v8::Number::New( f.timestampInc() ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + + return sub; + } + + case mongo::MinKey: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + sub->Set( v8::String::New( "$MinKey" ), v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + return sub; + } + + case mongo::MaxKey: { + Local<v8::Object> sub = internalFieldObjects->NewInstance(); + sub->Set( v8::String::New( "$MaxKey" ), v8::Boolean::New( true ) ); + sub->SetInternalField( 0, v8::Uint32::New( f.type() ) ); + return sub; + } + + case mongo::Undefined: + return v8::Undefined(); + + case mongo::DBRef: { + v8::Function* dbPointer = getNamedCons( "DBPointer" ); + v8::Handle<v8::Value> argv[2]; + argv[0] = v8::String::New( f.dbrefNS() ); + argv[1] = newId( f.dbrefOID() ); + return dbPointer->NewInstance(2, argv); + } + + default: + cout << "can't handle type: "; + cout << f.type() << " "; + cout << f.toString(); + cout << endl; + break; + } + + return v8::Undefined(); + } + + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , const string sname , v8::Handle<v8::Value> value ){ + + if ( value->IsString() ){ + b.append( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsFunction() ){ + b.appendCode( sname.c_str() , toSTLString( value ).c_str() ); + return; + } + + if ( value->IsNumber() ){ + if ( value->IsInt32() ) + b.append( sname.c_str(), int( value->ToInt32()->Value() ) ); + else + b.append( sname.c_str() , value->ToNumber()->Value() ); + return; + } + + if ( value->IsArray() ){ + BSONObj sub = v8ToMongo( value->ToObject() ); + b.appendArray( sname.c_str() , sub ); + return; + } + + if ( value->IsDate() ){ + b.appendDate( sname.c_str() , Date_t(v8::Date::Cast( *value )->NumberValue()) ); + return; + } + + if ( value->IsExternal() ) + return; + + if ( value->IsObject() ){ + // The user could potentially modify the fields of these special objects, + // wreaking havoc when we attempt to reinterpret them. Not doing any validation + // for now... + Local< v8::Object > obj = value->ToObject(); + if ( obj->InternalFieldCount() && obj->GetInternalField( 0 )->IsNumber() ) { + switch( obj->GetInternalField( 0 )->ToInt32()->Value() ) { // NOTE Uint32's Value() gave me a linking error, so going with this instead + case Timestamp: + b.appendTimestamp( sname.c_str(), + Date_t( v8::Date::Cast( *obj->Get( v8::String::New( "time" ) ) )->NumberValue() ), + obj->Get( v8::String::New( "i" ) )->ToInt32()->Value() ); + return; + case MinKey: + b.appendMinKey( sname.c_str() ); + return; + case MaxKey: + b.appendMaxKey( sname.c_str() ); + return; + default: + assert( "invalid internal field" == 0 ); + } + } + string s = toSTLString( value ); + if ( s.size() && s[0] == '/' ){ + s = s.substr( 1 ); + string r = s.substr( 0 , s.find( "/" ) ); + string o = s.substr( s.find( "/" ) + 1 ); + b.appendRegex( sname.c_str() , r.c_str() , o.c_str() ); + } + else if ( value->ToObject()->GetPrototype()->IsObject() && + value->ToObject()->GetPrototype()->ToObject()->HasRealNamedProperty( v8::String::New( "isObjectId" ) ) ){ + OID oid; + oid.init( toSTLString( value ) ); + b.appendOID( sname.c_str() , &oid ); + } + else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__DBPointer" ) ).IsEmpty() ) { + // TODO might be nice to potentially speed this up with an indexed internal + // field, but I don't yet know how to use an ObjectTemplate with a + // constructor. + OID oid; + oid.init( toSTLString( value->ToObject()->Get( v8::String::New( "id" ) ) ) ); + string ns = toSTLString( value->ToObject()->Get( v8::String::New( "ns" ) ) ); + b.appendDBRef( sname.c_str(), ns.c_str(), oid ); + } + else if ( !value->ToObject()->GetHiddenValue( v8::String::New( "__BinData" ) ).IsEmpty() ) { + int len = obj->Get( v8::String::New( "len" ) )->ToInt32()->Value(); + v8::String::Utf8Value data( obj->Get( v8::String::New( "data" ) ) ); + const char *dataArray = *data; + assert( data.length() == len ); + b.appendBinData( sname.c_str(), + len, + mongo::BinDataType( obj->Get( v8::String::New( "type" ) )->ToInt32()->Value() ), + dataArray ); + } else { + BSONObj sub = v8ToMongo( value->ToObject() ); + b.append( sname.c_str() , sub ); + } + return; + } + + if ( value->IsBoolean() ){ + b.appendBool( sname.c_str() , value->ToBoolean()->Value() ); + return; + } + + else if ( value->IsUndefined() ){ + b.appendUndefined( sname.c_str() ); + return; + } + + else if ( value->IsNull() ){ + b.appendNull( sname.c_str() ); + return; + } + + cout << "don't know how to convert to mongo field [" << name << "]\t" << value << endl; + } + + BSONObj v8ToMongo( v8::Handle<v8::Object> o ){ + BSONObjBuilder b; + + v8::Handle<v8::String> idName = v8::String::New( "_id" ); + if ( o->HasRealNamedProperty( idName ) ){ + v8ToMongoElement( b , idName , "_id" , o->Get( idName ) ); + } + + Local<v8::Array> names = o->GetPropertyNames(); + for ( unsigned int i=0; i<names->Length(); i++ ){ + v8::Local<v8::String> name = names->Get(v8::Integer::New(i) )->ToString(); + + if ( o->GetPrototype()->IsObject() && + o->GetPrototype()->ToObject()->HasRealNamedProperty( name ) ) + continue; + + v8::Local<v8::Value> value = o->Get( name ); + + const string sname = toSTLString( name ); + if ( sname == "_id" ) + continue; + + v8ToMongoElement( b , name , sname , value ); + } + return b.obj(); + } + + // --- object wrapper --- + + class WrapperHolder { + public: + WrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ) + : _o(o), _readOnly( readOnly ), _iDelete( iDelete ) { + } + + ~WrapperHolder(){ + if ( _o && _iDelete ){ + delete _o; + } + _o = 0; + } + + v8::Handle<v8::Value> get( v8::Local<v8::String> name ){ + const string& s = toSTLString( name ); + const BSONElement& e = _o->getField( s ); + return mongoToV8Element(e); + } + + const BSONObj * _o; + bool _readOnly; + bool _iDelete; + }; + + WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ){ + return new WrapperHolder( o , readOnly , iDelete ); + } + +#define WRAPPER_STRING (v8::String::New( "_wrapper" ) ) + + WrapperHolder * getWrapper( v8::Handle<v8::Object> o ){ + Handle<v8::Value> t = o->GetRealNamedProperty( WRAPPER_STRING ); + assert( t->IsExternal() ); + Local<External> c = External::Cast( *t ); + WrapperHolder * w = (WrapperHolder*)(c->Value()); + assert( w ); + return w; + } + + + Handle<Value> wrapperCons(const Arguments& args){ + if ( ! ( args.Length() == 1 && args[0]->IsExternal() ) ) + return v8::ThrowException( v8::String::New( "wrapperCons needs 1 External arg" ) ); + + args.This()->Set( WRAPPER_STRING , args[0] ); + + return v8::Undefined(); + } + + v8::Handle<v8::Value> wrapperGetHandler( v8::Local<v8::String> name, const v8::AccessorInfo &info){ + return getWrapper( info.This() )->get( name ); + } + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(){ + v8::Local<v8::FunctionTemplate> t = FunctionTemplate::New( wrapperCons ); + t->InstanceTemplate()->SetNamedPropertyHandler( wrapperGetHandler ); + return t; + } + + // --- random utils ---- + + v8::Function * getNamedCons( const char * name ){ + return v8::Function::Cast( *(v8::Context::GetCurrent()->Global()->Get( v8::String::New( name ) ) ) ); + } + + v8::Function * getObjectIdCons(){ + return getNamedCons( "ObjectId" ); + } + +} diff --git a/scripting/v8_wrapper.h b/scripting/v8_wrapper.h new file mode 100644 index 0000000..1d67cf1 --- /dev/null +++ b/scripting/v8_wrapper.h @@ -0,0 +1,43 @@ +// v8_wrapper.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <v8.h> +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include "../db/jsobj.h" + +namespace mongo { + + v8::Local<v8::Object> mongoToV8( const mongo::BSONObj & m , bool array = 0 , bool readOnly = false ); + mongo::BSONObj v8ToMongo( v8::Handle<v8::Object> o ); + + void v8ToMongoElement( BSONObjBuilder & b , v8::Handle<v8::String> name , + const string sname , v8::Handle<v8::Value> value ); + v8::Handle<v8::Value> mongoToV8Element( const BSONElement &f ); + + v8::Function * getNamedCons( const char * name ); + v8::Function * getObjectIdCons(); + + v8::Handle<v8::FunctionTemplate> getObjectWrapperTemplate(); + + class WrapperHolder; + WrapperHolder * createWrapperHolder( const BSONObj * o , bool readOnly , bool iDelete ); + +} diff --git a/shell/collection.js b/shell/collection.js new file mode 100644 index 0000000..d228ba7 --- /dev/null +++ b/shell/collection.js @@ -0,0 +1,569 @@ +// collection.js + + +if ( ( typeof DBCollection ) == "undefined" ){ + DBCollection = function( mongo , db , shortName , fullName ){ + this._mongo = mongo; + this._db = db; + this._shortName = shortName; + this._fullName = fullName; + + this.verify(); + } +} + +DBCollection.prototype.verify = function(){ + assert( this._fullName , "no fullName" ); + assert( this._shortName , "no shortName" ); + assert( this._db , "no db" ); + + assert.eq( this._fullName , this._db._name + "." + this._shortName , "name mismatch" ); + + assert( this._mongo , "no mongo in DBCollection" ); +} + +DBCollection.prototype.getName = function(){ + return this._shortName; +} + +DBCollection.prototype.help = function(){ + print("DBCollection help"); + print("\tdb.foo.count()"); + print("\tdb.foo.dataSize()"); + print("\tdb.foo.distinct( key ) - eg. db.foo.distinct( 'x' )" ); + print("\tdb.foo.drop() drop the collection"); + print("\tdb.foo.dropIndex(name)"); + print("\tdb.foo.dropIndexes()"); + print("\tdb.foo.ensureIndex(keypattern,options) - options should be an object with these possible fields: name, unique, dropDups"); + print("\tdb.foo.find( [query] , [fields]) - first parameter is an optional query filter. second parameter is optional set of fields to return."); + print("\t e.g. db.foo.find( { x : 77 } , { name : 1 , x : 1 } )"); + print("\tdb.foo.find(...).count()"); + print("\tdb.foo.find(...).limit(n)"); + print("\tdb.foo.find(...).skip(n)"); + print("\tdb.foo.find(...).sort(...)"); + print("\tdb.foo.findOne([query])"); + print("\tdb.foo.findAndModify( { update : ... , remove : bool [, query: {}, sort: {}, 'new': false] } )"); + print("\tdb.foo.getDB() get DB object associated with collection"); + print("\tdb.foo.getIndexes()"); + print("\tdb.foo.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )"); + print("\tdb.foo.mapReduce( mapFunction , reduceFunction , <optional params> )" ); + print("\tdb.foo.remove(query)" ); + print("\tdb.foo.renameCollection( newName , <dropTarget> ) renames the collection."); + print("\tdb.foo.save(obj)"); + print("\tdb.foo.stats()"); + print("\tdb.foo.storageSize() - includes free space allocated to this collection"); + print("\tdb.foo.totalIndexSize() - size in bytes of all the indexes"); + print("\tdb.foo.totalSize() - storage allocated for all data and indexes"); + print("\tdb.foo.update(query, object[, upsert_bool, multi_bool])"); + print("\tdb.foo.validate() - SLOW"); + print("\tdb.foo.getShardVersion() - only for use with sharding" ); +} + +DBCollection.prototype.getFullName = function(){ + return this._fullName; +} +DBCollection.prototype.getDB = function(){ + return this._db; +} + +DBCollection.prototype._dbCommand = function( cmd ){ + return this._db._dbCommand( cmd ); +} + +DBCollection.prototype._massageObject = function( q ){ + if ( ! q ) + return {}; + + var type = typeof q; + + if ( type == "function" ) + return { $where : q }; + + if ( q.isObjectId ) + return { _id : q }; + + if ( type == "object" ) + return q; + + if ( type == "string" ){ + if ( q.length == 24 ) + return { _id : q }; + + return { $where : q }; + } + + throw "don't know how to massage : " + type; + +} + + +DBCollection.prototype._validateObject = function( o ){ + if ( o._ensureSpecial && o._checkModify ) + throw "can't save a DBQuery object"; +} + +DBCollection._allowedFields = { $id : 1 , $ref : 1 }; + +DBCollection.prototype._validateForStorage = function( o ){ + this._validateObject( o ); + for ( var k in o ){ + if ( k.indexOf( "." ) >= 0 ) { + throw "can't have . in field names [" + k + "]" ; + } + + if ( k.indexOf( "$" ) == 0 && ! DBCollection._allowedFields[k] ) { + throw "field names cannot start with $ [" + k + "]"; + } + + if ( o[k] !== null && typeof( o[k] ) === "object" ) { + this._validateForStorage( o[k] ); + } + } +}; + + +DBCollection.prototype.find = function( query , fields , limit , skip ){ + return new DBQuery( this._mongo , this._db , this , + this._fullName , this._massageObject( query ) , fields , limit , skip ); +} + +DBCollection.prototype.findOne = function( query , fields ){ + var cursor = this._mongo.find( this._fullName , this._massageObject( query ) || {} , fields , -1 , 0 ); + if ( ! cursor.hasNext() ) + return null; + var ret = cursor.next(); + if ( cursor.hasNext() ) throw "findOne has more than 1 result!"; + if ( ret.$err ) + throw "error " + tojson( ret ); + return ret; +} + +DBCollection.prototype.insert = function( obj , _allow_dot ){ + if ( ! obj ) + throw "no object passed to insert!"; + if ( ! _allow_dot ) { + this._validateForStorage( obj ); + } + return this._mongo.insert( this._fullName , obj ); +} + +DBCollection.prototype.remove = function( t ){ + this._mongo.remove( this._fullName , this._massageObject( t ) ); +} + +DBCollection.prototype.update = function( query , obj , upsert , multi ){ + assert( query , "need a query" ); + assert( obj , "need an object" ); + this._validateObject( obj ); + this._mongo.update( this._fullName , query , obj , upsert ? true : false , multi ? true : false ); +} + +DBCollection.prototype.save = function( obj ){ + if ( obj == null || typeof( obj ) == "undefined" ) + throw "can't save a null"; + + if ( typeof( obj._id ) == "undefined" ){ + obj._id = new ObjectId(); + return this.insert( obj ); + } + else { + return this.update( { _id : obj._id } , obj , true ); + } +} + +DBCollection.prototype._genIndexName = function( keys ){ + var name = ""; + for ( var k in keys ){ + var v = keys[k]; + if ( typeof v == "function" ) + continue; + + if ( name.length > 0 ) + name += "_"; + name += k + "_"; + + if ( typeof v == "number" ) + name += v; + } + return name; +} + +DBCollection.prototype._indexSpec = function( keys, options ) { + var ret = { ns : this._fullName , key : keys , name : this._genIndexName( keys ) }; + + if ( ! options ){ + } + else if ( typeof ( options ) == "string" ) + ret.name = options; + else if ( typeof ( options ) == "boolean" ) + ret.unique = true; + else if ( typeof ( options ) == "object" ){ + if ( options.length ){ + var nb = 0; + for ( var i=0; i<options.length; i++ ){ + if ( typeof ( options[i] ) == "string" ) + ret.name = options[i]; + else if ( typeof( options[i] ) == "boolean" ){ + if ( options[i] ){ + if ( nb == 0 ) + ret.unique = true; + if ( nb == 1 ) + ret.dropDups = true; + } + nb++; + } + } + } + else { + Object.extend( ret , options ); + } + } + else { + throw "can't handle: " + typeof( options ); + } + /* + return ret; + + var name; + var nTrue = 0; + + if ( ! isObject( options ) ) { + options = [ options ]; + } + + if ( options.length ){ + for( var i = 0; i < options.length; ++i ) { + var o = options[ i ]; + if ( isString( o ) ) { + ret.name = o; + } else if ( typeof( o ) == "boolean" ) { + if ( o ) { + ++nTrue; + } + } + } + if ( nTrue > 0 ) { + ret.unique = true; + } + if ( nTrue > 1 ) { + ret.dropDups = true; + } + } +*/ + return ret; +} + +DBCollection.prototype.createIndex = function( keys , options ){ + var o = this._indexSpec( keys, options ); + this._db.getCollection( "system.indexes" ).insert( o , true ); +} + +DBCollection.prototype.ensureIndex = function( keys , options ){ + var name = this._indexSpec( keys, options ).name; + this._indexCache = this._indexCache || {}; + if ( this._indexCache[ name ] ){ + return; + } + + this.createIndex( keys , options ); + if ( this.getDB().getLastError() == "" ) { + this._indexCache[name] = true; + } +} + +DBCollection.prototype.resetIndexCache = function(){ + this._indexCache = {}; +} + +DBCollection.prototype.reIndex = function(){ + var specs = this.getIndexSpecs(); + this.dropIndexes(); + for ( var i = 0; i < specs.length; ++i ){ + this.ensureIndex( specs[i].key, [ specs[i].unique, specs[i].name ] ); + } +} + +DBCollection.prototype.dropIndexes = function(){ + this.resetIndexCache(); + + var res = this._db.runCommand( { deleteIndexes: this.getName(), index: "*" } ); + assert( res , "no result from dropIndex result" ); + if ( res.ok ) + return res; + + if ( res.errmsg.match( /not found/ ) ) + return res; + + throw "error dropping indexes : " + tojson( res ); +} + + +DBCollection.prototype.drop = function(){ + this.resetIndexCache(); + var ret = this._db.runCommand( { drop: this.getName() } ); + if ( ! ret.ok ){ + if ( ret.errmsg == "ns not found" ) + return false; + throw "drop failed: " + tojson( ret ); + } + return true; +} + +DBCollection.prototype.findAndModify = function(args){ + var cmd = { findandmodify: this.getName() }; + for (key in args){ + cmd[key] = args[key]; + } + + var ret = this._db.runCommand( cmd ); + if ( ! ret.ok ){ + if (ret.errmsg == "No matching object found"){ + return {}; + } + throw "findAndModifyFailed failed: " + tojson( ret.errmsg ); + } + return ret.value; +} + +DBCollection.prototype.renameCollection = function( newName , dropTarget ){ + return this._db._adminCommand( { renameCollection : this._fullName , + to : this._db._name + "." + newName , + dropTarget : dropTarget } ) +} + +DBCollection.prototype.validate = function() { + var res = this._db.runCommand( { validate: this.getName() } ); + + res.valid = false; + + if ( res.result ){ + var str = "-" + tojson( res.result ); + res.valid = ! ( str.match( /exception/ ) || str.match( /corrupt/ ) ); + + var p = /lastExtentSize:(\d+)/; + var r = p.exec( str ); + if ( r ){ + res.lastExtentSize = Number( r[1] ); + } + } + + return res; +} + +DBCollection.prototype.getShardVersion = function(){ + return this._db._adminCommand( { getShardVersion : this._fullName } ); +} + +DBCollection.prototype.getIndexes = function(){ + return this.getDB().getCollection( "system.indexes" ).find( { ns : this.getFullName() } ).toArray(); +} + +DBCollection.prototype.getIndices = DBCollection.prototype.getIndexes; +DBCollection.prototype.getIndexSpecs = DBCollection.prototype.getIndexes; + +DBCollection.prototype.getIndexKeys = function(){ + return this.getIndexes().map( + function(i){ + return i.key; + } + ); +} + + +DBCollection.prototype.count = function( x ){ + return this.find( x ).count(); +} + +/** + * Drop free lists. Normally not used. + * Note this only does the collection itself, not the namespaces of its indexes (see cleanAll). + */ +DBCollection.prototype.clean = function() { + return this._dbCommand( { clean: this.getName() } ); +} + + + +/** + * <p>Drop a specified index.</p> + * + * <p> + * Name is the name of the index in the system.indexes name field. (Run db.system.indexes.find() to + * see example data.) + * </p> + * + * <p>Note : alpha: space is not reclaimed </p> + * @param {String} name of index to delete. + * @return A result object. result.ok will be true if successful. + */ +DBCollection.prototype.dropIndex = function(index) { + assert(index , "need to specify index to dropIndex" ); + + if ( ! isString( index ) && isObject( index ) ) + index = this._genIndexName( index ); + + var res = this._dbCommand( { deleteIndexes: this.getName(), index: index } ); + this.resetIndexCache(); + return res; +} + +DBCollection.prototype.copyTo = function( newName ){ + return this.getDB().eval( + function( collName , newName ){ + var from = db[collName]; + var to = db[newName]; + to.ensureIndex( { _id : 1 } ); + var count = 0; + + var cursor = from.find(); + while ( cursor.hasNext() ){ + var o = cursor.next(); + count++; + to.save( o ); + } + + return count; + } , this.getName() , newName + ); +} + +DBCollection.prototype.getCollection = function( subName ){ + return this._db.getCollection( this._shortName + "." + subName ); +} + +DBCollection.prototype.stats = function(){ + return this._db.runCommand( { collstats : this._shortName } ); +} + +DBCollection.prototype.dataSize = function(){ + return this.stats().size; +} + +DBCollection.prototype.storageSize = function(){ + return this.stats().storageSize; +} + +DBCollection.prototype.totalIndexSize = function( verbose ){ + var total = 0; + var mydb = this._db; + var shortName = this._shortName; + this.getIndexes().forEach( + function( spec ){ + var coll = mydb.getCollection( shortName + ".$" + spec.name ); + var mysize = coll.dataSize(); + total += coll.dataSize(); + if ( verbose ) { + print( coll + "\t" + mysize ); + } + } + ); + return total; +} + + +DBCollection.prototype.totalSize = function(){ + var total = this.storageSize(); + var mydb = this._db; + var shortName = this._shortName; + this.getIndexes().forEach( + function( spec ){ + var coll = mydb.getCollection( shortName + ".$" + spec.name ); + var mysize = coll.storageSize(); + //print( coll + "\t" + mysize + "\t" + tojson( coll.validate() ) ); + total += coll.dataSize(); + } + ); + return total; +} + + +DBCollection.prototype.convertToCapped = function( bytes ){ + if ( ! bytes ) + throw "have to specify # of bytes"; + return this._dbCommand( { convertToCapped : this._shortName , size : bytes } ) +} + +DBCollection.prototype.exists = function(){ + return this._db.system.namespaces.findOne( { name : this._fullName } ); +} + +DBCollection.prototype.isCapped = function(){ + var e = this.exists(); + return ( e && e.options && e.options.capped ) ? true : false; +} + +DBCollection.prototype.distinct = function( keyString , query ){ + var res = this._dbCommand( { distinct : this._shortName , key : keyString , query : query || {} } ); + if ( ! res.ok ) + throw "distinct failed: " + tojson( res ); + return res.values; +} + +DBCollection.prototype.group = function( params ){ + params.ns = this._shortName; + return this._db.group( params ); +} + +DBCollection.prototype.groupcmd = function( params ){ + params.ns = this._shortName; + return this._db.groupcmd( params ); +} + +MapReduceResult = function( db , o ){ + Object.extend( this , o ); + this._o = o; + this._keys = Object.keySet( o ); + this._db = db; + this._coll = this._db.getCollection( this.result ); +} + +MapReduceResult.prototype._simpleKeys = function(){ + return this._o; +} + +MapReduceResult.prototype.find = function(){ + return DBCollection.prototype.find.apply( this._coll , arguments ); +} + +MapReduceResult.prototype.drop = function(){ + return this._coll.drop(); +} + +/** +* just for debugging really +*/ +MapReduceResult.prototype.convertToSingleObject = function(){ + var z = {}; + this._coll.find().forEach( function(a){ z[a._id] = a.value; } ); + return z; +} + +/** +* @param optional object of optional fields; +*/ +DBCollection.prototype.mapReduce = function( map , reduce , optional ){ + var c = { mapreduce : this._shortName , map : map , reduce : reduce }; + if ( optional ) + Object.extend( c , optional ); + var raw = this._db.runCommand( c ); + if ( ! raw.ok ) + throw "map reduce failed: " + tojson( raw ); + return new MapReduceResult( this._db , raw ); + +} + +DBCollection.prototype.toString = function(){ + return this.getFullName(); +} + +DBCollection.prototype.toString = function(){ + return this.getFullName(); +} + + +DBCollection.prototype.tojson = DBCollection.prototype.toString; + +DBCollection.prototype.shellPrint = DBCollection.prototype.toString; + + + diff --git a/shell/db.js b/shell/db.js new file mode 100644 index 0000000..ab79e22 --- /dev/null +++ b/shell/db.js @@ -0,0 +1,632 @@ +// db.js + +if ( typeof DB == "undefined" ){ + DB = function( mongo , name ){ + this._mongo = mongo; + this._name = name; + } +} + +DB.prototype.getMongo = function(){ + assert( this._mongo , "why no mongo!" ); + return this._mongo; +} + +DB.prototype.getSisterDB = function( name ){ + return this.getMongo().getDB( name ); +} + +DB.prototype.getName = function(){ + return this._name; +} + +DB.prototype.getCollection = function( name ){ + return new DBCollection( this._mongo , this , name , this._name + "." + name ); +} + +DB.prototype.commandHelp = function( name ){ + var c = {}; + c[name] = 1; + c.help = true; + return this.runCommand( c ).help; +} + +DB.prototype.runCommand = function( obj ){ + if ( typeof( obj ) == "string" ){ + var n = {}; + n[obj] = 1; + obj = n; + } + return this.getCollection( "$cmd" ).findOne( obj ); +} + +DB.prototype._dbCommand = DB.prototype.runCommand; + +DB.prototype._adminCommand = function( obj ){ + if ( this._name == "admin" ) + return this.runCommand( obj ); + return this.getSisterDB( "admin" ).runCommand( obj ); +} + +DB.prototype.addUser = function( username , pass ){ + var c = this.getCollection( "system.users" ); + + var u = c.findOne( { user : username } ) || { user : username }; + u.pwd = hex_md5( username + ":mongo:" + pass ); + print( tojson( u ) ); + + c.save( u ); +} + +DB.prototype.removeUser = function( username ){ + this.getCollection( "system.users" ).remove( { user : username } ); +} + +DB.prototype.auth = function( username , pass ){ + var n = this.runCommand( { getnonce : 1 } ); + + var a = this.runCommand( + { + authenticate : 1 , + user : username , + nonce : n.nonce , + key : hex_md5( n.nonce + username + hex_md5( username + ":mongo:" + pass ) ) + } + ); + + return a.ok; +} + +/** + Create a new collection in the database. Normally, collection creation is automatic. You would + use this function if you wish to specify special options on creation. + + If the collection already exists, no action occurs. + + <p>Options:</p> + <ul> + <li> + size: desired initial extent size for the collection. Must be <= 1000000000. + for fixed size (capped) collections, this size is the total/max size of the + collection. + </li> + <li> + capped: if true, this is a capped collection (where old data rolls out). + </li> + <li> max: maximum number of objects if capped (optional).</li> + </ul> + + <p>Example: </p> + + <code>db.createCollection("movies", { size: 10 * 1024 * 1024, capped:true } );</code> + + * @param {String} name Name of new collection to create + * @param {Object} options Object with options for call. Options are listed above. + * @return SOMETHING_FIXME +*/ +DB.prototype.createCollection = function(name, opt) { + var options = opt || {}; + var cmd = { create: name, capped: options.capped, size: options.size, max: options.max }; + var res = this._dbCommand(cmd); + return res; +} + +/** + * Returns the current profiling level of this database + * @return SOMETHING_FIXME or null on error + */ + DB.prototype.getProfilingLevel = function() { + var res = this._dbCommand( { profile: -1 } ); + return res ? res.was : null; +} + + +/** + Erase the entire database. (!) + + * @return Object returned has member ok set to true if operation succeeds, false otherwise. +*/ +DB.prototype.dropDatabase = function() { + if ( arguments.length ) + throw "dropDatabase doesn't take arguments"; + return this._dbCommand( { dropDatabase: 1 } ); +} + + +DB.prototype.shutdownServer = function() { + if( "admin" != this._name ){ + return "shutdown command only works with the admin database; try 'use admin'"; + } + + try { + var res = this._dbCommand("shutdown"); + if( res ) + throw "shutdownServer failed: " + res.errmsg; + throw "shutdownServer failed"; + } + catch ( e ){ + assert( tojson( e ).indexOf( "error doing query: failed" ) >= 0 , "unexpected error: " + tojson( e ) ); + print( "server should be down..." ); + } +} + +/** + Clone database on another server to here. + <p> + Generally, you should dropDatabase() first as otherwise the cloned information will MERGE + into whatever data is already present in this database. (That is however a valid way to use + clone if you are trying to do something intentionally, such as union three non-overlapping + databases into one.) + <p> + This is a low level administrative function will is not typically used. + + * @param {String} from Where to clone from (dbhostname[:port]). May not be this database + (self) as you cannot clone to yourself. + * @return Object returned has member ok set to true if operation succeeds, false otherwise. + * See also: db.copyDatabase() +*/ +DB.prototype.cloneDatabase = function(from) { + assert( isString(from) && from.length ); + //this.resetIndexCache(); + return this._dbCommand( { clone: from } ); +} + + +/** + Clone collection on another server to here. + <p> + Generally, you should drop() first as otherwise the cloned information will MERGE + into whatever data is already present in this collection. (That is however a valid way to use + clone if you are trying to do something intentionally, such as union three non-overlapping + collections into one.) + <p> + This is a low level administrative function is not typically used. + + * @param {String} from mongod instance from which to clnoe (dbhostname:port). May + not be this mongod instance, as clone from self is not allowed. + * @param {String} collection name of collection to clone. + * @param {Object} query query specifying which elements of collection are to be cloned. + * @return Object returned has member ok set to true if operation succeeds, false otherwise. + * See also: db.cloneDatabase() + */ +DB.prototype.cloneCollection = function(from, collection, query) { + assert( isString(from) && from.length ); + assert( isString(collection) && collection.length ); + collection = this._name + "." + collection; + query = query || {}; + //this.resetIndexCache(); + return this._dbCommand( { cloneCollection:collection, from:from, query:query } ); +} + + +/** + Copy database from one server or name to another server or name. + + Generally, you should dropDatabase() first as otherwise the copied information will MERGE + into whatever data is already present in this database (and you will get duplicate objects + in collections potentially.) + + For security reasons this function only works when executed on the "admin" db. However, + if you have access to said db, you can copy any database from one place to another. + + This method provides a way to "rename" a database by copying it to a new db name and + location. Additionally, it effectively provides a repair facility. + + * @param {String} fromdb database name from which to copy. + * @param {String} todb database name to copy to. + * @param {String} fromhost hostname of the database (and optionally, ":port") from which to + copy the data. default if unspecified is to copy from self. + * @return Object returned has member ok set to true if operation succeeds, false otherwise. + * See also: db.clone() +*/ +DB.prototype.copyDatabase = function(fromdb, todb, fromhost) { + assert( isString(fromdb) && fromdb.length ); + assert( isString(todb) && todb.length ); + fromhost = fromhost || ""; + //this.resetIndexCache(); + return this._adminCommand( { copydb:1, fromhost:fromhost, fromdb:fromdb, todb:todb } ); +} + +/** + Repair database. + + * @return Object returned has member ok set to true if operation succeeds, false otherwise. +*/ +DB.prototype.repairDatabase = function() { + return this._dbCommand( { repairDatabase: 1 } ); +} + + +DB.prototype.help = function() { + print("DB methods:"); + print("\tdb.addUser(username, password)"); + print("\tdb.auth(username, password)"); + print("\tdb.cloneDatabase(fromhost)"); + print("\tdb.commandHelp(name) returns the help for the command"); + print("\tdb.copyDatabase(fromdb, todb, fromhost)"); + print("\tdb.createCollection(name, { size : ..., capped : ..., max : ... } )"); + print("\tdb.currentOp() displays the current operation in the db" ); + print("\tdb.dropDatabase()"); + print("\tdb.eval(func, args) run code server-side"); + print("\tdb.getCollection(cname) same as db['cname'] or db.cname"); + print("\tdb.getCollectionNames()"); + print("\tdb.getLastError() - just returns the err msg string"); + print("\tdb.getLastErrorObj() - return full status object"); + print("\tdb.getMongo() get the server connection object"); + print("\tdb.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair"); + print("\tdb.getName()"); + print("\tdb.getPrevError()"); + print("\tdb.getProfilingLevel()"); + print("\tdb.getReplicationInfo()"); + print("\tdb.getSisterDB(name) get the db at the same server as this onew"); + print("\tdb.killOp(opid) kills the current operation in the db" ); + print("\tdb.printCollectionStats()" ); + print("\tdb.printReplicationInfo()"); + print("\tdb.printSlaveReplicationInfo()"); + print("\tdb.printShardingStatus()"); + print("\tdb.removeUser(username)"); + print("\tdb.repairDatabase()"); + print("\tdb.resetError()"); + print("\tdb.runCommand(cmdObj) run a database command. if cmdObj is a string, turns it into { cmdObj : 1 }"); + print("\tdb.setProfilingLevel(level,<slowms>) 0=off 1=slow 2=all"); + print("\tdb.shutdownServer()"); + print("\tdb.version() current version of the server" ); +} + +DB.prototype.printCollectionStats = function(){ + var mydb = this; + this.getCollectionNames().forEach( + function(z){ + print( z ); + printjson( mydb.getCollection(z).stats() ); + print( "---" ); + } + ); +} + +/** + * <p> Set profiling level for your db. Profiling gathers stats on query performance. </p> + * + * <p>Default is off, and resets to off on a database restart -- so if you want it on, + * turn it on periodically. </p> + * + * <p>Levels :</p> + * <ul> + * <li>0=off</li> + * <li>1=log very slow (>100ms) operations</li> + * <li>2=log all</li> + * @param {String} level Desired level of profiling + * @return SOMETHING_FIXME or null on error + */ +DB.prototype.setProfilingLevel = function(level,slowms) { + + if (level < 0 || level > 2) { + throw { dbSetProfilingException : "input level " + level + " is out of range [0..2]" }; + } + + var cmd = { profile: level }; + if ( slowms ) + cmd["slowms"] = slowms; + return this._dbCommand( cmd ); +} + + +/** + * <p> Evaluate a js expression at the database server.</p> + * + * <p>Useful if you need to touch a lot of data lightly; in such a scenario + * the network transfer of the data could be a bottleneck. A good example + * is "select count(*)" -- can be done server side via this mechanism. + * </p> + * + * <p> + * If the eval fails, an exception is thrown of the form: + * </p> + * <code>{ dbEvalException: { retval: functionReturnValue, ok: num [, errno: num] [, errmsg: str] } }</code> + * + * <p>Example: </p> + * <code>print( "mycount: " + db.eval( function(){db.mycoll.find({},{_id:ObjId()}).length();} );</code> + * + * @param {Function} jsfunction Javascript function to run on server. Note this it not a closure, but rather just "code". + * @return result of your function, or null if error + * + */ +DB.prototype.eval = function(jsfunction) { + var cmd = { $eval : jsfunction }; + if ( arguments.length > 1 ) { + cmd.args = argumentsToArray( arguments ).slice(1); + } + + var res = this._dbCommand( cmd ); + + if (!res.ok) + throw tojson( res ); + + return res.retval; +} + +DB.prototype.dbEval = DB.prototype.eval; + + +/** + * + * <p> + * Similar to SQL group by. For example: </p> + * + * <code>select a,b,sum(c) csum from coll where active=1 group by a,b</code> + * + * <p> + * corresponds to the following in 10gen: + * </p> + * + * <code> + db.group( + { + ns: "coll", + key: { a:true, b:true }, + // keyf: ..., + cond: { active:1 }, + reduce: function(obj,prev) { prev.csum += obj.c; } , + initial: { csum: 0 } + }); + </code> + * + * + * <p> + * An array of grouped items is returned. The array must fit in RAM, thus this function is not + * suitable when the return set is extremely large. + * </p> + * <p> + * To order the grouped data, simply sort it client side upon return. + * <p> + Defaults + cond may be null if you want to run against all rows in the collection + keyf is a function which takes an object and returns the desired key. set either key or keyf (not both). + * </p> +*/ +DB.prototype.groupeval = function(parmsObj) { + + var groupFunction = function() { + var parms = args[0]; + var c = db[parms.ns].find(parms.cond||{}); + var map = new Map(); + var pks = parms.key ? Object.keySet( parms.key ) : null; + var pkl = pks ? pks.length : 0; + var key = {}; + + while( c.hasNext() ) { + var obj = c.next(); + if ( pks ) { + for( var i=0; i<pkl; i++ ){ + var k = pks[i]; + key[k] = obj[k]; + } + } + else { + key = parms.$keyf(obj); + } + + var aggObj = map.get(key); + if( aggObj == null ) { + var newObj = Object.extend({}, key); // clone + aggObj = Object.extend(newObj, parms.initial) + map.put( key , aggObj ); + } + parms.$reduce(obj, aggObj); + } + + return map.values(); + } + + return this.eval(groupFunction, this._groupFixParms( parmsObj )); +} + +DB.prototype.groupcmd = function( parmsObj ){ + var ret = this.runCommand( { "group" : this._groupFixParms( parmsObj ) } ); + if ( ! ret.ok ){ + throw "group command failed: " + tojson( ret ); + } + return ret.retval; +} + +DB.prototype.group = DB.prototype.groupcmd; + +DB.prototype._groupFixParms = function( parmsObj ){ + var parms = Object.extend({}, parmsObj); + + if( parms.reduce ) { + parms.$reduce = parms.reduce; // must have $ to pass to db + delete parms.reduce; + } + + if( parms.keyf ) { + parms.$keyf = parms.keyf; + delete parms.keyf; + } + + return parms; +} + +DB.prototype.resetError = function(){ + return this.runCommand( { reseterror : 1 } ); +} + +DB.prototype.forceError = function(){ + return this.runCommand( { forceerror : 1 } ); +} + +DB.prototype.getLastError = function(){ + var res = this.runCommand( { getlasterror : 1 } ); + if ( ! res.ok ) + throw "getlasterror failed: " + tojson( res ); + return res.err; +} +DB.prototype.getLastErrorObj = function(){ + var res = this.runCommand( { getlasterror : 1 } ); + if ( ! res.ok ) + throw "getlasterror failed: " + tojson( res ); + return res; +} +DB.prototype.getLastErrorCmd = DB.prototype.getLastErrorObj; + + +/* Return the last error which has occurred, even if not the very last error. + + Returns: + { err : <error message>, nPrev : <how_many_ops_back_occurred>, ok : 1 } + + result.err will be null if no error has occurred. + */ +DB.prototype.getPrevError = function(){ + return this.runCommand( { getpreverror : 1 } ); +} + +DB.prototype.getCollectionNames = function(){ + var all = []; + + var nsLength = this._name.length + 1; + + this.getCollection( "system.namespaces" ).find().sort({name:1}).forEach( + function(z){ + var name = z.name; + + if ( name.indexOf( "$" ) >= 0 ) + return; + + all.push( name.substring( nsLength ) ); + } + ); + return all; +} + +DB.prototype.tojson = function(){ + return this._name; +} + +DB.prototype.toString = function(){ + return this._name; +} + +DB.prototype.currentOp = function(){ + return db.$cmd.sys.inprog.findOne(); +} +DB.prototype.currentOP = DB.prototype.currentOp; + +DB.prototype.killOp = function(op) { + if( !op ) + throw "no opNum to kill specified"; + return db.$cmd.sys.killop.findOne({'op':op}); +} +DB.prototype.killOP = DB.prototype.killOp; + +DB.tsToSeconds = function(x){ + if ( x.t && x.i ) + return x.t / 1000; + return x / 4294967296; // low 32 bits are ordinal #s within a second +} + +/** + Get a replication log information summary. + <p> + This command is for the database/cloud administer and not applicable to most databases. + It is only used with the local database. One might invoke from the JS shell: + <pre> + use local + db.getReplicationInfo(); + </pre> + It is assumed that this database is a replication master -- the information returned is + about the operation log stored at local.oplog.$main on the replication master. (It also + works on a machine in a replica pair: for replica pairs, both machines are "masters" from + an internal database perspective. + <p> + * @return Object timeSpan: time span of the oplog from start to end if slave is more out + * of date than that, it can't recover without a complete resync +*/ +DB.prototype.getReplicationInfo = function() { + var db = this.getSisterDB("local"); + + var result = { }; + var ol = db.system.namespaces.findOne({name:"local.oplog.$main"}); + if( ol && ol.options ) { + result.logSizeMB = ol.options.size / 1000 / 1000; + } else { + result.errmsg = "local.oplog.$main, or its options, not found in system.namespaces collection (not --master?)"; + return result; + } + + var firstc = db.oplog.$main.find().sort({$natural:1}).limit(1); + var lastc = db.oplog.$main.find().sort({$natural:-1}).limit(1); + if( !firstc.hasNext() || !lastc.hasNext() ) { + result.errmsg = "objects not found in local.oplog.$main -- is this a new and empty db instance?"; + result.oplogMainRowCount = db.oplog.$main.count(); + return result; + } + + var first = firstc.next(); + var last = lastc.next(); + { + var tfirst = first.ts; + var tlast = last.ts; + + if( tfirst && tlast ) { + tfirst = DB.tsToSeconds( tfirst ); + tlast = DB.tsToSeconds( tlast ); + result.timeDiff = tlast - tfirst; + result.timeDiffHours = Math.round(result.timeDiff / 36)/100; + result.tFirst = (new Date(tfirst*1000)).toString(); + result.tLast = (new Date(tlast*1000)).toString(); + result.now = Date(); + } + else { + result.errmsg = "ts element not found in oplog objects"; + } + } + + return result; +} +DB.prototype.printReplicationInfo = function() { + var result = this.getReplicationInfo(); + if( result.errmsg ) { + print(tojson(result)); + return; + } + print("configured oplog size: " + result.logSizeMB + "MB"); + print("log length start to end: " + result.timeDiff + "secs (" + result.timeDiffHours + "hrs)"); + print("oplog first event time: " + result.tFirst); + print("oplog last event time: " + result.tLast); + print("now: " + result.now); +} + +DB.prototype.printSlaveReplicationInfo = function() { + function g(x) { + print("source: " + x.host); + var st = new Date( DB.tsToSeconds( x.syncedTo ) * 1000 ); + var now = new Date(); + print("syncedTo: " + st.toString() ); + var ago = (now-st)/1000; + var hrs = Math.round(ago/36)/100; + print(" = " + Math.round(ago) + "secs ago (" + hrs + "hrs)"); + } + var L = this.getSisterDB("local"); + if( L.sources.count() == 0 ) { + print("local.sources is empty; is this db a --slave?"); + return; + } + L.sources.find().forEach(g); +} + +DB.prototype.serverBuildInfo = function(){ + return this._adminCommand( "buildinfo" ); +} + +DB.prototype.serverStatus = function(){ + return this._adminCommand( "serverStatus" ); +} + +DB.prototype.version = function(){ + return this.serverBuildInfo().version; +} + +DB.prototype.printShardingStatus = function(){ + printShardingStatus( this.getSisterDB( "config" ) ); +} diff --git a/shell/dbshell.cpp b/shell/dbshell.cpp new file mode 100644 index 0000000..7984383 --- /dev/null +++ b/shell/dbshell.cpp @@ -0,0 +1,503 @@ +// dbshell.cpp + +#include <stdio.h> + +#ifdef USE_READLINE +#include <readline/readline.h> +#include <readline/history.h> +#include <setjmp.h> +jmp_buf jbuf; +#endif + +#include "../scripting/engine.h" +#include "../client/dbclient.h" +#include "../util/unittest.h" +#include "../db/cmdline.h" +#include "utils.h" + +using namespace std; +using namespace boost::filesystem; + +string historyFile; +bool gotInterrupted = 0; +bool inMultiLine = 0; + +#if defined(USE_READLINE) && !defined(__freebsd__) && !defined(_WIN32) +#define CTRLC_HANDLE +#endif + +void shellHistoryInit(){ +#ifdef USE_READLINE + stringstream ss; + char * h = getenv( "HOME" ); + if ( h ) + ss << h << "/"; + ss << ".dbshell"; + historyFile = ss.str(); + + using_history(); + read_history( historyFile.c_str() ); + +#else + cout << "type \"exit\" to exit" << endl; +#endif +} +void shellHistoryDone(){ +#ifdef USE_READLINE + write_history( historyFile.c_str() ); +#endif +} +void shellHistoryAdd( const char * line ){ + if ( strlen(line) == 0 ) + return; +#ifdef USE_READLINE + add_history( line ); +#endif +} + +void intr( int sig ){ +#ifdef CTRLC_HANDLE + longjmp( jbuf , 1 ); +#endif +} + +#if !defined(_WIN32) +void quitNicely( int sig ){ + if ( sig == SIGINT && inMultiLine ){ + gotInterrupted = 1; + return; + } + if ( sig == SIGPIPE ) + mongo::rawOut( "mongo got signal SIGPIPE\n" ); + shellHistoryDone(); + exit(0); +} +#endif + +char * shellReadline( const char * prompt , int handlesigint = 0 ){ +#ifdef USE_READLINE + +#ifdef CTRLC_HANDLE + if ( ! handlesigint ) + return readline( prompt ); + if ( setjmp( jbuf ) ){ + gotInterrupted = 1; + sigrelse(SIGINT); + signal( SIGINT , quitNicely ); + return 0; + } + signal( SIGINT , intr ); +#endif + + char * ret = readline( prompt ); + signal( SIGINT , quitNicely ); + return ret; +#else + printf( prompt ); + char * buf = new char[1024]; + char * l = fgets( buf , 1024 , stdin ); + int len = strlen( buf ); + buf[len-1] = 0; + return l; +#endif +} + +#if !defined(_WIN32) +#include <string.h> + +void quitAbruptly( int sig ) { + ostringstream ossSig; + ossSig << "mongo got signal " << sig << " (" << strsignal( sig ) << "), stack trace: " << endl; + mongo::rawOut( ossSig.str() ); + + ostringstream ossBt; + mongo::printStackTrace( ossBt ); + mongo::rawOut( ossBt.str() ); + + mongo::shellUtils::KillMongoProgramInstances(); + exit(14); +} + +void setupSignals() { + signal( SIGINT , quitNicely ); + signal( SIGTERM , quitNicely ); + signal( SIGPIPE , quitNicely ); // Maybe just log and continue? + signal( SIGABRT , quitAbruptly ); + signal( SIGSEGV , quitAbruptly ); + signal( SIGBUS , quitAbruptly ); + signal( SIGFPE , quitAbruptly ); +} +#else +inline void setupSignals() {} +#endif + +string fixHost( string url , string host , string port ){ + //cout << "fixHost url: " << url << " host: " << host << " port: " << port << endl; + + if ( host.size() == 0 && port.size() == 0 ){ + if ( url.find( "/" ) == string::npos ){ + // check for ips + if ( url.find( "." ) != string::npos ) + return url + "/test"; + + if ( url.find( ":" ) != string::npos && + isdigit( url[url.find(":")+1] ) ) + return url + "/test"; + } + return url; + } + + if ( url.find( "/" ) != string::npos ){ + cerr << "url can't have host or port if you specify them individually" << endl; + exit(-1); + } + + if ( host.size() == 0 ) + host = "127.0.0.1"; + + string newurl = host; + if ( port.size() > 0 ) + newurl += ":" + port; + + newurl += "/" + url; + + return newurl; +} + +bool isBalanced( string code ){ + int brackets = 0; + int parens = 0; + + for ( size_t i=0; i<code.size(); i++ ){ + switch( code[i] ){ + case '/': + if ( i+1 < code.size() && code[i+1] == '/' ){ + while ( i<code.size() && code[i] != '\n' ) + i++; + } + continue; + case '{': brackets++; break; + case '}': if ( brackets <= 0 ) return true; brackets--; break; + case '(': parens++; break; + case ')': if ( parens <= 0 ) return true; parens--; break; + case '"': + i++; + while ( i < code.size() && code[i] != '"' ) i++; + break; + case '\'': + i++; + while ( i < code.size() && code[i] != '\'' ) i++; + break; + } + } + + return brackets == 0 && parens == 0; +} + +using mongo::asserted; + +struct BalancedTest : public mongo::UnitTest { +public: + void run(){ + assert( isBalanced( "x = 5" ) ); + assert( isBalanced( "function(){}" ) ); + assert( isBalanced( "function(){\n}" ) ); + assert( ! isBalanced( "function(){" ) ); + assert( isBalanced( "x = \"{\";" ) ); + assert( isBalanced( "// {" ) ); + assert( ! isBalanced( "// \n {" ) ); + assert( ! isBalanced( "\"//\" {" ) ); + + } +} balnaced_test; + +string finishCode( string code ){ + while ( ! isBalanced( code ) ){ + inMultiLine = 1; + code += "\n"; + char * line = shellReadline("... " , 1 ); + if ( gotInterrupted ) + return ""; + if ( ! line ) + return ""; + code += line; + } + return code; +} + +#include <boost/program_options.hpp> +namespace po = boost::program_options; + +void show_help_text(const char* name, po::options_description options) { + cout << "MongoDB shell version: " << mongo::versionString << endl; + cout << "usage: " << name << " [options] [db address] [file names (ending in .js)]" << endl + << "db address can be:" << endl + << " foo foo database on local machine" << endl + << " 192.169.0.5/foo foo database on 192.168.0.5 machine" << endl + << " 192.169.0.5:9999/foo foo database on 192.168.0.5 machine on port 9999" << endl + << options << endl + << "file names: a list of files to run. files have to end in .js and will exit after " + << "unless --shell is specified" << endl; +}; + +bool fileExists( string file ){ + try { + path p(file); + return boost::filesystem::exists( file ); + } + catch (...){ + return false; + } +} + +int _main(int argc, char* argv[]) { + setupSignals(); + + mongo::shellUtils::RecordMyLocation( argv[ 0 ] ); + + string url = "test"; + string dbhost; + string port; + vector<string> files; + + string username; + string password; + + bool runShell = false; + bool nodb = false; + + string script; + + po::options_description shell_options("options"); + po::options_description hidden_options("Hidden options"); + po::options_description cmdline_options("Command line options"); + po::positional_options_description positional_options; + + shell_options.add_options() + ("shell", "run the shell after executing files") + ("nodb", "don't connect to mongod on startup - no 'db address' arg expected") + ("quiet", "be less chatty" ) + ("port", po::value<string>(&port), "port to connect to") + ("host", po::value<string>(&dbhost), "server to connect to") + ("eval", po::value<string>(&script), "evaluate javascript") + ("username,u", po::value<string>(&username), "username for authentication") + ("password,p", po::value<string>(&password), "password for authentication") + ("help,h", "show this usage information") + ("version", "show version information") + ; + + hidden_options.add_options() + ("dbaddress", po::value<string>(), "dbaddress") + ("files", po::value< vector<string> >(), "files") + ; + + positional_options.add("dbaddress", 1); + positional_options.add("files", -1); + + cmdline_options.add(shell_options).add(hidden_options); + + po::variables_map params; + + /* using the same style as db.cpp uses because eventually we're going + * to merge some of this stuff. */ + int command_line_style = (((po::command_line_style::unix_style ^ + po::command_line_style::allow_guessing) | + po::command_line_style::allow_long_disguise) ^ + po::command_line_style::allow_sticky); + + try { + po::store(po::command_line_parser(argc, argv).options(cmdline_options). + positional(positional_options). + style(command_line_style).run(), params); + po::notify(params); + } catch (po::error &e) { + cout << "ERROR: " << e.what() << endl << endl; + show_help_text(argv[0], shell_options); + return mongo::EXIT_BADOPTIONS; + } + + if (params.count("shell")) { + runShell = true; + } + if (params.count("nodb")) { + nodb = true; + } + if (params.count("help")) { + show_help_text(argv[0], shell_options); + return mongo::EXIT_CLEAN; + } + if (params.count("files")) { + files = params["files"].as< vector<string> >(); + } + if (params.count("version")) { + cout << "MongoDB shell version: " << mongo::versionString << endl; + return mongo::EXIT_CLEAN; + } + if (params.count("quiet")) { + mongo::cmdLine.quiet = true; + } + + /* This is a bit confusing, here are the rules: + * + * if nodb is set then all positional parameters are files + * otherwise the first positional parameter might be a dbaddress, but + * only if one of these conditions is met: + * - it contains no '.' after the last appearance of '\' or '/' + * - it doesn't end in '.js' and it doesn't specify a path to an existing file */ + if (params.count("dbaddress")) { + string dbaddress = params["dbaddress"].as<string>(); + if (nodb) { + files.insert(files.begin(), dbaddress); + } else { + string basename = dbaddress.substr(dbaddress.find_last_of("/\\") + 1); + if (basename.find_first_of('.') == string::npos || + (basename.find(".js", basename.size() - 3) == string::npos && !fileExists(dbaddress))) { + url = dbaddress; + } else { + files.insert(files.begin(), dbaddress); + } + } + } + + if ( ! mongo::cmdLine.quiet ) + cout << "MongoDB shell version: " << mongo::versionString << endl; + + mongo::UnitTest::runTests(); + + if ( !nodb ) { // connect to db + if ( ! mongo::cmdLine.quiet ) cout << "url: " << url << endl; + + stringstream ss; + if ( mongo::cmdLine.quiet ) + ss << "__quiet = true;"; + ss << "db = connect( \"" << fixHost( url , dbhost , port ) << "\")"; + + mongo::shellUtils::_dbConnect = ss.str(); + + if ( username.size() && password.size() ){ + stringstream ss; + ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }"; + mongo::shellUtils::_dbAuth = ss.str(); + } + + } + + mongo::ScriptEngine::setup(); + mongo::globalScriptEngine->setScopeInitCallback( mongo::shellUtils::initScope ); + auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() ); + + if ( !script.empty() ) { + mongo::shellUtils::MongoProgramScope s; + if ( ! scope->exec( script , "(shell eval)" , true , true , false ) ) + return -4; + } + + for (size_t i = 0; i < files.size(); i++) { + mongo::shellUtils::MongoProgramScope s; + + if ( files.size() > 1 ) + cout << "loading file: " << files[i] << endl; + + if ( ! scope->execFile( files[i] , false , true , false ) ){ + cout << "failed to load: " << files[i] << endl; + return -3; + } + } + + if ( files.size() == 0 && script.empty() ) { + runShell = true; + } + + if ( runShell ){ + + mongo::shellUtils::MongoProgramScope s; + + shellHistoryInit(); + + cout << "type \"help\" for help" << endl; + + //v8::Handle<v8::Object> shellHelper = baseContext_->Global()->Get( v8::String::New( "shellHelper" ) )->ToObject(); + + while ( 1 ){ + inMultiLine = 0; + gotInterrupted = 0; + char * line = shellReadline( "> " ); + + if ( line ) + while ( line[0] == ' ' ) + line++; + + if ( ! line || ( strlen(line) == 4 && strstr( line , "exit" ) ) ){ + cout << "bye" << endl; + break; + } + + string code = line; + if ( code == "exit" ){ + break; + } + if ( code.size() == 0 ) + continue; + + code = finishCode( code ); + if ( gotInterrupted ){ + cout << endl; + continue; + } + + if ( code.size() == 0 ) + break; + + bool wascmd = false; + { + string cmd = line; + if ( cmd.find( " " ) > 0 ) + cmd = cmd.substr( 0 , cmd.find( " " ) ); + + if ( cmd.find( "\"" ) == string::npos ){ + scope->exec( (string)"__iscmd__ = shellHelper[\"" + cmd + "\"];" , "(shellhelp1)" , false , true , true ); + if ( scope->getBoolean( "__iscmd__" ) ){ + scope->exec( (string)"shellHelper( \"" + cmd + "\" , \"" + code.substr( cmd.size() ) + "\");" , "(shellhelp2)" , false , true , false ); + wascmd = true; + } + } + + } + + if ( ! wascmd ){ + try { + scope->exec( code.c_str() , "(shell)" , false , true , false ); + scope->exec( "shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false ); + } + catch ( std::exception& e ){ + cout << "error:" << e.what() << endl; + } + } + + + shellHistoryAdd( line ); + } + + shellHistoryDone(); + } + + return 0; +} + +int main(int argc, char* argv[]) { + try { + return _main( argc , argv ); + } + catch ( mongo::DBException& e ){ + cerr << "exception: " << e.what() << endl; + return -1; + } +} + +namespace mongo { + DBClientBase * createDirectClient(){ + uassert( 10256 , "no createDirectClient in shell" , 0 ); + return 0; + } +} + diff --git a/shell/mongo.js b/shell/mongo.js new file mode 100644 index 0000000..dc7fcd9 --- /dev/null +++ b/shell/mongo.js @@ -0,0 +1,84 @@ +// mongo.js + +// NOTE 'Mongo' may be defined here or in MongoJS.cpp. Add code to init, not to this constructor. +if ( typeof Mongo == "undefined" ){ + Mongo = function( host ){ + this.init( host ); + } +} + +if ( ! Mongo.prototype ){ + throw "Mongo.prototype not defined"; +} + +if ( ! Mongo.prototype.find ) + Mongo.prototype.find = function( ns , query , fields , limit , skip ){ throw "find not implemented"; } +if ( ! Mongo.prototype.insert ) + Mongo.prototype.insert = function( ns , obj ){ throw "insert not implemented"; } +if ( ! Mongo.prototype.remove ) + Mongo.prototype.remove = function( ns , pattern ){ throw "remove not implemented;" } +if ( ! Mongo.prototype.update ) + Mongo.prototype.update = function( ns , query , obj , upsert ){ throw "update not implemented;" } + +if ( typeof mongoInject == "function" ){ + mongoInject( Mongo.prototype ); +} + +Mongo.prototype.setSlaveOk = function() { + this.slaveOk = true; +} + +Mongo.prototype.getDB = function( name ){ + return new DB( this , name ); +} + +Mongo.prototype.getDBs = function(){ + var res = this.getDB( "admin" ).runCommand( { "listDatabases" : 1 } ); + assert( res.ok == 1 , "listDatabases failed" ); + return res; +} + +Mongo.prototype.getDBNames = function(){ + return this.getDBs().databases.map( + function(z){ + return z.name; + } + ); +} + +Mongo.prototype.getCollection = function(ns){ + var idx = ns.indexOf( "." ); + if ( idx < 0 ) + throw "need . in ns"; + var db = ns.substring( 0 , idx ); + var c = ns.substring( idx + 1 ); + return this.getDB( db ).getCollection( c ); +} + +Mongo.prototype.toString = function(){ + return "mongo connection to " + this.host; +} + +connect = function( url , user , pass ){ + chatty( "connecting to: " + url ) + + if ( user && ! pass ) + throw "you specified a user and not a password. either you need a password, or you're using the old connect api"; + + var idx = url.indexOf( "/" ); + + var db; + + if ( idx < 0 ) + db = new Mongo().getDB( url ); + else + db = new Mongo( url.substring( 0 , idx ) ).getDB( url.substring( idx + 1 ) ); + + if ( user && pass ){ + if ( ! db.auth( user , pass ) ){ + throw "couldn't login"; + } + } + + return db; +} diff --git a/shell/mongo_vstudio.cpp b/shell/mongo_vstudio.cpp new file mode 100644 index 0000000..d0f1b48 --- /dev/null +++ b/shell/mongo_vstudio.cpp @@ -0,0 +1,2006 @@ +const char * jsconcatcode = +"if ( ( typeof DBCollection ) == \"undefined\" ){\n" + "DBCollection = function( mongo , db , shortName , fullName ){\n" + "this._mongo = mongo;\n" + "this._db = db;\n" + "this._shortName = shortName;\n" + "this._fullName = fullName;\n" + "this.verify();\n" + "}\n" + "}\n" + "DBCollection.prototype.verify = function(){\n" + "assert( this._fullName , \"no fullName\" );\n" + "assert( this._shortName , \"no shortName\" );\n" + "assert( this._db , \"no db\" );\n" + "assert.eq( this._fullName , this._db._name + \".\" + this._shortName , \"name mismatch\" );\n" + "assert( this._mongo , \"no mongo in DBCollection\" );\n" + "}\n" + "DBCollection.prototype.getName = function(){\n" + "return this._shortName;\n" + "}\n" + "DBCollection.prototype.help = function(){\n" + "print(\"DBCollection help\");\n" + "print(\"\\tdb.foo.getDB() get DB object associated with collection\");\n" + "print(\"\\tdb.foo.findOne([query])\");\n" + "print(\"\\tdb.foo.find( [query] , [fields]) - first parameter is an optional query filter. second parameter is optional set of fields to return.\");\n" + "print(\"\\t e.g. db.foo.find( { x : 77 } , { name : 1 , x : 1 } )\");\n" + "print(\"\\tdb.foo.find(...).sort(...)\");\n" + "print(\"\\tdb.foo.find(...).limit(n)\");\n" + "print(\"\\tdb.foo.find(...).skip(n)\");\n" + "print(\"\\tdb.foo.find(...).count()\");\n" + "print(\"\\tdb.foo.count()\");\n" + "print(\"\\tdb.foo.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )\");\n" + "print(\"\\tdb.foo.save(obj)\");\n" + "print(\"\\tdb.foo.update(query, object[, upsert_bool])\");\n" + "print(\"\\tdb.foo.remove(query)\" );\n" + "print(\"\\tdb.foo.ensureIndex(keypattern,options) - options should be an object with these possible fields: name, unique, dropDups\");\n" + "print(\"\\tdb.foo.dropIndexes()\");\n" + "print(\"\\tdb.foo.dropIndex(name)\");\n" + "print(\"\\tdb.foo.getIndexes()\");\n" + "print(\"\\tdb.foo.drop() drop the collection\");\n" + "print(\"\\tdb.foo.renameCollection( newName ) renames the collection\");\n" + "print(\"\\tdb.foo.validate() - SLOW\");\n" + "print(\"\\tdb.foo.stats()\");\n" + "print(\"\\tdb.foo.dataSize()\");\n" + "print(\"\\tdb.foo.storageSize() - includes free space allocated to this collection\");\n" + "print(\"\\tdb.foo.totalIndexSize() - size in bytes of all the indexes\");\n" + "print(\"\\tdb.foo.totalSize() - storage allocated for all data and indexes\");\n" + "}\n" + "DBCollection.prototype.getFullName = function(){\n" + "return this._fullName;\n" + "}\n" + "DBCollection.prototype.getDB = function(){\n" + "return this._db;\n" + "}\n" + "DBCollection.prototype._dbCommand = function( cmd ){\n" + "return this._db._dbCommand( cmd );\n" + "}\n" + "DBCollection.prototype._massageObject = function( q ){\n" + "if ( ! q )\n" + "return {};\n" + "var type = typeof q;\n" + "if ( type == \"function\" )\n" + "return { $where : q };\n" + "if ( q.isObjectId )\n" + "return { _id : q };\n" + "if ( type == \"object\" )\n" + "return q;\n" + "if ( type == \"string\" ){\n" + "if ( q.length == 24 )\n" + "return { _id : q };\n" + "return { $where : q };\n" + "}\n" + "throw \"don't know how to massage : \" + type;\n" + "}\n" + "DBCollection.prototype._validateObject = function( o ){\n" + "if ( o._ensureSpecial && o._checkModify )\n" + "throw \"can't save a DBQuery object\";\n" + "}\n" + "DBCollection._allowedFields = { $id : 1 , $ref : 1 };\n" + "DBCollection.prototype._validateForStorage = function( o ){\n" + "this._validateObject( o );\n" + "for ( var k in o ){\n" + "if ( k.indexOf( \".\" ) >= 0 ) {\n" + "throw \"can't have . in field names [\" + k + \"]\" ;\n" + "}\n" + "if ( k.indexOf( \"$\" ) == 0 && ! DBCollection._allowedFields[k] ) {\n" + "throw \"field names cannot start with $ [\" + k + \"]\";\n" + "}\n" + "if ( o[k] !== null && typeof( o[k] ) === \"object\" ) {\n" + "this._validateForStorage( o[k] );\n" + "}\n" + "}\n" + "};\n" + "DBCollection.prototype.find = function( query , fields , limit , skip ){\n" + "return new DBQuery( this._mongo , this._db , this ,\n" + "this._fullName , this._massageObject( query ) , fields , limit , skip );\n" + "}\n" + "DBCollection.prototype.findOne = function( query , fields ){\n" + "var cursor = this._mongo.find( this._fullName , this._massageObject( query ) || {} , fields , -1 , 0 );\n" + "if ( ! cursor.hasNext() )\n" + "return null;\n" + "var ret = cursor.next();\n" + "if ( cursor.hasNext() ) throw \"findOne has more than 1 result!\";\n" + "if ( ret.$err )\n" + "throw \"error \" + tojson( ret );\n" + "return ret;\n" + "}\n" + "DBCollection.prototype.insert = function( obj , _allow_dot ){\n" + "if ( ! obj )\n" + "throw \"no object!\";\n" + "if ( ! _allow_dot ) {\n" + "this._validateForStorage( obj );\n" + "}\n" + "return this._mongo.insert( this._fullName , obj );\n" + "}\n" + "DBCollection.prototype.remove = function( t ){\n" + "this._mongo.remove( this._fullName , this._massageObject( t ) );\n" + "}\n" + "DBCollection.prototype.update = function( query , obj , upsert ){\n" + "assert( query , \"need a query\" );\n" + "assert( obj , \"need an object\" );\n" + "this._validateObject( obj );\n" + "return this._mongo.update( this._fullName , query , obj , upsert ? true : false );\n" + "}\n" + "DBCollection.prototype.save = function( obj ){\n" + "if ( obj == null || typeof( obj ) == \"undefined\" )\n" + "throw \"can't save a null\";\n" + "if ( typeof( obj._id ) == \"undefined\" ){\n" + "obj._id = new ObjectId();\n" + "return this.insert( obj );\n" + "}\n" + "else {\n" + "return this.update( { _id : obj._id } , obj , true );\n" + "}\n" + "}\n" + "DBCollection.prototype._genIndexName = function( keys ){\n" + "var name = \"\";\n" + "for ( var k in keys ){\n" + "if ( name.length > 0 )\n" + "name += \"_\";\n" + "name += k + \"_\";\n" + "var v = keys[k];\n" + "if ( typeof v == \"number\" )\n" + "name += v;\n" + "}\n" + "return name;\n" + "}\n" + "DBCollection.prototype._indexSpec = function( keys, options ) {\n" + "var ret = { ns : this._fullName , key : keys , name : this._genIndexName( keys ) };\n" + "if ( ! options ){\n" + "}\n" + "else if ( typeof ( options ) == \"string\" )\n" + "ret.name = options;\n" + "else if ( typeof ( options ) == \"boolean\" )\n" + "ret.unique = true;\n" + "else if ( typeof ( options ) == \"object\" ){\n" + "if ( options.length ){\n" + "var nb = 0;\n" + "for ( var i=0; i<options.length; i++ ){\n" + "if ( typeof ( options[i] ) == \"string\" )\n" + "ret.name = options[i];\n" + "else if ( typeof( options[i] ) == \"boolean\" ){\n" + "if ( options[i] ){\n" + "if ( nb == 0 )\n" + "ret.unique = true;\n" + "if ( nb == 1 )\n" + "ret.dropDups = true;\n" + "}\n" + "nb++;\n" + "}\n" + "}\n" + "}\n" + "else {\n" + "Object.extend( ret , options );\n" + "}\n" + "}\n" + "else {\n" + "throw \"can't handle: \" + typeof( options );\n" + "}\n" + "/*\n" + "return ret;\n" + "var name;\n" + "var nTrue = 0;\n" + "if ( ! isObject( options ) ) {\n" + "options = [ options ];\n" + "}\n" + "if ( options.length ){\n" + "for( var i = 0; i < options.length; ++i ) {\n" + "var o = options[ i ];\n" + "if ( isString( o ) ) {\n" + "ret.name = o;\n" + "} else if ( typeof( o ) == \"boolean\" ) {\n" + "if ( o ) {\n" + "++nTrue;\n" + "}\n" + "}\n" + "}\n" + "if ( nTrue > 0 ) {\n" + "ret.unique = true;\n" + "}\n" + "if ( nTrue > 1 ) {\n" + "ret.dropDups = true;\n" + "}\n" + "}\n" + "*/\n" + "return ret;\n" + "}\n" + "DBCollection.prototype.createIndex = function( keys , options ){\n" + "var o = this._indexSpec( keys, options );\n" + "this._db.getCollection( \"system.indexes\" ).insert( o , true );\n" + "}\n" + "DBCollection.prototype.ensureIndex = function( keys , options ){\n" + "var name = this._indexSpec( keys, options ).name;\n" + "this._indexCache = this._indexCache || {};\n" + "if ( this._indexCache[ name ] ){\n" + "return false;\n" + "}\n" + "this.createIndex( keys , options );\n" + "if ( this.getDB().getLastError() == \"\" ) {\n" + "this._indexCache[name] = true;\n" + "}\n" + "return true;\n" + "}\n" + "DBCollection.prototype.resetIndexCache = function(){\n" + "this._indexCache = {};\n" + "}\n" + "DBCollection.prototype.reIndex = function(){\n" + "var specs = this.getIndexSpecs();\n" + "this.dropIndexes();\n" + "for ( var i = 0; i < specs.length; ++i ){\n" + "this.ensureIndex( specs[i].key, [ specs[i].unique, specs[i].name ] );\n" + "}\n" + "}\n" + "DBCollection.prototype.dropIndexes = function(){\n" + "this.resetIndexCache();\n" + "var res = this._db.runCommand( { deleteIndexes: this.getName(), index: \"*\" } );\n" + "assert( res , \"no result from dropIndex result\" );\n" + "if ( res.ok )\n" + "return res;\n" + "if ( res.errmsg.match( /not found/ ) )\n" + "return res;\n" + "throw \"error dropping indexes : \" + tojson( res );\n" + "}\n" + "DBCollection.prototype.drop = function(){\n" + "this.resetIndexCache();\n" + "return this._db.runCommand( { drop: this.getName() } );\n" + "}\n" + "DBCollection.prototype.renameCollection = function( newName ){\n" + "return this._db._adminCommand( { renameCollection : this._fullName , to : this._db._name + \".\" + newName } ).ok;\n" + "}\n" + "DBCollection.prototype.validate = function() {\n" + "var res = this._db.runCommand( { validate: this.getName() } );\n" + "res.valid = false;\n" + "if ( res.result ){\n" + "var str = \"-\" + tojson( res.result );\n" + "res.valid = ! ( str.match( /exception/ ) || str.match( /corrupt/ ) );\n" + "var p = /lastExtentSize:(\\d+)/;\n" + "var r = p.exec( str );\n" + "if ( r ){\n" + "res.lastExtentSize = Number( r[1] );\n" + "}\n" + "}\n" + "return res;\n" + "}\n" + "DBCollection.prototype.getIndexes = function(){\n" + "return this.getDB().getCollection( \"system.indexes\" ).find( { ns : this.getFullName() } ).toArray();\n" + "}\n" + "DBCollection.prototype.getIndices = DBCollection.prototype.getIndexes;\n" + "DBCollection.prototype.getIndexSpecs = DBCollection.prototype.getIndexes;\n" + "DBCollection.prototype.getIndexKeys = function(){\n" + "return this.getIndexes().map(\n" + "function(i){\n" + "return i.key;\n" + "}\n" + ");\n" + "}\n" + "DBCollection.prototype.count = function( x ){\n" + "return this.find( x ).count();\n" + "}\n" + "/**\n" + "* Drop free lists. Normally not used.\n" + "* Note this only does the collection itself, not the namespaces of its indexes (see cleanAll).\n" + "*/\n" + "DBCollection.prototype.clean = function() {\n" + "return this._dbCommand( { clean: this.getName() } );\n" + "}\n" + "/**\n" + "* <p>Drop a specified index.</p>\n" + "*\n" + "* <p>\n" + "* Name is the name of the index in the system.indexes name field. (Run db.system.indexes.find() to\n" + "* see example data.)\n" + "* </p>\n" + "*\n" + "* <p>Note : alpha: space is not reclaimed </p>\n" + "* @param {String} name of index to delete.\n" + "* @return A result object. result.ok will be true if successful.\n" + "*/\n" + "DBCollection.prototype.dropIndex = function(index) {\n" + "assert(index , \"need to specify index to dropIndex\" );\n" + "if ( ! isString( index ) && isObject( index ) )\n" + "index = this._genIndexName( index );\n" + "var res = this._dbCommand( { deleteIndexes: this.getName(), index: index } );\n" + "this.resetIndexCache();\n" + "return res;\n" + "}\n" + "DBCollection.prototype.copyTo = function( newName ){\n" + "return this.getDB().eval(\n" + "function( collName , newName ){\n" + "var from = db[collName];\n" + "var to = db[newName];\n" + "to.ensureIndex( { _id : 1 } );\n" + "var count = 0;\n" + "var cursor = from.find();\n" + "while ( cursor.hasNext() ){\n" + "var o = cursor.next();\n" + "count++;\n" + "to.save( o );\n" + "}\n" + "return count;\n" + "} , this.getName() , newName\n" + ");\n" + "}\n" + "DBCollection.prototype.getCollection = function( subName ){\n" + "return this._db.getCollection( this._shortName + \".\" + subName );\n" + "}\n" + "DBCollection.prototype.stats = function(){\n" + "return this._db.runCommand( { collstats : this._shortName } );\n" + "}\n" + "DBCollection.prototype.dataSize = function(){\n" + "return this.stats().size;\n" + "}\n" + "DBCollection.prototype.storageSize = function(){\n" + "return this.stats().storageSize;\n" + "}\n" + "DBCollection.prototype.totalIndexSize = function( verbose ){\n" + "var total = 0;\n" + "var mydb = this._db;\n" + "var shortName = this._shortName;\n" + "this.getIndexes().forEach(\n" + "function( spec ){\n" + "var coll = mydb.getCollection( shortName + \".$\" + spec.name );\n" + "var mysize = coll.dataSize();\n" + "total += coll.dataSize();\n" + "if ( verbose ) {\n" + "print( coll + \"\\t\" + mysize );\n" + "}\n" + "}\n" + ");\n" + "return total;\n" + "}\n" + "DBCollection.prototype.totalSize = function(){\n" + "var total = this.storageSize();\n" + "var mydb = this._db;\n" + "var shortName = this._shortName;\n" + "this.getIndexes().forEach(\n" + "function( spec ){\n" + "var coll = mydb.getCollection( shortName + \".$\" + spec.name );\n" + "var mysize = coll.storageSize();\n" + "total += coll.dataSize();\n" + "}\n" + ");\n" + "return total;\n" + "}\n" + "DBCollection.prototype.convertToCapped = function( bytes ){\n" + "if ( ! bytes )\n" + "throw \"have to specify # of bytes\";\n" + "return this._dbCommand( { convertToCapped : this._shortName , size : bytes } )\n" + "}\n" + "DBCollection.prototype.exists = function(){\n" + "return this._db.system.namespaces.findOne( { name : this._fullName } );\n" + "}\n" + "DBCollection.prototype.isCapped = function(){\n" + "var e = this.exists();\n" + "return ( e && e.options && e.options.capped ) ? true : false;\n" + "}\n" + "DBCollection.prototype.distinct = function( keyString ){\n" + "var res = this._dbCommand( { distinct : this._shortName , key : keyString } );\n" + "if ( ! res.ok )\n" + "throw \"distinct failed: \" + tojson( res );\n" + "return res.values;\n" + "}\n" + "DBCollection.prototype.group = function( params ){\n" + "params.ns = this._shortName;\n" + "return this._db.group( params );\n" + "}\n" + "DBCollection.prototype.groupcmd = function( params ){\n" + "params.ns = this._shortName;\n" + "return this._db.groupcmd( params );\n" + "}\n" + "DBCollection.prototype.toString = function(){\n" + "return this.getFullName();\n" + "}\n" + "DBCollection.prototype.shellPrint = DBCollection.prototype.toString;\n" + "if ( typeof DB == \"undefined\" ){\n" + "DB = function( mongo , name ){\n" + "this._mongo = mongo;\n" + "this._name = name;\n" + "}\n" + "}\n" + "DB.prototype.getMongo = function(){\n" + "assert( this._mongo , \"why no mongo!\" );\n" + "return this._mongo;\n" + "}\n" + "DB.prototype.getSisterDB = function( name ){\n" + "return this.getMongo().getDB( name );\n" + "}\n" + "DB.prototype.getName = function(){\n" + "return this._name;\n" + "}\n" + "DB.prototype.getCollection = function( name ){\n" + "return new DBCollection( this._mongo , this , name , this._name + \".\" + name );\n" + "}\n" + "DB.prototype.commandHelp = function( name ){\n" + "var c = {};\n" + "c[name] = 1;\n" + "c.help = true;\n" + "return this.runCommand( c ).help;\n" + "}\n" + "DB.prototype.runCommand = function( obj ){\n" + "if ( typeof( obj ) == \"string\" ){\n" + "var n = {};\n" + "n[obj] = 1;\n" + "obj = n;\n" + "}\n" + "return this.getCollection( \"$cmd\" ).findOne( obj );\n" + "}\n" + "DB.prototype._dbCommand = DB.prototype.runCommand;\n" + "DB.prototype._adminCommand = function( obj ){\n" + "if ( this._name == \"admin\" )\n" + "return this.runCommand( obj );\n" + "return this.getSisterDB( \"admin\" ).runCommand( obj );\n" + "}\n" + "DB.prototype.addUser = function( username , pass ){\n" + "var c = this.getCollection( \"system.users\" );\n" + "var u = c.findOne( { user : username } ) || { user : username };\n" + "u.pwd = hex_md5( username + \":mongo:\" + pass );\n" + "print( tojson( u ) );\n" + "c.save( u );\n" + "}\n" + "DB.prototype.removeUser = function( username ){\n" + "this.getCollection( \"system.users\" ).remove( { user : username } );\n" + "}\n" + "DB.prototype.auth = function( username , pass ){\n" + "var n = this.runCommand( { getnonce : 1 } );\n" + "var a = this.runCommand(\n" + "{\n" + "authenticate : 1 ,\n" + "user : username ,\n" + "nonce : n.nonce ,\n" + "key : hex_md5( n.nonce + username + hex_md5( username + \":mongo:\" + pass ) )\n" + "}\n" + ");\n" + "return a.ok;\n" + "}\n" + "/**\n" + "Create a new collection in the database. Normally, collection creation is automatic. You would\n" + "use this function if you wish to specify special options on creation.\n" + "If the collection already exists, no action occurs.\n" + "<p>Options:</p>\n" + "<ul>\n" + "<li>\n" + "size: desired initial extent size for the collection. Must be <= 1000000000.\n" + "for fixed size (capped) collections, this size is the total/max size of the\n" + "collection.\n" + "</li>\n" + "<li>\n" + "capped: if true, this is a capped collection (where old data rolls out).\n" + "</li>\n" + "<li> max: maximum number of objects if capped (optional).</li>\n" + "</ul>\n" + "<p>Example: </p>\n" + "<code>db.createCollection(\"movies\", { size: 10 * 1024 * 1024, capped:true } );</code>\n" + "* @param {String} name Name of new collection to create\n" + "* @param {Object} options Object with options for call. Options are listed above.\n" + "* @return SOMETHING_FIXME\n" + "*/\n" + "DB.prototype.createCollection = function(name, opt) {\n" + "var options = opt || {};\n" + "var cmd = { create: name, capped: options.capped, size: options.size, max: options.max };\n" + "var res = this._dbCommand(cmd);\n" + "return res;\n" + "}\n" + "/**\n" + "* Returns the current profiling level of this database\n" + "* @return SOMETHING_FIXME or null on error\n" + "*/\n" + "DB.prototype.getProfilingLevel = function() {\n" + "var res = this._dbCommand( { profile: -1 } );\n" + "return res ? res.was : null;\n" + "}\n" + "/**\n" + "Erase the entire database. (!)\n" + "* @return Object returned has member ok set to true if operation succeeds, false otherwise.\n" + "*/\n" + "DB.prototype.dropDatabase = function() {\n" + "if ( arguments.length )\n" + "throw \"dropDatabase doesn't take arguments\";\n" + "return this._dbCommand( { dropDatabase: 1 } );\n" + "}\n" + "DB.prototype.shutdownServer = function() {\n" + "if( \"admin\" != this._name ){\n" + "return \"shutdown command only works with the admin database; try 'use admin'\";\n" + "}\n" + "try {\n" + "this._dbCommand(\"shutdown\");\n" + "throw \"shutdownServer failed\";\n" + "}\n" + "catch ( e ){\n" + "assert( tojson( e ).indexOf( \"error doing query: failed\" ) >= 0 , \"unexpected error: \" + tojson( e ) );\n" + "print( \"server should be down...\" );\n" + "}\n" + "}\n" + "/**\n" + "Clone database on another server to here.\n" + "<p>\n" + "Generally, you should dropDatabase() first as otherwise the cloned information will MERGE\n" + "into whatever data is already present in this database. (That is however a valid way to use\n" + "clone if you are trying to do something intentionally, such as union three non-overlapping\n" + "databases into one.)\n" + "<p>\n" + "This is a low level administrative function will is not typically used.\n" + "* @param {String} from Where to clone from (dbhostname[:port]). May not be this database\n" + "(self) as you cannot clone to yourself.\n" + "* @return Object returned has member ok set to true if operation succeeds, false otherwise.\n" + "* See also: db.copyDatabase()\n" + "*/\n" + "DB.prototype.cloneDatabase = function(from) {\n" + "assert( isString(from) && from.length );\n" + "return this._dbCommand( { clone: from } );\n" + "}\n" + "/**\n" + "Clone collection on another server to here.\n" + "<p>\n" + "Generally, you should drop() first as otherwise the cloned information will MERGE\n" + "into whatever data is already present in this collection. (That is however a valid way to use\n" + "clone if you are trying to do something intentionally, such as union three non-overlapping\n" + "collections into one.)\n" + "<p>\n" + "This is a low level administrative function is not typically used.\n" + "* @param {String} from mongod instance from which to clnoe (dbhostname:port). May\n" + "not be this mongod instance, as clone from self is not allowed.\n" + "* @param {String} collection name of collection to clone.\n" + "* @param {Object} query query specifying which elements of collection are to be cloned.\n" + "* @return Object returned has member ok set to true if operation succeeds, false otherwise.\n" + "* See also: db.cloneDatabase()\n" + "*/\n" + "DB.prototype.cloneCollection = function(from, collection, query) {\n" + "assert( isString(from) && from.length );\n" + "assert( isString(collection) && collection.length );\n" + "collection = this._name + \".\" + collection;\n" + "query = query || {};\n" + "return this._dbCommand( { cloneCollection:collection, from:from, query:query } );\n" + "}\n" + "/**\n" + "Copy database from one server or name to another server or name.\n" + "Generally, you should dropDatabase() first as otherwise the copied information will MERGE\n" + "into whatever data is already present in this database (and you will get duplicate objects\n" + "in collections potentially.)\n" + "For security reasons this function only works when executed on the \"admin\" db. However,\n" + "if you have access to said db, you can copy any database from one place to another.\n" + "This method provides a way to \"rename\" a database by copying it to a new db name and\n" + "location. Additionally, it effectively provides a repair facility.\n" + "* @param {String} fromdb database name from which to copy.\n" + "* @param {String} todb database name to copy to.\n" + "* @param {String} fromhost hostname of the database (and optionally, \":port\") from which to\n" + "copy the data. default if unspecified is to copy from self.\n" + "* @return Object returned has member ok set to true if operation succeeds, false otherwise.\n" + "* See also: db.clone()\n" + "*/\n" + "DB.prototype.copyDatabase = function(fromdb, todb, fromhost) {\n" + "assert( isString(fromdb) && fromdb.length );\n" + "assert( isString(todb) && todb.length );\n" + "fromhost = fromhost || \"\";\n" + "return this._adminCommand( { copydb:1, fromhost:fromhost, fromdb:fromdb, todb:todb } );\n" + "}\n" + "/**\n" + "Repair database.\n" + "* @return Object returned has member ok set to true if operation succeeds, false otherwise.\n" + "*/\n" + "DB.prototype.repairDatabase = function() {\n" + "return this._dbCommand( { repairDatabase: 1 } );\n" + "}\n" + "DB.prototype.help = function() {\n" + "print(\"DB methods:\");\n" + "print(\"\\tdb.auth(username, password)\");\n" + "print(\"\\tdb.getMongo() get the server connection object\");\n" + "print(\"\\tdb.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair\");\n" + "print(\"\\tdb.getSisterDB(name) get the db at the same server as this onew\");\n" + "print(\"\\tdb.getName()\");\n" + "print(\"\\tdb.getCollection(cname) same as db['cname'] or db.cname\");\n" + "print(\"\\tdb.runCommand(cmdObj) run a database command. if cmdObj is a string, turns it into { cmdObj : 1 }\");\n" + "print(\"\\tdb.commandHelp(name) returns the help for the command\");\n" + "print(\"\\tdb.addUser(username, password)\");\n" + "print(\"\\tdb.removeUser(username)\");\n" + "print(\"\\tdb.createCollection(name, { size : ..., capped : ..., max : ... } )\");\n" + "print(\"\\tdb.getReplicationInfo()\");\n" + "print(\"\\tdb.printReplicationInfo()\");\n" + "print(\"\\tdb.printSlaveReplicationInfo()\");\n" + "print(\"\\tdb.getProfilingLevel()\");\n" + "print(\"\\tdb.setProfilingLevel(level) 0=off 1=slow 2=all\");\n" + "print(\"\\tdb.cloneDatabase(fromhost)\");\n" + "print(\"\\tdb.copyDatabase(fromdb, todb, fromhost)\");\n" + "print(\"\\tdb.shutdownServer()\");\n" + "print(\"\\tdb.dropDatabase()\");\n" + "print(\"\\tdb.repairDatabase()\");\n" + "print(\"\\tdb.eval(func, args) run code server-side\");\n" + "print(\"\\tdb.getLastError() - just returns the err msg string\");\n" + "print(\"\\tdb.getLastErrorObj() - return full status object\");\n" + "print(\"\\tdb.getPrevError()\");\n" + "print(\"\\tdb.resetError()\");\n" + "print(\"\\tdb.getCollectionNames()\");\n" + "print(\"\\tdb.currentOp() displays the current operation in the db\" );\n" + "print(\"\\tdb.killOp() kills the current operation in the db\" );\n" + "print(\"\\tdb.printCollectionStats()\" );\n" + "print(\"\\tdb.version() current version of the server\" );\n" + "}\n" + "DB.prototype.printCollectionStats = function(){\n" + "this.getCollectionNames().forEach(\n" + "function(z){\n" + "print( z );\n" + "printjson( db[z].stats() );\n" + "print( \"---\" );\n" + "}\n" + ");\n" + "}\n" + "/**\n" + "* <p> Set profiling level for your db. Profiling gathers stats on query performance. </p>\n" + "*\n" + "* <p>Default is off, and resets to off on a database restart -- so if you want it on,\n" + "* turn it on periodically. </p>\n" + "*\n" + "* <p>Levels :</p>\n" + "* <ul>\n" + "* <li>0=off</li>\n" + "* <li>1=log very slow (>100ms) operations</li>\n" + "* <li>2=log all</li>\n" + "* @param {String} level Desired level of profiling\n" + "* @return SOMETHING_FIXME or null on error\n" + "*/\n" + "DB.prototype.setProfilingLevel = function(level) {\n" + "if (level < 0 || level > 2) {\n" + "throw { dbSetProfilingException : \"input level \" + level + \" is out of range [0..2]\" };\n" + "}\n" + "if (level) {\n" + "this.createCollection(\"system.profile\", { capped: true, size: 128 * 1024 } );\n" + "}\n" + "return this._dbCommand( { profile: level } );\n" + "}\n" + "/**\n" + "* <p> Evaluate a js expression at the database server.</p>\n" + "*\n" + "* <p>Useful if you need to touch a lot of data lightly; in such a scenario\n" + "* the network transfer of the data could be a bottleneck. A good example\n" + "* is \"select count(*)\" -- can be done server side via this mechanism.\n" + "* </p>\n" + "*\n" + "* <p>\n" + "* If the eval fails, an exception is thrown of the form:\n" + "* </p>\n" + "* <code>{ dbEvalException: { retval: functionReturnValue, ok: num [, errno: num] [, errmsg: str] } }</code>\n" + "*\n" + "* <p>Example: </p>\n" + "* <code>print( \"mycount: \" + db.eval( function(){db.mycoll.find({},{_id:ObjId()}).length();} );</code>\n" + "*\n" + "* @param {Function} jsfunction Javascript function to run on server. Note this it not a closure, but rather just \"code\".\n" + "* @return result of your function, or null if error\n" + "*\n" + "*/\n" + "DB.prototype.eval = function(jsfunction) {\n" + "var cmd = { $eval : jsfunction };\n" + "if ( arguments.length > 1 ) {\n" + "cmd.args = argumentsToArray( arguments ).slice(1);\n" + "}\n" + "var res = this._dbCommand( cmd );\n" + "if (!res.ok)\n" + "throw tojson( res );\n" + "return res.retval;\n" + "}\n" + "DB.prototype.dbEval = DB.prototype.eval;\n" + "/**\n" + "*\n" + "* <p>\n" + "* Similar to SQL group by. For example: </p>\n" + "*\n" + "* <code>select a,b,sum(c) csum from coll where active=1 group by a,b</code>\n" + "*\n" + "* <p>\n" + "* corresponds to the following in 10gen:\n" + "* </p>\n" + "*\n" + "* <code>\n" + "db.group(\n" + "{\n" + "ns: \"coll\",\n" + "key: { a:true, b:true },\n" + "cond: { active:1 },\n" + "reduce: function(obj,prev) { prev.csum += obj.c; } ,\n" + "initial: { csum: 0 }\n" + "});\n" + "</code>\n" + "*\n" + "*\n" + "* <p>\n" + "* An array of grouped items is returned. The array must fit in RAM, thus this function is not\n" + "* suitable when the return set is extremely large.\n" + "* </p>\n" + "* <p>\n" + "* To order the grouped data, simply sort it client side upon return.\n" + "* <p>\n" + "Defaults\n" + "cond may be null if you want to run against all rows in the collection\n" + "keyf is a function which takes an object and returns the desired key. set either key or keyf (not both).\n" + "* </p>\n" + "*/\n" + "DB.prototype.groupeval = function(parmsObj) {\n" + "var groupFunction = function() {\n" + "var parms = args[0];\n" + "var c = db[parms.ns].find(parms.cond||{});\n" + "var map = new Map();\n" + "var pks = parms.key ? parms.key.keySet() : null;\n" + "var pkl = pks ? pks.length : 0;\n" + "var key = {};\n" + "while( c.hasNext() ) {\n" + "var obj = c.next();\n" + "if ( pks ) {\n" + "for( var i=0; i<pkl; i++ ){\n" + "var k = pks[i];\n" + "key[k] = obj[k];\n" + "}\n" + "}\n" + "else {\n" + "key = parms.$keyf(obj);\n" + "}\n" + "var aggObj = map.get(key);\n" + "if( aggObj == null ) {\n" + "var newObj = Object.extend({}, key);\n" + "aggObj = Object.extend(newObj, parms.initial)\n" + "map.put( key , aggObj );\n" + "}\n" + "parms.$reduce(obj, aggObj);\n" + "}\n" + "return map.values();\n" + "}\n" + "return this.eval(groupFunction, this._groupFixParms( parmsObj ));\n" + "}\n" + "DB.prototype.groupcmd = function( parmsObj ){\n" + "var ret = this.runCommand( { \"group\" : this._groupFixParms( parmsObj ) } );\n" + "if ( ! ret.ok ){\n" + "throw \"group command failed: \" + tojson( ret );\n" + "}\n" + "return ret.retval;\n" + "}\n" + "DB.prototype.group = DB.prototype.groupcmd;\n" + "DB.prototype._groupFixParms = function( parmsObj ){\n" + "var parms = Object.extend({}, parmsObj);\n" + "if( parms.reduce ) {\n" + "parms.$reduce = parms.reduce;\n" + "delete parms.reduce;\n" + "}\n" + "if( parms.keyf ) {\n" + "parms.$keyf = parms.keyf;\n" + "delete parms.keyf;\n" + "}\n" + "return parms;\n" + "}\n" + "DB.prototype.resetError = function(){\n" + "return this.runCommand( { reseterror : 1 } );\n" + "}\n" + "DB.prototype.forceError = function(){\n" + "return this.runCommand( { forceerror : 1 } );\n" + "}\n" + "DB.prototype.getLastError = function(){\n" + "var res = this.runCommand( { getlasterror : 1 } );\n" + "if ( ! res.ok )\n" + "throw \"getlasterror failed: \" + tojson( res );\n" + "return res.err;\n" + "}\n" + "DB.prototype.getLastErrorObj = function(){\n" + "var res = this.runCommand( { getlasterror : 1 } );\n" + "if ( ! res.ok )\n" + "throw \"getlasterror failed: \" + tojson( res );\n" + "return res;\n" + "}\n" + "/* Return the last error which has occurred, even if not the very last error.\n" + "Returns:\n" + "{ err : <error message>, nPrev : <how_many_ops_back_occurred>, ok : 1 }\n" + "result.err will be null if no error has occurred.\n" + "*/\n" + "DB.prototype.getPrevError = function(){\n" + "return this.runCommand( { getpreverror : 1 } );\n" + "}\n" + "DB.prototype.getCollectionNames = function(){\n" + "var all = [];\n" + "var nsLength = this._name.length + 1;\n" + "this.getCollection( \"system.namespaces\" ).find().sort({name:1}).forEach(\n" + "function(z){\n" + "var name = z.name;\n" + "if ( name.indexOf( \"$\" ) >= 0 )\n" + "return;\n" + "all.push( name.substring( nsLength ) );\n" + "}\n" + ");\n" + "return all;\n" + "}\n" + "DB.prototype.tojson = function(){\n" + "return this._name;\n" + "}\n" + "DB.prototype.toString = function(){\n" + "return this._name;\n" + "}\n" + "DB.prototype.currentOp = function(){\n" + "return db.$cmd.sys.inprog.findOne();\n" + "}\n" + "DB.prototype.currentOP = DB.prototype.currentOp;\n" + "DB.prototype.killOp = function(){\n" + "return db.$cmd.sys.killop.findOne();\n" + "}\n" + "DB.prototype.killOP = DB.prototype.killOp;\n" + "/**\n" + "Get a replication log information summary.\n" + "<p>\n" + "This command is for the database/cloud administer and not applicable to most databases.\n" + "It is only used with the local database. One might invoke from the JS shell:\n" + "<pre>\n" + "use local\n" + "db.getReplicationInfo();\n" + "</pre>\n" + "It is assumed that this database is a replication master -- the information returned is\n" + "about the operation log stored at local.oplog.$main on the replication master. (It also\n" + "works on a machine in a replica pair: for replica pairs, both machines are \"masters\" from\n" + "an internal database perspective.\n" + "<p>\n" + "* @return Object timeSpan: time span of the oplog from start to end if slave is more out\n" + "* of date than that, it can't recover without a complete resync\n" + "*/\n" + "DB.prototype.getReplicationInfo = function() {\n" + "var db = this.getSisterDB(\"local\");\n" + "var result = { };\n" + "var ol = db.system.namespaces.findOne({name:\"local.oplog.$main\"});\n" + "if( ol && ol.options ) {\n" + "result.logSizeMB = ol.options.size / 1000 / 1000;\n" + "} else {\n" + "result.errmsg = \"local.oplog.$main, or its options, not found in system.namespaces collection (not --master?)\";\n" + "return result;\n" + "}\n" + "var firstc = db.oplog.$main.find().sort({$natural:1}).limit(1);\n" + "var lastc = db.oplog.$main.find().sort({$natural:-1}).limit(1);\n" + "if( !firstc.hasNext() || !lastc.hasNext() ) {\n" + "result.errmsg = \"objects not found in local.oplog.$main -- is this a new and empty db instance?\";\n" + "result.oplogMainRowCount = db.oplog.$main.count();\n" + "return result;\n" + "}\n" + "var first = firstc.next();\n" + "var last = lastc.next();\n" + "{\n" + "var tfirst = first.ts;\n" + "var tlast = last.ts;\n" + "if( tfirst && tlast ) {\n" + "tfirst = tfirst / 4294967296;\n" + "tlast = tlast / 4294967296;\n" + "result.timeDiff = tlast - tfirst;\n" + "result.timeDiffHours = Math.round(result.timeDiff / 36)/100;\n" + "result.tFirst = (new Date(tfirst*1000)).toString();\n" + "result.tLast = (new Date(tlast*1000)).toString();\n" + "result.now = Date();\n" + "}\n" + "else {\n" + "result.errmsg = \"ts element not found in oplog objects\";\n" + "}\n" + "}\n" + "return result;\n" + "}\n" + "DB.prototype.printReplicationInfo = function() {\n" + "var result = this.getReplicationInfo();\n" + "if( result.errmsg ) {\n" + "print(tojson(result));\n" + "return;\n" + "}\n" + "print(\"configured oplog size: \" + result.logSizeMB + \"MB\");\n" + "print(\"log length start to end: \" + result.timeDiff + \"secs (\" + result.timeDiffHours + \"hrs)\");\n" + "print(\"oplog first event time: \" + result.tFirst);\n" + "print(\"oplog last event time: \" + result.tLast);\n" + "print(\"now: \" + result.now);\n" + "}\n" + "DB.prototype.printSlaveReplicationInfo = function() {\n" + "function g(x) {\n" + "print(\"source: \" + x.host);\n" + "var st = new Date(x.syncedTo/4294967296*1000);\n" + "var now = new Date();\n" + "print(\"syncedTo: \" + st.toString() );\n" + "var ago = (now-st)/1000;\n" + "var hrs = Math.round(ago/36)/100;\n" + "print(\" = \" + Math.round(ago) + \"secs ago (\" + hrs + \"hrs)\");\n" + "}\n" + "var L = this.getSisterDB(\"local\");\n" + "if( L.sources.count() == 0 ) {\n" + "print(\"local.sources is empty; is this db a --slave?\");\n" + "return;\n" + "}\n" + "L.sources.find().forEach(g);\n" + "}\n" + "DB.prototype.serverBuildInfo = function(){\n" + "return this._adminCommand( \"buildinfo\" );\n" + "}\n" + "DB.prototype.serverStatus = function(){\n" + "return this._adminCommand( \"serverStatus\" );\n" + "}\n" + "DB.prototype.version = function(){\n" + "return this.serverBuildInfo().version;\n" + "}\n" + "if ( typeof Mongo == \"undefined\" ){\n" + "Mongo = function( host ){\n" + "this.init( host );\n" + "}\n" + "}\n" + "if ( ! Mongo.prototype ){\n" + "throw \"Mongo.prototype not defined\";\n" + "}\n" + "if ( ! Mongo.prototype.find )\n" + "Mongo.prototype.find = function( ns , query , fields , limit , skip ){ throw \"find not implemented\"; }\n" + "if ( ! Mongo.prototype.insert )\n" + "Mongo.prototype.insert = function( ns , obj ){ throw \"insert not implemented\"; }\n" + "if ( ! Mongo.prototype.remove )\n" + "Mongo.prototype.remove = function( ns , pattern ){ throw \"remove not implemented;\" }\n" + "if ( ! Mongo.prototype.update )\n" + "Mongo.prototype.update = function( ns , query , obj , upsert ){ throw \"update not implemented;\" }\n" + "if ( typeof mongoInject == \"function\" ){\n" + "mongoInject( Mongo.prototype );\n" + "}\n" + "Mongo.prototype.setSlaveOk = function() {\n" + "this.slaveOk = true;\n" + "}\n" + "Mongo.prototype.getDB = function( name ){\n" + "return new DB( this , name );\n" + "}\n" + "Mongo.prototype.getDBs = function(){\n" + "var res = this.getDB( \"admin\" ).runCommand( { \"listDatabases\" : 1 } );\n" + "assert( res.ok == 1 , \"listDatabases failed\" );\n" + "return res;\n" + "}\n" + "Mongo.prototype.getDBNames = function(){\n" + "return this.getDBs().databases.map(\n" + "function(z){\n" + "return z.name;\n" + "}\n" + ");\n" + "}\n" + "Mongo.prototype.toString = function(){\n" + "return \"mongo connection to \" + this.host;\n" + "}\n" + "connect = function( url , user , pass ){\n" + "print( \"connecting to: \" + url )\n" + "if ( user && ! pass )\n" + "throw \"you specified a user and not a password. either you need a password, or you're using the old connect api\";\n" + "var idx = url.indexOf( \"/\" );\n" + "var db;\n" + "if ( idx < 0 )\n" + "db = new Mongo().getDB( url );\n" + "else\n" + "db = new Mongo( url.substring( 0 , idx ) ).getDB( url.substring( idx + 1 ) );\n" + "if ( user && pass ){\n" + "if ( ! db.auth( user , pass ) ){\n" + "throw \"couldn't login\";\n" + "}\n" + "}\n" + "return db;\n" + "}\n" + "MR = {};\n" + "MR.init = function(){\n" + "$max = 0;\n" + "$arr = [];\n" + "emit = MR.emit;\n" + "gc();\n" + "}\n" + "MR.cleanup = function(){\n" + "MR.init();\n" + "gc();\n" + "}\n" + "MR.emit = function(k,v){\n" + "var num = get_num( k );\n" + "var data = $arr[num];\n" + "if ( ! data ){\n" + "data = { key : k , values : [] };\n" + "$arr[num] = data;\n" + "}\n" + "data.values.push( v );\n" + "$max = Math.max( $max , data.values.length );\n" + "}\n" + "MR.doReduce = function( useDB ){\n" + "$max = 0;\n" + "for ( var i=0; i<$arr.length; i++){\n" + "var data = $arr[i];\n" + "if ( ! data )\n" + "continue;\n" + "if ( useDB ){\n" + "var x = tempcoll.findOne( { _id : data.key } );\n" + "if ( x ){\n" + "data.values.push( x.value );\n" + "}\n" + "}\n" + "var r = $reduce( data.key , data.values );\n" + "if ( r.length && r[0] ){\n" + "data.values = r;\n" + "}\n" + "else{\n" + "data.values = [ r ];\n" + "}\n" + "$max = Math.max( $max , data.values.length );\n" + "if ( useDB ){\n" + "if ( data.values.length == 1 ){\n" + "tempcoll.save( { _id : data.key , value : data.values[0] } );\n" + "}\n" + "else {\n" + "tempcoll.save( { _id : data.key , value : data.values } );\n" + "}\n" + "}\n" + "}\n" + "}\n" + "MR.check = function(){\n" + "if ( $max < 2000 && $arr.length < 1000 ){\n" + "return 0;\n" + "}\n" + "MR.doReduce();\n" + "if ( $max < 2000 && $arr.length < 1000 ){\n" + "return 1;\n" + "}\n" + "MR.doReduce( true );\n" + "$arr = [];\n" + "$max = 0;\n" + "reset_num();\n" + "gc();\n" + "return 2;\n" + "}\n" + "if ( typeof DBQuery == \"undefined\" ){\n" + "DBQuery = function( mongo , db , collection , ns , query , fields , limit , skip ){\n" + "this._mongo = mongo;\n" + "this._db = db;\n" + "this._collection = collection;\n" + "this._ns = ns;\n" + "this._query = query || {};\n" + "this._fields = fields;\n" + "this._limit = limit || 0;\n" + "this._skip = skip || 0;\n" + "this._cursor = null;\n" + "this._numReturned = 0;\n" + "this._special = false;\n" + "}\n" + "print( \"DBQuery probably won't have array access \" );\n" + "}\n" + "DBQuery.prototype.help = function(){\n" + "print( \"DBQuery help\" );\n" + "print( \"\\t.sort( {...} )\" )\n" + "print( \"\\t.limit( n )\" )\n" + "print( \"\\t.skip( n )\" )\n" + "print( \"\\t.count()\" )\n" + "print( \"\\t.explain()\" )\n" + "print( \"\\t.forEach( func )\" )\n" + "print( \"\\t.map( func )\" )\n" + "}\n" + "DBQuery.prototype.clone = function(){\n" + "var q = new DBQuery( this._mongo , this._db , this._collection , this._ns ,\n" + "this._query , this._fields ,\n" + "this._limit , this._skip );\n" + "q._special = this._special;\n" + "return q;\n" + "}\n" + "DBQuery.prototype._ensureSpecial = function(){\n" + "if ( this._special )\n" + "return;\n" + "var n = { query : this._query };\n" + "this._query = n;\n" + "this._special = true;\n" + "}\n" + "DBQuery.prototype._checkModify = function(){\n" + "if ( this._cursor )\n" + "throw \"query already executed\";\n" + "}\n" + "DBQuery.prototype._exec = function(){\n" + "if ( ! this._cursor ){\n" + "assert.eq( 0 , this._numReturned );\n" + "this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip );\n" + "this._cursorSeen = 0;\n" + "}\n" + "return this._cursor;\n" + "}\n" + "DBQuery.prototype.limit = function( limit ){\n" + "this._checkModify();\n" + "this._limit = limit;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.skip = function( skip ){\n" + "this._checkModify();\n" + "this._skip = skip;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.hasNext = function(){\n" + "this._exec();\n" + "if ( this._limit > 0 && this._cursorSeen >= this._limit )\n" + "return false;\n" + "var o = this._cursor.hasNext();\n" + "return o;\n" + "}\n" + "DBQuery.prototype.next = function(){\n" + "this._exec();\n" + "var o = this._cursor.hasNext();\n" + "if ( o )\n" + "this._cursorSeen++;\n" + "else\n" + "throw \"error hasNext: \" + o;\n" + "var ret = this._cursor.next();\n" + "if ( ret.$err && this._numReturned == 0 && ! this.hasNext() )\n" + "throw \"error: \" + tojson( ret );\n" + "this._numReturned++;\n" + "return ret;\n" + "}\n" + "DBQuery.prototype.toArray = function(){\n" + "if ( this._arr )\n" + "return this._arr;\n" + "var a = [];\n" + "while ( this.hasNext() )\n" + "a.push( this.next() );\n" + "this._arr = a;\n" + "return a;\n" + "}\n" + "DBQuery.prototype.count = function(){\n" + "var cmd = { count: this._collection.getName() };\n" + "if ( this._query ){\n" + "if ( this._special )\n" + "cmd.query = this._query.query;\n" + "else\n" + "cmd.query = this._query;\n" + "}\n" + "cmd.fields = this._fields || {};\n" + "var res = this._db.runCommand( cmd );\n" + "if( res && res.n != null ) return res.n;\n" + "throw \"count failed: \" + tojson( res );\n" + "}\n" + "DBQuery.prototype.countReturn = function(){\n" + "var c = this.count();\n" + "if ( this._skip )\n" + "c = c - this._skip;\n" + "if ( this._limit > 0 && this._limit < c )\n" + "return this._limit;\n" + "return c;\n" + "}\n" + "/**\n" + "* iterative count - only for testing\n" + "*/\n" + "DBQuery.prototype.itcount = function(){\n" + "var num = 0;\n" + "while ( this.hasNext() ){\n" + "num++;\n" + "this.next();\n" + "}\n" + "return num;\n" + "}\n" + "DBQuery.prototype.length = function(){\n" + "return this.toArray().length;\n" + "}\n" + "DBQuery.prototype.sort = function( sortBy ){\n" + "this._ensureSpecial();\n" + "this._query.orderby = sortBy;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.hint = function( hint ){\n" + "this._ensureSpecial();\n" + "this._query[\"$hint\"] = hint;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.min = function( min ) {\n" + "this._ensureSpecial();\n" + "this._query[\"$min\"] = min;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.max = function( max ) {\n" + "this._ensureSpecial();\n" + "this._query[\"$max\"] = max;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.forEach = function( func ){\n" + "while ( this.hasNext() )\n" + "func( this.next() );\n" + "}\n" + "DBQuery.prototype.map = function( func ){\n" + "var a = [];\n" + "while ( this.hasNext() )\n" + "a.push( func( this.next() ) );\n" + "return a;\n" + "}\n" + "DBQuery.prototype.arrayAccess = function( idx ){\n" + "return this.toArray()[idx];\n" + "}\n" + "DBQuery.prototype.explain = function(){\n" + "var n = this.clone();\n" + "n._ensureSpecial();\n" + "n._query.$explain = true;\n" + "n._limit = n._limit * -1;\n" + "return n.next();\n" + "}\n" + "DBQuery.prototype.snapshot = function(){\n" + "this._ensureSpecial();\n" + "this._query.$snapshot = true;\n" + "return this;\n" + "}\n" + "DBQuery.prototype.shellPrint = function(){\n" + "try {\n" + "var n = 0;\n" + "while ( this.hasNext() && n < 20 ){\n" + "var s = tojson( this.next() );\n" + "print( s );\n" + "n++;\n" + "}\n" + "if ( this.hasNext() ){\n" + "print( \"has more\" );\n" + "___it___ = this;\n" + "}\n" + "else {\n" + "___it___ = null;\n" + "}\n" + "}\n" + "catch ( e ){\n" + "print( e );\n" + "}\n" + "}\n" + "DBQuery.prototype.toString = function(){\n" + "return \"DBQuery: \" + this._ns + \" -> \" + tojson( this.query );\n" + "}\n" + "_parsePath = function() {\n" + "var dbpath = \"\";\n" + "for( var i = 0; i < arguments.length; ++i )\n" + "if ( arguments[ i ] == \"--dbpath\" )\n" + "dbpath = arguments[ i + 1 ];\n" + "if ( dbpath == \"\" )\n" + "throw \"No dbpath specified\";\n" + "return dbpath;\n" + "}\n" + "_parsePort = function() {\n" + "var port = \"\";\n" + "for( var i = 0; i < arguments.length; ++i )\n" + "if ( arguments[ i ] == \"--port\" )\n" + "port = arguments[ i + 1 ];\n" + "if ( port == \"\" )\n" + "throw \"No port specified\";\n" + "return port;\n" + "}\n" + "createMongoArgs = function( binaryName , args ){\n" + "var fullArgs = [ binaryName ];\n" + "if ( args.length == 1 && isObject( args[0] ) ){\n" + "var o = args[0];\n" + "for ( var k in o ){\n" + "if ( k == \"v\" && isNumber( o[k] ) ){\n" + "var n = o[k];\n" + "if ( n > 0 ){\n" + "var temp = \"-\";\n" + "while ( n-- > 0 ) temp += \"v\";\n" + "fullArgs.push( temp );\n" + "}\n" + "}\n" + "else {\n" + "fullArgs.push( \"--\" + k );\n" + "if ( o[k] != \"\" )\n" + "fullArgs.push( \"\" + o[k] );\n" + "}\n" + "}\n" + "}\n" + "else {\n" + "for ( var i=0; i<args.length; i++ )\n" + "fullArgs.push( args[i] )\n" + "}\n" + "return fullArgs;\n" + "}\n" + "startMongod = function(){\n" + "var args = createMongoArgs( \"mongod\" , arguments );\n" + "var dbpath = _parsePath.apply( null, args );\n" + "resetDbpath( dbpath );\n" + "return startMongoProgram.apply( null, args );\n" + "}\n" + "startMongos = function(){\n" + "return startMongoProgram.apply( null, createMongoArgs( \"mongos\" , arguments ) );\n" + "}\n" + "startMongoProgram = function(){\n" + "var port = _parsePort.apply( null, arguments );\n" + "_startMongoProgram.apply( null, arguments );\n" + "var m;\n" + "assert.soon\n" + "( function() {\n" + "try {\n" + "m = new Mongo( \"127.0.0.1:\" + port );\n" + "return true;\n" + "} catch( e ) {\n" + "}\n" + "return false;\n" + "}, \"unable to connect to mongo program on port \" + port, 30000 );\n" + "return m;\n" + "}\n" + "startMongoProgramNoConnect = function() {\n" + "return _startMongoProgram.apply( null, arguments );\n" + "}\n" + "myPort = function() {\n" + "var m = db.getMongo();\n" + "if ( m.host.match( /:/ ) )\n" + "return m.host.match( /:(.*)/ )[ 1 ];\n" + "else\n" + "return 27017;\n" + "}\n" + "ShardingTest = function( testName , numServers , verboseLevel , numMongos ){\n" + "this._connections = [];\n" + "this._serverNames = [];\n" + "for ( var i=0; i<numServers; i++){\n" + "var conn = startMongod( { port : 30000 + i , dbpath : \"/data/db/\" + testName + i , noprealloc : \"\" } );\n" + "conn.name = \"localhost:\" + ( 30000 + i );\n" + "this._connections.push( conn );\n" + "this._serverNames.push( conn.name );\n" + "}\n" + "this._configDB = \"localhost:30000\";\n" + "this._mongos = [];\n" + "var startMongosPort = 39999;\n" + "for ( var i=0; i<(numMongos||1); i++ ){\n" + "var myPort = startMongosPort - i;\n" + "var conn = startMongos( { port : startMongosPort - i , v : verboseLevel || 0 , configdb : this._configDB } );\n" + "conn.name = \"localhost:\" + myPort;\n" + "this._mongos.push( conn );\n" + "if ( i == 0 )\n" + "this.s = conn;\n" + "}\n" + "var admin = this.admin = this.s.getDB( \"admin\" );\n" + "this.config = this.s.getDB( \"config\" );\n" + "this._serverNames.forEach(\n" + "function(z){\n" + "admin.runCommand( { addshard : z } );\n" + "}\n" + ");\n" + "}\n" + "ShardingTest.prototype.getDB = function( name ){\n" + "return this.s.getDB( name );\n" + "}\n" + "ShardingTest.prototype.getServerName = function( dbname ){\n" + "return this.config.databases.findOne( { name : dbname } ).primary;\n" + "}\n" + "ShardingTest.prototype.getServer = function( dbname ){\n" + "var name = this.getServerName( dbname );\n" + "for ( var i=0; i<this._serverNames.length; i++ ){\n" + "if ( name == this._serverNames[i] )\n" + "return this._connections[i];\n" + "}\n" + "throw \"can't find server for: \" + dbname + \" name:\" + name;\n" + "}\n" + "ShardingTest.prototype.getOther = function( one ){\n" + "if ( this._connections.length != 2 )\n" + "throw \"getOther only works with 2 servers\";\n" + "if ( this._connections[0] == one )\n" + "return this._connections[1];\n" + "return this._connections[0];\n" + "}\n" + "ShardingTest.prototype.stop = function(){\n" + "for ( var i=0; i<this._mongos.length; i++ ){\n" + "stopMongoProgram( 39999 - i );\n" + "}\n" + "for ( var i=0; i<this._connections.length; i++){\n" + "stopMongod( 30000 + i );\n" + "}\n" + "}\n" + "ShardingTest.prototype.adminCommand = function(cmd){\n" + "var res = this.admin.runCommand( cmd );\n" + "if ( res && res.ok == 1 )\n" + "return true;\n" + "throw \"command \" + tojson( cmd ) + \" failed: \" + tojson( res );\n" + "}\n" + "ShardingTest.prototype.getChunksString = function( ns ){\n" + "var q = {}\n" + "if ( ns )\n" + "q.ns = ns;\n" + "return Array.tojson( this.config.chunks.find( q ).toArray() , \"\\n\" );\n" + "}\n" + "ShardingTest.prototype.printChunks = function( ns ){\n" + "print( this.getChunksString( ns ) );\n" + "}\n" + "ShardingTest.prototype.sync = function(){\n" + "this.adminCommand( \"connpoolsync\" );\n" + "}\n" + "MongodRunner = function( port, dbpath, peer, arbiter, extraArgs ) {\n" + "this.port_ = port;\n" + "this.dbpath_ = dbpath;\n" + "this.peer_ = peer;\n" + "this.arbiter_ = arbiter;\n" + "this.extraArgs_ = extraArgs;\n" + "}\n" + "MongodRunner.prototype.start = function( reuseData ) {\n" + "var args = [];\n" + "if ( reuseData ) {\n" + "args.push( \"mongod\" );\n" + "}\n" + "args.push( \"--port\" );\n" + "args.push( this.port_ );\n" + "args.push( \"--dbpath\" );\n" + "args.push( this.dbpath_ );\n" + "if ( this.peer_ && this.arbiter_ ) {\n" + "args.push( \"--pairwith\" );\n" + "args.push( this.peer_ );\n" + "args.push( \"--arbiter\" );\n" + "args.push( this.arbiter_ );\n" + "args.push( \"--oplogSize\" );\n" + "args.push( \"1\" );\n" + "}\n" + "args.push( \"--nohttpinterface\" );\n" + "args.push( \"--noprealloc\" );\n" + "args.push( \"--bind_ip\" );\n" + "args.push( \"127.0.0.1\" );\n" + "if ( this.extraArgs_ ) {\n" + "args = args.concat( this.extraArgs_ );\n" + "}\n" + "if ( reuseData ) {\n" + "return startMongoProgram.apply( null, args );\n" + "} else {\n" + "return startMongod.apply( null, args );\n" + "}\n" + "}\n" + "MongodRunner.prototype.port = function() { return this.port_; }\n" + "MongodRunner.prototype.toString = function() { return [ this.port_, this.dbpath_, this.peer_, this.arbiter_ ].toString(); }\n" + "ReplPair = function( left, right, arbiter ) {\n" + "this.left_ = left;\n" + "this.leftC_ = null;\n" + "this.right_ = right;\n" + "this.rightC_ = null;\n" + "this.arbiter_ = arbiter;\n" + "this.arbiterC_ = null;\n" + "this.master_ = null;\n" + "this.slave_ = null;\n" + "}\n" + "ReplPair.prototype.start = function( reuseData ) {\n" + "if ( this.arbiterC_ == null ) {\n" + "this.arbiterC_ = this.arbiter_.start();\n" + "}\n" + "if ( this.leftC_ == null ) {\n" + "this.leftC_ = this.left_.start( reuseData );\n" + "}\n" + "if ( this.rightC_ == null ) {\n" + "this.rightC_ = this.right_.start( reuseData );\n" + "}\n" + "}\n" + "ReplPair.prototype.isMaster = function( mongo, debug ) {\n" + "var im = mongo.getDB( \"admin\" ).runCommand( { ismaster : 1 } );\n" + "assert( im && im.ok, \"command ismaster failed\" );\n" + "if ( debug ) {\n" + "printjson( im );\n" + "}\n" + "return im.ismaster;\n" + "}\n" + "ReplPair.prototype.isInitialSyncComplete = function( mongo, debug ) {\n" + "var isc = mongo.getDB( \"admin\" ).runCommand( { isinitialsynccomplete : 1 } );\n" + "assert( isc && isc.ok, \"command isinitialsynccomplete failed\" );\n" + "if ( debug ) {\n" + "printjson( isc );\n" + "}\n" + "return isc.initialsynccomplete;\n" + "}\n" + "ReplPair.prototype.checkSteadyState = function( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ) {\n" + "leftValues = leftValues || {};\n" + "rightValues = rightValues || {};\n" + "var lm = null;\n" + "var lisc = null;\n" + "if ( this.leftC_ != null ) {\n" + "lm = this.isMaster( this.leftC_, debug );\n" + "leftValues[ lm ] = true;\n" + "lisc = this.isInitialSyncComplete( this.leftC_, debug );\n" + "}\n" + "var rm = null;\n" + "var risc = null;\n" + "if ( this.rightC_ != null ) {\n" + "rm = this.isMaster( this.rightC_, debug );\n" + "rightValues[ rm ] = true;\n" + "risc = this.isInitialSyncComplete( this.rightC_, debug );\n" + "}\n" + "var stateSet = {}\n" + "state.forEach( function( i ) { stateSet[ i ] = true; } );\n" + "if ( !( 1 in stateSet ) || ( ( risc || risc == null ) && ( lisc || lisc == null ) ) ) {\n" + "if ( rm == 1 && lm != 1 ) {\n" + "assert( twoMasterOk || !( 1 in leftValues ) );\n" + "this.master_ = this.rightC_;\n" + "this.slave_ = this.leftC_;\n" + "} else if ( lm == 1 && rm != 1 ) {\n" + "assert( twoMasterOk || !( 1 in rightValues ) );\n" + "this.master_ = this.leftC_;\n" + "this.slave_ = this.rightC_;\n" + "}\n" + "if ( !twoMasterOk ) {\n" + "assert( lm != 1 || rm != 1, \"two masters\" );\n" + "}\n" + "if ( state.sort().toString() == [ lm, rm ].sort().toString() ) {\n" + "if ( expectedMasterHost != null ) {\n" + "if( expectedMasterHost == this.master_.host ) {\n" + "return true;\n" + "}\n" + "} else {\n" + "return true;\n" + "}\n" + "}\n" + "}\n" + "this.master_ = null;\n" + "this.slave_ = null;\n" + "return false;\n" + "}\n" + "ReplPair.prototype.waitForSteadyState = function( state, expectedMasterHost, twoMasterOk, debug ) {\n" + "state = state || [ 1, 0 ];\n" + "twoMasterOk = twoMasterOk || false;\n" + "var rp = this;\n" + "var leftValues = {};\n" + "var rightValues = {};\n" + "assert.soon( function() { return rp.checkSteadyState( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ); },\n" + "\"rp (\" + rp + \") failed to reach expected steady state (\" + state + \")\" );\n" + "}\n" + "ReplPair.prototype.master = function() { return this.master_; }\n" + "ReplPair.prototype.slave = function() { return this.slave_; }\n" + "ReplPair.prototype.right = function() { return this.rightC_; }\n" + "ReplPair.prototype.left = function() { return this.leftC_; }\n" + "ReplPair.prototype.killNode = function( mongo, signal ) {\n" + "signal = signal || 15;\n" + "if ( this.leftC_ != null && this.leftC_.host == mongo.host ) {\n" + "stopMongod( this.left_.port_ );\n" + "this.leftC_ = null;\n" + "}\n" + "if ( this.rightC_ != null && this.rightC_.host == mongo.host ) {\n" + "stopMongod( this.right_.port_ );\n" + "this.rightC_ = null;\n" + "}\n" + "}\n" + "ReplPair.prototype._annotatedNode = function( mongo ) {\n" + "var ret = \"\";\n" + "if ( mongo != null ) {\n" + "ret += \" (connected)\";\n" + "if ( this.master_ != null && mongo.host == this.master_.host ) {\n" + "ret += \"(master)\";\n" + "}\n" + "if ( this.slave_ != null && mongo.host == this.slave_.host ) {\n" + "ret += \"(slave)\";\n" + "}\n" + "}\n" + "return ret;\n" + "}\n" + "ReplPair.prototype.toString = function() {\n" + "var ret = \"\";\n" + "ret += \"left: \" + this.left_;\n" + "ret += \" \" + this._annotatedNode( this.leftC_ );\n" + "ret += \" right: \" + this.right_;\n" + "ret += \" \" + this._annotatedNode( this.rightC_ );\n" + "return ret;\n" + "}\n" + "friendlyEqual = function( a , b ){\n" + "if ( a == b )\n" + "return true;\n" + "if ( tojson( a ) == tojson( b ) )\n" + "return true;\n" + "return false;\n" + "}\n" + "doassert = function( msg ){\n" + "print( \"assert: \" + msg );\n" + "throw msg;\n" + "}\n" + "assert = function( b , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( b )\n" + "return;\n" + "doassert( \"assert failed : \" + msg );\n" + "}\n" + "assert._debug = false;\n" + "assert.eq = function( a , b , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( a == b )\n" + "return;\n" + "if ( ( a != null && b != null ) && friendlyEqual( a , b ) )\n" + "return;\n" + "doassert( \"[\" + tojson( a ) + \"] != [\" + tojson( b ) + \"] are not equal : \" + msg );\n" + "}\n" + "assert.neq = function( a , b , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( a != b )\n" + "return;\n" + "doassert( \"[\" + a + \"] != [\" + b + \"] are equal : \" + msg );\n" + "}\n" + "assert.soon = function( f, msg, timeout, interval ) {\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "var start = new Date();\n" + "timeout = timeout || 30000;\n" + "interval = interval || 200;\n" + "var last;\n" + "while( 1 ) {\n" + "if ( typeof( f ) == \"string\" ){\n" + "if ( eval( f ) )\n" + "return;\n" + "}\n" + "else {\n" + "if ( f() )\n" + "return;\n" + "}\n" + "if ( ( new Date() ).getTime() - start.getTime() > timeout )\n" + "doassert( \"assert.soon failed: \" + f + \", msg:\" + msg );\n" + "sleep( interval );\n" + "}\n" + "}\n" + "assert.throws = function( func , params , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "try {\n" + "func.apply( null , params );\n" + "}\n" + "catch ( e ){\n" + "return e;\n" + "}\n" + "doassert( \"did not throw exception: \" + msg );\n" + "}\n" + "assert.commandWorked = function( res , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( res.ok == 1 )\n" + "return;\n" + "doassert( \"command failed: \" + tojson( res ) + \" : \" + msg );\n" + "}\n" + "assert.commandFailed = function( res , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( res.ok == 0 )\n" + "return;\n" + "doassert( \"command worked when it should have failed: \" + tojson( res ) + \" : \" + msg );\n" + "}\n" + "assert.isnull = function( what , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( what == null )\n" + "return;\n" + "doassert( \"supposed to null (\" + ( msg || \"\" ) + \") was: \" + tojson( what ) );\n" + "}\n" + "assert.lt = function( a , b , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( a < b )\n" + "return;\n" + "doassert( a + \" is not less than \" + b + \" : \" + msg );\n" + "}\n" + "assert.gt = function( a , b , msg ){\n" + "if ( assert._debug && msg ) print( \"in assert for: \" + msg );\n" + "if ( a > b )\n" + "return;\n" + "doassert( a + \" is not greater than \" + b + \" : \" + msg );\n" + "}\n" + "Object.extend = function( dst , src ){\n" + "for ( var k in src ){\n" + "dst[k] = src[k];\n" + "}\n" + "return dst;\n" + "}\n" + "argumentsToArray = function( a ){\n" + "var arr = [];\n" + "for ( var i=0; i<a.length; i++ )\n" + "arr[i] = a[i];\n" + "return arr;\n" + "}\n" + "isString = function( x ){\n" + "return typeof( x ) == \"string\";\n" + "}\n" + "isNumber = function(x){\n" + "return typeof( x ) == \"number\";\n" + "}\n" + "isObject = function( x ){\n" + "return typeof( x ) == \"object\";\n" + "}\n" + "String.prototype.trim = function() {\n" + "return this.replace(/^\\s+|\\s+$/g,\"\");\n" + "}\n" + "String.prototype.ltrim = function() {\n" + "return this.replace(/^\\s+/,\"\");\n" + "}\n" + "String.prototype.rtrim = function() {\n" + "return this.replace(/\\s+$/,\"\");\n" + "}\n" + "Date.timeFunc = function( theFunc , numTimes ){\n" + "var start = new Date();\n" + "numTimes = numTimes || 1;\n" + "for ( var i=0; i<numTimes; i++ ){\n" + "theFunc.apply( null , argumentsToArray( arguments ).slice( 2 ) );\n" + "}\n" + "return (new Date()).getTime() - start.getTime();\n" + "}\n" + "Date.prototype.tojson = function(){\n" + "return \"\\\"\" + this.toString() + \"\\\"\";\n" + "}\n" + "RegExp.prototype.tojson = RegExp.prototype.toString;\n" + "Array.contains = function( a , x ){\n" + "for ( var i=0; i<a.length; i++ ){\n" + "if ( a[i] == x )\n" + "return true;\n" + "}\n" + "return false;\n" + "}\n" + "Array.unique = function( a ){\n" + "var u = [];\n" + "for ( var i=0; i<a.length; i++){\n" + "var o = a[i];\n" + "if ( ! Array.contains( u , o ) ){\n" + "u.push( o );\n" + "}\n" + "}\n" + "return u;\n" + "}\n" + "Array.shuffle = function( arr ){\n" + "for ( var i=0; i<arr.length-1; i++ ){\n" + "var pos = i+Math.floor(Math.random()*(arr.length-i));\n" + "var save = arr[i];\n" + "arr[i] = arr[pos];\n" + "arr[pos] = save;\n" + "}\n" + "return arr;\n" + "}\n" + "Array.tojson = function( a , sepLines ){\n" + "var s = \"[\";\n" + "if ( sepLines ) s += \"\\n\";\n" + "for ( var i=0; i<a.length; i++){\n" + "if ( i > 0 ){\n" + "s += \",\";\n" + "if ( sepLines ) s += \"\\n\";\n" + "}\n" + "s += tojson( a[i] );\n" + "}\n" + "s += \"]\";\n" + "if ( sepLines ) s += \"\\n\";\n" + "return s;\n" + "}\n" + "Array.fetchRefs = function( arr , coll ){\n" + "var n = [];\n" + "for ( var i=0; i<arr.length; i ++){\n" + "var z = arr[i];\n" + "if ( coll && coll != z.getCollection() )\n" + "continue;\n" + "n.push( z.fetch() );\n" + "}\n" + "return n;\n" + "}\n" + "if ( ! ObjectId.prototype )\n" + "ObjectId.prototype = {}\n" + "ObjectId.prototype.toString = function(){\n" + "return this.str;\n" + "}\n" + "ObjectId.prototype.tojson = function(){\n" + "return \" ObjectId( \\\"\" + this.str + \"\\\") \";\n" + "}\n" + "ObjectId.prototype.isObjectId = true;\n" + "if ( typeof( DBPointer ) != \"undefined\" ){\n" + "DBPointer.prototype.fetch = function(){\n" + "assert( this.ns , \"need a ns\" );\n" + "assert( this.id , \"need an id\" );\n" + "return db[ this.ns ].findOne( { _id : this.id } );\n" + "}\n" + "DBPointer.prototype.tojson = function(){\n" + "return \"{ 'ns' : \\\"\" + this.ns + \"\\\" , 'id' : \\\"\" + this.id + \"\\\" } \";\n" + "}\n" + "DBPointer.prototype.getCollection = function(){\n" + "return this.ns;\n" + "}\n" + "DBPointer.prototype.toString = function(){\n" + "return \"DBPointer \" + this.ns + \":\" + this.id;\n" + "}\n" + "}\n" + "else {\n" + "print( \"warning: no DBPointer\" );\n" + "}\n" + "if ( typeof( DBRef ) != \"undefined\" ){\n" + "DBRef.prototype.fetch = function(){\n" + "assert( this.$ref , \"need a ns\" );\n" + "assert( this.$id , \"need an id\" );\n" + "return db[ this.$ref ].findOne( { _id : this.$id } );\n" + "}\n" + "DBRef.prototype.tojson = function(){\n" + "return \"{ '$ref' : \\\"\" + this.$ref + \"\\\" , '$id' : \\\"\" + this.$id + \"\\\" } \";\n" + "}\n" + "DBRef.prototype.getCollection = function(){\n" + "return this.$ref;\n" + "}\n" + "DBRef.prototype.toString = function(){\n" + "return this.tojson();\n" + "}\n" + "}\n" + "else {\n" + "print( \"warning: no DBRef\" );\n" + "}\n" + "if ( typeof( BinData ) != \"undefined\" ){\n" + "BinData.prototype.tojson = function(){\n" + "return \"BinData type: \" + this.type + \" len: \" + this.len;\n" + "}\n" + "}\n" + "else {\n" + "print( \"warning: no BinData\" );\n" + "}\n" + "tojson = function( x ){\n" + "if ( x == null )\n" + "return \"null\";\n" + "if ( x == undefined )\n" + "return \"\";\n" + "switch ( typeof x ){\n" + "case \"string\": {\n" + "var s = \"\\\"\";\n" + "for ( var i=0; i<x.length; i++ ){\n" + "if ( x[i] == '\"' ){\n" + "s += \"\\\\\\\"\";\n" + "}\n" + "else\n" + "s += x[i];\n" + "}\n" + "return s + \"\\\"\";\n" + "}\n" + "case \"number\":\n" + "case \"boolean\":\n" + "return \"\" + x;\n" + "case \"object\":\n" + "return tojsonObject( x );\n" + "case \"function\":\n" + "return x.toString();\n" + "default:\n" + "throw \"tojson can't handle type \" + ( typeof x );\n" + "}\n" + "}\n" + "tojsonObject = function( x ){\n" + "assert.eq( ( typeof x ) , \"object\" , \"tojsonObject needs object, not [\" + ( typeof x ) + \"]\" );\n" + "if ( typeof( x.tojson ) == \"function\" && x.tojson != tojson )\n" + "return x.tojson();\n" + "if ( typeof( x.constructor.tojson ) == \"function\" && x.constructor.tojson != tojson )\n" + "return x.constructor.tojson( x );\n" + "if ( x.toString() == \"[object MaxKey]\" )\n" + "return \"{ $maxKey : 1 }\";\n" + "if ( x.toString() == \"[object MinKey]\" )\n" + "return \"{ $minKey : 1 }\";\n" + "var s = \"{\";\n" + "var first = true;\n" + "for ( var k in x ){\n" + "var val = x[k];\n" + "if ( val == DB.prototype || val == DBCollection.prototype )\n" + "continue;\n" + "if ( first ) first = false;\n" + "else s += \" , \";\n" + "s += \"\\\"\" + k + \"\\\" : \" + tojson( val );\n" + "}\n" + "return s + \"}\";\n" + "}\n" + "shellPrint = function( x ){\n" + "it = x;\n" + "if ( x != undefined )\n" + "shellPrintHelper( x );\n" + "if ( db ){\n" + "var e = db.getPrevError();\n" + "if ( e.err ) {\n" + "if( e.nPrev <= 1 )\n" + "print( \"error on last call: \" + tojson( e.err ) );\n" + "else\n" + "print( \"an error \" + tojson(e.err) + \" occurred \" + e.nPrev + \" operations back in the command invocation\" );\n" + "}\n" + "db.resetError();\n" + "}\n" + "}\n" + "printjson = function(x){\n" + "print( tojson( x ) );\n" + "}\n" + "shellPrintHelper = function( x ){\n" + "if ( typeof( x ) == \"undefined\" ){\n" + "if ( typeof( db ) != \"undefined\" && db.getLastError ){\n" + "var e = db.getLastError();\n" + "if ( e != null )\n" + "print( e );\n" + "}\n" + "return;\n" + "}\n" + "if ( x == null ){\n" + "print( \"null\" );\n" + "return;\n" + "}\n" + "if ( typeof x != \"object\" )\n" + "return print( x );\n" + "var p = x.shellPrint;\n" + "if ( typeof p == \"function\" )\n" + "return x.shellPrint();\n" + "var p = x.tojson;\n" + "if ( typeof p == \"function\" )\n" + "print( x.tojson() );\n" + "else\n" + "print( tojson( x ) );\n" + "}\n" + "shellHelper = function( command , rest , shouldPrint ){\n" + "command = command.trim();\n" + "var args = rest.trim().replace(/;$/,\"\").split( \"\\s+\" );\n" + "if ( ! shellHelper[command] )\n" + "throw \"no command [\" + command + \"]\";\n" + "var res = shellHelper[command].apply( null , args );\n" + "if ( shouldPrint ){\n" + "shellPrintHelper( res );\n" + "}\n" + "return res;\n" + "}\n" + "help = shellHelper.help = function(){\n" + "print( \"HELP\" );\n" + "print( \"\\t\" + \"show dbs show database names\");\n" + "print( \"\\t\" + \"show collections show collections in current database\");\n" + "print( \"\\t\" + \"show users show users in current database\");\n" + "print( \"\\t\" + \"show profile show most recent system.profile entries with time >= 1ms\");\n" + "print( \"\\t\" + \"use <db name> set curent database to <db name>\" );\n" + "print( \"\\t\" + \"db.help() help on DB methods\");\n" + "print( \"\\t\" + \"db.foo.help() help on collection methods\");\n" + "print( \"\\t\" + \"db.foo.find() list objects in collection foo\" );\n" + "print( \"\\t\" + \"db.foo.find( { a : 1 } ) list objects in foo where a == 1\" );\n" + "print( \"\\t\" + \"it result of the last line evaluated; use to further iterate\");\n" + "}\n" + "shellHelper.use = function( dbname ){\n" + "db = db.getMongo().getDB( dbname );\n" + "print( \"switched to db \" + db.getName() );\n" + "}\n" + "shellHelper.it = function(){\n" + "if ( typeof( ___it___ ) == \"undefined\" || ___it___ == null ){\n" + "print( \"no cursor\" );\n" + "return;\n" + "}\n" + "shellPrintHelper( ___it___ );\n" + "}\n" + "shellHelper.show = function( what ){\n" + "assert( typeof what == \"string\" );\n" + "if( what == \"profile\" ) {\n" + "if( db.system.profile.count() == 0 ) {\n" + "print(\"db.system.profile is empty\");\n" + "print(\"Use db.setProfilingLevel(2) will enable profiling\");\n" + "print(\"Use db.system.profile.find() to show raw profile entries\");\n" + "}\n" + "else {\n" + "print();\n" + "db.system.profile.find({ millis : { $gt : 0 } }).sort({$natural:-1}).limit(5).forEach( function(x){print(\"\"+x.millis+\"ms \" + String(x.ts).substring(0,24)); print(x.info); print(\"\\n\");} )\n" + "}\n" + "return \"\";\n" + "}\n" + "if ( what == \"users\" ){\n" + "db.system.users.find().forEach( printjson );\n" + "return \"\";\n" + "}\n" + "if ( what == \"collections\" || what == \"tables\" ) {\n" + "db.getCollectionNames().forEach( function(x){print(x)} );\n" + "return \"\";\n" + "}\n" + "if ( what == \"dbs\" ) {\n" + "db.getMongo().getDBNames().sort().forEach( function(x){print(x)} );\n" + "return \"\";\n" + "}\n" + "throw \"don't know how to show [\" + what + \"]\";\n" + "}\n" + "if ( typeof( Map ) == \"undefined\" ){\n" + "Map = function(){\n" + "this._data = {};\n" + "}\n" + "}\n" + "Map.hash = function( val ){\n" + "if ( ! val )\n" + "return val;\n" + "switch ( typeof( val ) ){\n" + "case 'string':\n" + "case 'number':\n" + "case 'date':\n" + "return val.toString();\n" + "case 'object':\n" + "case 'array':\n" + "var s = \"\";\n" + "for ( var k in val ){\n" + "s += k + val[k];\n" + "}\n" + "return s;\n" + "}\n" + "throw \"can't hash : \" + typeof( val );\n" + "}\n" + "Map.prototype.put = function( key , value ){\n" + "var o = this._get( key );\n" + "var old = o.value;\n" + "o.value = value;\n" + "return old;\n" + "}\n" + "Map.prototype.get = function( key ){\n" + "return this._get( key ).value;\n" + "}\n" + "Map.prototype._get = function( key ){\n" + "var h = Map.hash( key );\n" + "var a = this._data[h];\n" + "if ( ! a ){\n" + "a = [];\n" + "this._data[h] = a;\n" + "}\n" + "for ( var i=0; i<a.length; i++ ){\n" + "if ( friendlyEqual( key , a[i].key ) ){\n" + "return a[i];\n" + "}\n" + "}\n" + "var o = { key : key , value : null };\n" + "a.push( o );\n" + "return o;\n" + "}\n" + "Map.prototype.values = function(){\n" + "var all = [];\n" + "for ( var k in this._data ){\n" + "this._data[k].forEach( function(z){ all.push( z.value ); } );\n" + "}\n" + "return all;\n" + "}\n" + "Math.sigFig = function( x , N ){\n" + "if ( ! N ){\n" + "N = 3;\n" + "}\n" + "var p = Math.pow( 10, N - Math.ceil( Math.log( Math.abs(x) ) / Math.log( 10 )) );\n" + "return Math.round(x*p)/p;\n" + "}\n" + ; + diff --git a/shell/mr.js b/shell/mr.js new file mode 100644 index 0000000..7b0814d --- /dev/null +++ b/shell/mr.js @@ -0,0 +1,95 @@ +// mr.js + +MR = {}; + +MR.init = function(){ + $max = 0; + $arr = []; + emit = MR.emit; + $numEmits = 0; + $numReduces = 0; + $numReducesToDB = 0; + gc(); // this is just so that keep memory size sane +} + +MR.cleanup = function(){ + MR.init(); + gc(); +} + +MR.emit = function(k,v){ + $numEmits++; + var num = nativeHelper.apply( get_num_ , [ k ] ); + var data = $arr[num]; + if ( ! data ){ + data = { key : k , values : new Array(1000) , count : 0 }; + $arr[num] = data; + } + data.values[data.count++] = v; + $max = Math.max( $max , data.count ); +} + +MR.doReduce = function( useDB ){ + $numReduces++; + if ( useDB ) + $numReducesToDB++; + $max = 0; + for ( var i=0; i<$arr.length; i++){ + var data = $arr[i]; + if ( ! data ) + continue; + + if ( useDB ){ + var x = tempcoll.findOne( { _id : data.key } ); + if ( x ){ + data.values[data.count++] = x.value; + } + } + + var r = $reduce( data.key , data.values.slice( 0 , data.count ) ); + if ( r && r.length && r[0] ){ + data.values = r; + data.count = r.length; + } + else{ + data.values[0] = r; + data.count = 1; + } + + $max = Math.max( $max , data.count ); + + if ( useDB ){ + if ( data.count == 1 ){ + tempcoll.save( { _id : data.key , value : data.values[0] } ); + } + else { + tempcoll.save( { _id : data.key , value : data.values.slice( 0 , data.count ) } ); + } + } + } +} + +MR.check = function(){ + if ( $max < 2000 && $arr.length < 1000 ){ + return 0; + } + MR.doReduce(); + if ( $max < 2000 && $arr.length < 1000 ){ + return 1; + } + MR.doReduce( true ); + $arr = []; + $max = 0; + reset_num(); + gc(); + return 2; +} + +MR.finalize = function(){ + tempcoll.find().forEach( + function(z){ + z.value = $finalize( z._id , z.value ); + tempcoll.save( z ); + } + ); +} diff --git a/shell/query.js b/shell/query.js new file mode 100644 index 0000000..aabd12e --- /dev/null +++ b/shell/query.js @@ -0,0 +1,248 @@ +// query.js + +if ( typeof DBQuery == "undefined" ){ + DBQuery = function( mongo , db , collection , ns , query , fields , limit , skip ){ + + this._mongo = mongo; // 0 + this._db = db; // 1 + this._collection = collection; // 2 + this._ns = ns; // 3 + + this._query = query || {}; // 4 + this._fields = fields; // 5 + this._limit = limit || 0; // 6 + this._skip = skip || 0; // 7 + + this._cursor = null; + this._numReturned = 0; + this._special = false; + } + print( "DBQuery probably won't have array access " ); +} + +DBQuery.prototype.help = function(){ + print( "DBQuery help" ); + print( "\t.sort( {...} )" ) + print( "\t.limit( n )" ) + print( "\t.skip( n )" ) + print( "\t.count() - total # of objects matching query, ignores skip,limit" ) + print( "\t.size() - total # of objects cursor would return skip,limit effect this" ) + print( "\t.explain()" ) + print( "\t.forEach( func )" ) + print( "\t.map( func )" ) + +} + +DBQuery.prototype.clone = function(){ + var q = new DBQuery( this._mongo , this._db , this._collection , this._ns , + this._query , this._fields , + this._limit , this._skip ); + q._special = this._special; + return q; +} + +DBQuery.prototype._ensureSpecial = function(){ + if ( this._special ) + return; + + var n = { query : this._query }; + this._query = n; + this._special = true; +} + +DBQuery.prototype._checkModify = function(){ + if ( this._cursor ) + throw "query already executed"; +} + +DBQuery.prototype._exec = function(){ + if ( ! this._cursor ){ + assert.eq( 0 , this._numReturned ); + this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip ); + this._cursorSeen = 0; + } + return this._cursor; +} + +DBQuery.prototype.limit = function( limit ){ + this._checkModify(); + this._limit = limit; + return this; +} + +DBQuery.prototype.skip = function( skip ){ + this._checkModify(); + this._skip = skip; + return this; +} + +DBQuery.prototype.hasNext = function(){ + this._exec(); + + if ( this._limit > 0 && this._cursorSeen >= this._limit ) + return false; + var o = this._cursor.hasNext(); + return o; +} + +DBQuery.prototype.next = function(){ + this._exec(); + + var o = this._cursor.hasNext(); + if ( o ) + this._cursorSeen++; + else + throw "error hasNext: " + o; + + var ret = this._cursor.next(); + if ( ret.$err && this._numReturned == 0 && ! this.hasNext() ) + throw "error: " + tojson( ret ); + + this._numReturned++; + return ret; +} + +DBQuery.prototype.toArray = function(){ + if ( this._arr ) + return this._arr; + + var a = []; + while ( this.hasNext() ) + a.push( this.next() ); + this._arr = a; + return a; +} + +DBQuery.prototype.count = function( applySkipLimit ){ + var cmd = { count: this._collection.getName() }; + if ( this._query ){ + if ( this._special ) + cmd.query = this._query.query; + else + cmd.query = this._query; + } + cmd.fields = this._fields || {}; + + if ( applySkipLimit ){ + if ( this._limit ) + cmd.limit = this._limit; + if ( this._skip ) + cmd.skip = this._skip; + } + + var res = this._db.runCommand( cmd ); + if( res && res.n != null ) return res.n; + throw "count failed: " + tojson( res ); +} + +DBQuery.prototype.size = function(){ + return this.count( true ); +} + +DBQuery.prototype.countReturn = function(){ + var c = this.count(); + + if ( this._skip ) + c = c - this._skip; + + if ( this._limit > 0 && this._limit < c ) + return this._limit; + + return c; +} + +/** +* iterative count - only for testing +*/ +DBQuery.prototype.itcount = function(){ + var num = 0; + while ( this.hasNext() ){ + num++; + this.next(); + } + return num; +} + +DBQuery.prototype.length = function(){ + return this.toArray().length; +} + +DBQuery.prototype.sort = function( sortBy ){ + this._ensureSpecial(); + this._query.orderby = sortBy; + return this; +} + +DBQuery.prototype.hint = function( hint ){ + this._ensureSpecial(); + this._query["$hint"] = hint; + return this; +} + +DBQuery.prototype.min = function( min ) { + this._ensureSpecial(); + this._query["$min"] = min; + return this; +} + +DBQuery.prototype.max = function( max ) { + this._ensureSpecial(); + this._query["$max"] = max; + return this; +} + +DBQuery.prototype.forEach = function( func ){ + while ( this.hasNext() ) + func( this.next() ); +} + +DBQuery.prototype.map = function( func ){ + var a = []; + while ( this.hasNext() ) + a.push( func( this.next() ) ); + return a; +} + +DBQuery.prototype.arrayAccess = function( idx ){ + return this.toArray()[idx]; +} + +DBQuery.prototype.explain = function(){ + var n = this.clone(); + n._ensureSpecial(); + n._query.$explain = true; + n._limit = n._limit * -1; + return n.next(); +} + +DBQuery.prototype.snapshot = function(){ + this._ensureSpecial(); + this._query.$snapshot = true; + return this; + } + +DBQuery.prototype.shellPrint = function(){ + try { + var n = 0; + while ( this.hasNext() && n < 20 ){ + var s = tojson( this.next() , "" , true ); + print( s ); + n++; + } + if ( this.hasNext() ){ + print( "has more" ); + ___it___ = this; + } + else { + ___it___ = null; + } + } + catch ( e ){ + print( e ); + } + +} + +DBQuery.prototype.toString = function(){ + return "DBQuery: " + this._ns + " -> " + tojson( this.query ); +} diff --git a/shell/servers.js b/shell/servers.js new file mode 100644 index 0000000..109f871 --- /dev/null +++ b/shell/servers.js @@ -0,0 +1,608 @@ + + +_parsePath = function() { + var dbpath = ""; + for( var i = 0; i < arguments.length; ++i ) + if ( arguments[ i ] == "--dbpath" ) + dbpath = arguments[ i + 1 ]; + + if ( dbpath == "" ) + throw "No dbpath specified"; + + return dbpath; +} + +_parsePort = function() { + var port = ""; + for( var i = 0; i < arguments.length; ++i ) + if ( arguments[ i ] == "--port" ) + port = arguments[ i + 1 ]; + + if ( port == "" ) + throw "No port specified"; + return port; +} + +createMongoArgs = function( binaryName , args ){ + var fullArgs = [ binaryName ]; + + if ( args.length == 1 && isObject( args[0] ) ){ + var o = args[0]; + for ( var k in o ){ + if ( k == "v" && isNumber( o[k] ) ){ + var n = o[k]; + if ( n > 0 ){ + var temp = "-"; + while ( n-- > 0 ) temp += "v"; + fullArgs.push( temp ); + } + } + else { + fullArgs.push( "--" + k ); + if ( o[k] != "" ) + fullArgs.push( "" + o[k] ); + } + } + } + else { + for ( var i=0; i<args.length; i++ ) + fullArgs.push( args[i] ) + } + + return fullArgs; +} + +// Start a mongod instance and return a 'Mongo' object connected to it. +// This function's arguments are passed as command line arguments to mongod. +// The specified 'dbpath' is cleared if it exists, created if not. +startMongod = function(){ + + var args = createMongoArgs( "mongod" , arguments ); + + var dbpath = _parsePath.apply( null, args ); + resetDbpath( dbpath ); + + return startMongoProgram.apply( null, args ); +} + +startMongos = function(){ + return startMongoProgram.apply( null, createMongoArgs( "mongos" , arguments ) ); +} + +// Start a mongo program instance (generally mongod or mongos) and return a +// 'Mongo' object connected to it. This function's first argument is the +// program name, and subsequent arguments to this function are passed as +// command line arguments to the program. +startMongoProgram = function(){ + var port = _parsePort.apply( null, arguments ); + + _startMongoProgram.apply( null, arguments ); + + var m; + assert.soon + ( function() { + try { + m = new Mongo( "127.0.0.1:" + port ); + return true; + } catch( e ) { + } + return false; + }, "unable to connect to mongo program on port " + port, 30000 ); + + return m; +} + +// Start a mongo program instance. This function's first argument is the +// program name, and subsequent arguments to this function are passed as +// command line arguments to the program. Returns pid of the spawned program. +startMongoProgramNoConnect = function() { + return _startMongoProgram.apply( null, arguments ); +} + +myPort = function() { + var m = db.getMongo(); + if ( m.host.match( /:/ ) ) + return m.host.match( /:(.*)/ )[ 1 ]; + else + return 27017; +} + +ShardingTest = function( testName , numServers , verboseLevel , numMongos ){ + this._connections = []; + this._serverNames = []; + + for ( var i=0; i<numServers; i++){ + var conn = startMongod( { port : 30000 + i , dbpath : "/data/db/" + testName + i , + noprealloc : "" , smallfiles : "" , oplogSize : "2" } ); + conn.name = "localhost:" + ( 30000 + i ); + + this._connections.push( conn ); + this._serverNames.push( conn.name ); + } + + this._configDB = "localhost:30000"; + + + this._mongos = []; + var startMongosPort = 39999; + for ( var i=0; i<(numMongos||1); i++ ){ + var myPort = startMongosPort - i; + var conn = startMongos( { port : startMongosPort - i , v : verboseLevel || 0 , configdb : this._configDB } ); + conn.name = "localhost:" + myPort; + this._mongos.push( conn ); + if ( i == 0 ) + this.s = conn; + } + + var admin = this.admin = this.s.getDB( "admin" ); + this.config = this.s.getDB( "config" ); + + this._serverNames.forEach( + function(z){ + admin.runCommand( { addshard : z , allowLocal : true } ); + } + ); +} + +ShardingTest.prototype.getDB = function( name ){ + return this.s.getDB( name ); +} + +ShardingTest.prototype.getServerName = function( dbname ){ + return this.config.databases.findOne( { name : dbname } ).primary; +} + +ShardingTest.prototype.getServer = function( dbname ){ + var name = this.getServerName( dbname ); + for ( var i=0; i<this._serverNames.length; i++ ){ + if ( name == this._serverNames[i] ) + return this._connections[i]; + } + throw "can't find server for: " + dbname + " name:" + name; + +} + +ShardingTest.prototype.getOther = function( one ){ + if ( this._connections.length != 2 ) + throw "getOther only works with 2 servers"; + + if ( this._connections[0] == one ) + return this._connections[1]; + return this._connections[0]; +} + +ShardingTest.prototype.stop = function(){ + for ( var i=0; i<this._mongos.length; i++ ){ + stopMongoProgram( 39999 - i ); + } + for ( var i=0; i<this._connections.length; i++){ + stopMongod( 30000 + i ); + } +} + +ShardingTest.prototype.adminCommand = function(cmd){ + var res = this.admin.runCommand( cmd ); + if ( res && res.ok == 1 ) + return true; + + throw "command " + tojson( cmd ) + " failed: " + tojson( res ); +} + +ShardingTest.prototype.getChunksString = function( ns ){ + var q = {} + if ( ns ) + q.ns = ns; + return Array.tojson( this.config.chunks.find( q ).toArray() , "\n" ); +} + +ShardingTest.prototype.printChunks = function( ns ){ + print( this.getChunksString( ns ) ); +} + +ShardingTest.prototype.printShardingStatus = function(){ + printShardingStatus( this.config ); +} + +ShardingTest.prototype.printCollectionInfo = function( ns , msg ){ + var out = ""; + if ( msg ) + out += msg + "\n"; + out += "sharding collection info: " + ns + "\n"; + for ( var i=0; i<this._connections.length; i++ ){ + var c = this._connections[i]; + out += " mongod " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n"; + } + for ( var i=0; i<this._mongos.length; i++ ){ + var c = this._mongos[i]; + out += " mongos " + c + " " + tojson( c.getCollection( ns ).getShardVersion() , " " , true ) + "\n"; + } + + print( out ); +} + +printShardingStatus = function( configDB ){ + + var version = configDB.getCollection( "version" ).findOne(); + if ( version == null ){ + print( "not a shard db!" ); + return; + } + + var raw = ""; + var output = function(s){ + raw += s + "\n"; + } + output( "--- Sharding Status --- " ); + output( " sharding version: " + tojson( configDB.getCollection( "version" ).findOne() ) ); + + output( " shards:" ); + configDB.shards.find().forEach( + function(z){ + output( " " + tojson(z) ); + } + ); + + output( " databases:" ); + configDB.databases.find().sort( { name : 1 } ).forEach( + function(z){ + output( "\t" + tojson(z,"",true) ); + + output( "\t\tmy chunks" ); + + configDB.chunks.find( { "ns" : new RegExp( "^" + z.name ) } ).sort( { ns : 1 } ).forEach( + function(z){ + output( "\t\t\t" + z.ns + " " + tojson( z.min ) + " -->> " + tojson( z.max ) + + " on : " + z.shard + " " + tojson( z.lastmod ) ); + } + ); + } + ); + + print( raw ); +} + +ShardingTest.prototype.sync = function(){ + this.adminCommand( "connpoolsync" ); +} + +ShardingTest.prototype.onNumShards = function( collName , dbName ){ + this.sync(); // we should sync since we're going directly to mongod here + dbName = dbName || "test"; + var num=0; + for ( var i=0; i<this._connections.length; i++ ) + if ( this._connections[i].getDB( dbName ).getCollection( collName ).count() > 0 ) + num++; + return num; +} + +ShardingTest.prototype.shardGo = function( collName , key , split , move , dbName ){ + split = split || key; + move = move || split; + dbName = dbName || "test"; + + var c = dbName + "." + collName; + + s.adminCommand( { shardcollection : c , key : key } ); + s.adminCommand( { split : c , middle : split } ); + s.adminCommand( { movechunk : c , find : move , to : this.getOther( s.getServer( dbName ) ).name } ); + +} + +MongodRunner = function( port, dbpath, peer, arbiter, extraArgs ) { + this.port_ = port; + this.dbpath_ = dbpath; + this.peer_ = peer; + this.arbiter_ = arbiter; + this.extraArgs_ = extraArgs; +} + +MongodRunner.prototype.start = function( reuseData ) { + var args = []; + if ( reuseData ) { + args.push( "mongod" ); + } + args.push( "--port" ); + args.push( this.port_ ); + args.push( "--dbpath" ); + args.push( this.dbpath_ ); + if ( this.peer_ && this.arbiter_ ) { + args.push( "--pairwith" ); + args.push( this.peer_ ); + args.push( "--arbiter" ); + args.push( this.arbiter_ ); + args.push( "--oplogSize" ); + // small oplog by default so startup fast + args.push( "1" ); + } + args.push( "--nohttpinterface" ); + args.push( "--noprealloc" ); + args.push( "--smallfiles" ); + args.push( "--bind_ip" ); + args.push( "127.0.0.1" ); + if ( this.extraArgs_ ) { + args = args.concat( this.extraArgs_ ); + } + if ( reuseData ) { + return startMongoProgram.apply( null, args ); + } else { + return startMongod.apply( null, args ); + } +} + +MongodRunner.prototype.port = function() { return this.port_; } + +MongodRunner.prototype.toString = function() { return [ this.port_, this.dbpath_, this.peer_, this.arbiter_ ].toString(); } + +ReplPair = function( left, right, arbiter ) { + this.left_ = left; + this.leftC_ = null; + this.right_ = right; + this.rightC_ = null; + this.arbiter_ = arbiter; + this.arbiterC_ = null; + this.master_ = null; + this.slave_ = null; +} + +ReplPair.prototype.start = function( reuseData ) { + if ( this.arbiterC_ == null ) { + this.arbiterC_ = this.arbiter_.start(); + } + if ( this.leftC_ == null ) { + this.leftC_ = this.left_.start( reuseData ); + } + if ( this.rightC_ == null ) { + this.rightC_ = this.right_.start( reuseData ); + } +} + +ReplPair.prototype.isMaster = function( mongo, debug ) { + var im = mongo.getDB( "admin" ).runCommand( { ismaster : 1 } ); + assert( im && im.ok, "command ismaster failed" ); + if ( debug ) { + printjson( im ); + } + return im.ismaster; +} + +ReplPair.prototype.isInitialSyncComplete = function( mongo, debug ) { + var isc = mongo.getDB( "admin" ).runCommand( { isinitialsynccomplete : 1 } ); + assert( isc && isc.ok, "command isinitialsynccomplete failed" ); + if ( debug ) { + printjson( isc ); + } + return isc.initialsynccomplete; +} + +ReplPair.prototype.checkSteadyState = function( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ) { + leftValues = leftValues || {}; + rightValues = rightValues || {}; + + var lm = null; + var lisc = null; + if ( this.leftC_ != null ) { + lm = this.isMaster( this.leftC_, debug ); + leftValues[ lm ] = true; + lisc = this.isInitialSyncComplete( this.leftC_, debug ); + } + var rm = null; + var risc = null; + if ( this.rightC_ != null ) { + rm = this.isMaster( this.rightC_, debug ); + rightValues[ rm ] = true; + risc = this.isInitialSyncComplete( this.rightC_, debug ); + } + + var stateSet = {} + state.forEach( function( i ) { stateSet[ i ] = true; } ); + if ( !( 1 in stateSet ) || ( ( risc || risc == null ) && ( lisc || lisc == null ) ) ) { + if ( rm == 1 && lm != 1 ) { + assert( twoMasterOk || !( 1 in leftValues ) ); + this.master_ = this.rightC_; + this.slave_ = this.leftC_; + } else if ( lm == 1 && rm != 1 ) { + assert( twoMasterOk || !( 1 in rightValues ) ); + this.master_ = this.leftC_; + this.slave_ = this.rightC_; + } + if ( !twoMasterOk ) { + assert( lm != 1 || rm != 1, "two masters" ); + } + // check for expected state + if ( state.sort().toString() == [ lm, rm ].sort().toString() ) { + if ( expectedMasterHost != null ) { + if( expectedMasterHost == this.master_.host ) { + return true; + } + } else { + return true; + } + } + } + + this.master_ = null; + this.slave_ = null; + return false; +} + +ReplPair.prototype.waitForSteadyState = function( state, expectedMasterHost, twoMasterOk, debug ) { + state = state || [ 1, 0 ]; + twoMasterOk = twoMasterOk || false; + var rp = this; + var leftValues = {}; + var rightValues = {}; + assert.soon( function() { return rp.checkSteadyState( state, expectedMasterHost, twoMasterOk, leftValues, rightValues, debug ); }, + "rp (" + rp + ") failed to reach expected steady state (" + state + ")" ); +} + +ReplPair.prototype.master = function() { return this.master_; } +ReplPair.prototype.slave = function() { return this.slave_; } +ReplPair.prototype.right = function() { return this.rightC_; } +ReplPair.prototype.left = function() { return this.leftC_; } +ReplPair.prototype.arbiter = function() { return this.arbiterC_; } + +ReplPair.prototype.killNode = function( mongo, signal ) { + signal = signal || 15; + if ( this.leftC_ != null && this.leftC_.host == mongo.host ) { + stopMongod( this.left_.port_ ); + this.leftC_ = null; + } + if ( this.rightC_ != null && this.rightC_.host == mongo.host ) { + stopMongod( this.right_.port_ ); + this.rightC_ = null; + } + if ( this.arbiterC_ != null && this.arbiterC_.host == mongo.host ) { + stopMongod( this.arbiter_.port_ ); + this.arbiterC_ = null; + } +} + +ReplPair.prototype._annotatedNode = function( mongo ) { + var ret = ""; + if ( mongo != null ) { + ret += " (connected)"; + if ( this.master_ != null && mongo.host == this.master_.host ) { + ret += "(master)"; + } + if ( this.slave_ != null && mongo.host == this.slave_.host ) { + ret += "(slave)"; + } + } + return ret; +} + +ReplPair.prototype.toString = function() { + var ret = ""; + ret += "left: " + this.left_; + ret += " " + this._annotatedNode( this.leftC_ ); + ret += " right: " + this.right_; + ret += " " + this._annotatedNode( this.rightC_ ); + return ret; +} + + +ToolTest = function( name ){ + this.name = name; + this.port = allocatePorts(1)[0]; + this.baseName = "jstests_tool_" + name; + this.root = "/data/db/" + this.baseName; + this.dbpath = this.root + "/"; + this.ext = this.root + "_external/"; + this.extFile = this.root + "_external/a"; + resetDbpath( this.dbpath ); +} + +ToolTest.prototype.startDB = function( coll ){ + assert( ! this.m , "db already running" ); + + this.m = startMongoProgram( "mongod" , "--port", this.port , "--dbpath" , this.dbpath , "--nohttpinterface", "--noprealloc" , "--smallfiles" , "--bind_ip", "127.0.0.1" ); + this.db = this.m.getDB( this.baseName ); + if ( coll ) + return this.db.getCollection( coll ); + return this.db; +} + +ToolTest.prototype.stop = function(){ + if ( ! this.m ) + return; + stopMongod( this.port ); + this.m = null; + this.db = null; +} + +ToolTest.prototype.runTool = function(){ + var a = [ "mongo" + arguments[0] ]; + + var hasdbpath = false; + + for ( var i=1; i<arguments.length; i++ ){ + a.push( arguments[i] ); + if ( arguments[i] == "--dbpath" ) + hasdbpath = true; + } + + if ( ! hasdbpath ){ + a.push( "--host" ); + a.push( "127.0.0.1:" + this.port ); + } + + runMongoProgram.apply( null , a ); +} + + +ReplTest = function( name, ports ){ + this.name = name; + this.ports = ports || allocatePorts( 2 ); +} + +ReplTest.prototype.getPort = function( master ){ + if ( master ) + return this.ports[ 0 ]; + return this.ports[ 1 ] +} + +ReplTest.prototype.getPath = function( master ){ + var p = "/data/db/" + this.name + "-"; + if ( master ) + p += "master"; + else + p += "slave" + return p; +} + + +ReplTest.prototype.getOptions = function( master , extra , putBinaryFirst ){ + + if ( ! extra ) + extra = {}; + + if ( ! extra.oplogSize ) + extra.oplogSize = "1"; + + var a = [] + if ( putBinaryFirst ) + a.push( "mongod" ) + a.push( "--nohttpinterface", "--noprealloc", "--bind_ip" , "127.0.0.1" , "--smallfiles" ); + + a.push( "--port" ); + a.push( this.getPort( master ) ); + + a.push( "--dbpath" ); + a.push( this.getPath( master ) ); + + + if ( master ){ + a.push( "--master" ); + } + else { + a.push( "--slave" ); + a.push( "--source" ); + a.push( "127.0.0.1:" + this.ports[0] ); + } + + for ( var k in extra ){ + var v = extra[k]; + a.push( "--" + k ); + if ( v != null ) + a.push( v ); + } + + return a; +} + +ReplTest.prototype.start = function( master , options , restart ){ + var o = this.getOptions( master , options , restart ); + if ( restart ) + return startMongoProgram.apply( null , o ); + else + return startMongod.apply( null , o ); +} + +ReplTest.prototype.stop = function( master , signal ){ + if ( arguments.length == 0 ){ + this.stop( true ); + this.stop( false ); + return; + } + stopMongod( this.getPort( master ) , signal || 15 ); +} diff --git a/shell/utils.cpp b/shell/utils.cpp new file mode 100644 index 0000000..c4735ee --- /dev/null +++ b/shell/utils.cpp @@ -0,0 +1,541 @@ +// utils.cpp + +#include <boost/thread/xtime.hpp> + +#include <cstring> +#include <cstdio> +#include <cstdlib> +#include <assert.h> +#include <iostream> +#include <map> +#include <sstream> +#include <vector> + +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +#include "../client/dbclient.h" +#include "../util/processinfo.h" +#include "../util/md5.hpp" +#include "utils.h" + +extern const char * jsconcatcode_server; + +namespace mongo { + + namespace shellUtils { + + std::string _dbConnect; + std::string _dbAuth; + + const char *argv0 = 0; + void RecordMyLocation( const char *_argv0 ) { argv0 = _argv0; } + + // helpers + + BSONObj makeUndefined() { + BSONObjBuilder b; + b.appendUndefined( "" ); + return b.obj(); + } + const BSONObj undefined_ = makeUndefined(); + + BSONObj encapsulate( const BSONObj &obj ) { + return BSON( "" << obj ); + } + + void sleepms( int ms ) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += ( ms / 1000 ); + xt.nsec += ( ms % 1000 ) * 1000000; + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } + + // real methods + + + + mongo::BSONObj JSSleep(const mongo::BSONObj &args){ + assert( args.nFields() == 1 ); + assert( args.firstElement().isNumber() ); + int ms = int( args.firstElement().number() ); + { + auto_ptr< ScriptEngine::Unlocker > u = globalScriptEngine->newThreadUnlocker(); + sleepms( ms ); + } + return undefined_; + } + + BSONObj listFiles(const BSONObj& args){ + uassert( 10257 , "need to specify 1 argument to listFiles" , args.nFields() == 1 ); + + BSONObjBuilder lst; + + string rootname = args.firstElement().valuestrsafe(); + path root( rootname ); + uassert( 12581, "listFiles: no such directory", boost::filesystem::exists( root ) ); + + directory_iterator end; + directory_iterator i( root); + + int num =0; + while ( i != end ){ + path p = *i; + BSONObjBuilder b; + b << "name" << p.string(); + b.appendBool( "isDirectory", is_directory( p ) ); + if ( ! is_directory( p ) ){ + try { + b.append( "size" , (double)file_size( p ) ); + } + catch ( ... ){ + i++; + continue; + } + } + + stringstream ss; + ss << num; + string name = ss.str(); + lst.append( name.c_str(), b.done() ); + num++; + i++; + } + + BSONObjBuilder ret; + ret.appendArray( "", lst.done() ); + return ret.obj(); + } + + BSONObj Quit(const BSONObj& args) { + // If not arguments are given first element will be EOO, which + // converts to the integer value 0. + int exit_code = int( args.firstElement().number() ); + ::exit(exit_code); + return undefined_; + } + + BSONObj JSGetMemInfo( const BSONObj& args ){ + ProcessInfo pi; + uassert( 10258 , "processinfo not supported" , pi.supported() ); + + BSONObjBuilder e; + e.append( "virtual" , pi.getVirtualMemorySize() ); + e.append( "resident" , pi.getResidentSize() ); + + BSONObjBuilder b; + b.append( "ret" , e.obj() ); + + return b.obj(); + } + + BSONObj JSVersion( const BSONObj& args ){ + cout << "version: " << versionString << endl; + if ( strstr( versionString , "+" ) ) + printGitVersion(); + return BSONObj(); + } + +#ifndef _WIN32 +#include <signal.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/wait.h> + + BSONObj AllocatePorts( const BSONObj &args ) { + uassert( 10259 , "allocatePorts takes exactly 1 argument", args.nFields() == 1 ); + uassert( 10260 , "allocatePorts needs to be passed an integer", args.firstElement().isNumber() ); + + int n = int( args.firstElement().number() ); + + vector< int > ports; + vector< int > sockets; + for( int i = 0; i < n; ++i ) { + int s = socket( AF_INET, SOCK_STREAM, 0 ); + assert( s ); + + sockaddr_in address; + memset(address.sin_zero, 0, sizeof(address.sin_zero)); + address.sin_family = AF_INET; + address.sin_port = 0; + address.sin_addr.s_addr = inet_addr( "127.0.0.1" ); + assert( 0 == ::bind( s, (sockaddr*)&address, sizeof( address ) ) ); + + sockaddr_in newAddress; + socklen_t len = sizeof( newAddress ); + assert( 0 == getsockname( s, (sockaddr*)&newAddress, &len ) ); + ports.push_back( ntohs( newAddress.sin_port ) ); + sockets.push_back( s ); + } + for( vector< int >::const_iterator i = sockets.begin(); i != sockets.end(); ++i ) + assert( 0 == close( *i ) ); + + sort( ports.begin(), ports.end() ); + for( unsigned i = 1; i < ports.size(); ++i ) + massert( 10434 , "duplicate ports allocated", ports[ i - 1 ] != ports[ i ] ); + BSONObjBuilder b; + b.append( "", ports ); + return b.obj(); + } + + map< int, pair< pid_t, int > > dbs; + map< pid_t, int > shells; + + char *copyString( const char *original ) { + char *ret = reinterpret_cast< char * >( malloc( strlen( original ) + 1 ) ); + strcpy( ret, original ); + return ret; + } + + boost::mutex &mongoProgramOutputMutex( *( new boost::mutex ) ); + stringstream mongoProgramOutput_; + + void writeMongoProgramOutputLine( int port, int pid, const char *line ) { + boost::mutex::scoped_lock lk( mongoProgramOutputMutex ); + stringstream buf; + if ( port > 0 ) + buf << "m" << port << "| " << line; + else + buf << "sh" << pid << "| " << line; + cout << buf.str() << endl; + mongoProgramOutput_ << buf.str() << endl; + } + + BSONObj RawMongoProgramOutput( const BSONObj &args ) { + boost::mutex::scoped_lock lk( mongoProgramOutputMutex ); + return BSON( "" << mongoProgramOutput_.str() ); + } + + class MongoProgramRunner { + char **argv_; + int port_; + int pipe_; + pid_t pid_; + public: + pid_t pid() const { return pid_; } + MongoProgramRunner( const BSONObj &args ) { + assert( args.nFields() > 0 ); + string program( args.firstElement().valuestrsafe() ); + + assert( !program.empty() ); + boost::filesystem::path programPath = ( boost::filesystem::path( argv0 ) ).branch_path() / program; + massert( 10435 , "couldn't find " + programPath.native_file_string(), boost::filesystem::exists( programPath ) ); + + port_ = -1; + argv_ = new char *[ args.nFields() + 1 ]; + { + string s = programPath.native_file_string(); + if ( s == program ) + s = "./" + s; + argv_[ 0 ] = copyString( s.c_str() ); + } + + BSONObjIterator j( args ); + j.next(); + for( int i = 1; i < args.nFields(); ++i ) { + BSONElement e = j.next(); + string str; + if ( e.isNumber() ) { + stringstream ss; + ss << e.number(); + str = ss.str(); + } else { + assert( e.type() == mongo::String ); + str = e.valuestr(); + } + char *s = copyString( str.c_str() ); + if ( string( "--port" ) == s ) + port_ = -2; + else if ( port_ == -2 ) + port_ = strtol( s, 0, 10 ); + argv_[ i ] = s; + } + argv_[ args.nFields() ] = 0; + + if ( program != "mongod" && program != "mongos" && program != "mongobridge" ) + port_ = 0; + else + assert( port_ > 0 ); + if ( port_ > 0 && dbs.count( port_ ) != 0 ){ + cerr << "count for port: " << port_ << " is not 0 is: " << dbs.count( port_ ) << endl; + assert( dbs.count( port_ ) == 0 ); + } + } + + void start() { + int pipeEnds[ 2 ]; + assert( pipe( pipeEnds ) != -1 ); + + fflush( 0 ); + pid_ = fork(); + assert( pid_ != -1 ); + + if ( pid_ == 0 ) { + + assert( dup2( pipeEnds[ 1 ], STDOUT_FILENO ) != -1 ); + assert( dup2( pipeEnds[ 1 ], STDERR_FILENO ) != -1 ); + execvp( argv_[ 0 ], argv_ ); + massert( 10436 , "Unable to start program" , 0 ); + } + + cout << "shell: started mongo program"; + int i = 0; + while( argv_[ i ] ) + cout << " " << argv_[ i++ ]; + cout << endl; + + i = 0; + while( argv_[ i ] ) + free( argv_[ i++ ] ); + free( argv_ ); + + if ( port_ > 0 ) + dbs.insert( make_pair( port_, make_pair( pid_, pipeEnds[ 1 ] ) ) ); + else + shells.insert( make_pair( pid_, pipeEnds[ 1 ] ) ); + pipe_ = pipeEnds[ 0 ]; + } + + // Continue reading output + void operator()() { + // This assumes there aren't any 0's in the mongo program output. + // Hope that's ok. + char buf[ 1024 ]; + char temp[ 1024 ]; + char *start = buf; + while( 1 ) { + int lenToRead = 1023 - ( start - buf ); + int ret = read( pipe_, (void *)start, lenToRead ); + assert( ret != -1 ); + start[ ret ] = '\0'; + if ( strlen( start ) != unsigned( ret ) ) + writeMongoProgramOutputLine( port_, pid_, "WARNING: mongod wrote null bytes to output" ); + char *last = buf; + for( char *i = strchr( buf, '\n' ); i; last = i + 1, i = strchr( last, '\n' ) ) { + *i = '\0'; + writeMongoProgramOutputLine( port_, pid_, last ); + } + if ( ret == 0 ) { + if ( *last ) + writeMongoProgramOutputLine( port_, pid_, last ); + close( pipe_ ); + break; + } + if ( last != buf ) { + strcpy( temp, last ); + strcpy( buf, temp ); + } else { + assert( strlen( buf ) < 1023 ); + } + start = buf + strlen( buf ); + } + } + }; + + BSONObj StartMongoProgram( const BSONObj &a ) { + MongoProgramRunner r( a ); + r.start(); + boost::thread t( r ); + return BSON( string( "" ) << int( r.pid() ) ); + } + + BSONObj RunMongoProgram( const BSONObj &a ) { + MongoProgramRunner r( a ); + r.start(); + boost::thread t( r ); + int temp; + waitpid( r.pid() , &temp , 0 ); + shells.erase( r.pid() ); + return BSON( string( "" ) << int( r.pid() ) ); + } + + BSONObj ResetDbpath( const BSONObj &a ) { + assert( a.nFields() == 1 ); + string path = a.firstElement().valuestrsafe(); + assert( !path.empty() ); + if ( boost::filesystem::exists( path ) ) + boost::filesystem::remove_all( path ); + boost::filesystem::create_directory( path ); + return undefined_; + } + + void killDb( int port, pid_t _pid, int signal ) { + pid_t pid; + if ( port > 0 ) { + if( dbs.count( port ) != 1 ) { + cout << "No db started on port: " << port << endl; + return; + } + pid = dbs[ port ].first; + } else { + pid = _pid; + } + + assert( 0 == kill( pid, signal ) ); + + int i = 0; + for( ; i < 65; ++i ) { + if ( i == 5 ) { + char now[64]; + time_t_to_String(time(0), now); + now[ 20 ] = 0; + cout << now << " process on port " << port << ", with pid " << pid << " not terminated, sending sigkill" << endl; + assert( 0 == kill( pid, SIGKILL ) ); + } + int temp; + int ret = waitpid( pid, &temp, WNOHANG ); + if ( ret == pid ) + break; + sleepms( 1000 ); + } + if ( i == 65 ) { + char now[64]; + time_t_to_String(time(0), now); + now[ 20 ] = 0; + cout << now << " failed to terminate process on port " << port << ", with pid " << pid << endl; + assert( "Failed to terminate process" == 0 ); + } + + if ( port > 0 ) { + close( dbs[ port ].second ); + dbs.erase( port ); + } else { + close( shells[ pid ] ); + shells.erase( pid ); + } + if ( i > 4 || signal == SIGKILL ) { + sleepms( 4000 ); // allow operating system to reclaim resources + } + } + + int getSignal( const BSONObj &a ) { + int ret = SIGTERM; + if ( a.nFields() == 2 ) { + BSONObjIterator i( a ); + i.next(); + BSONElement e = i.next(); + assert( e.isNumber() ); + ret = int( e.number() ); + } + return ret; + } + + BSONObj StopMongoProgram( const BSONObj &a ) { + assert( a.nFields() == 1 || a.nFields() == 2 ); + assert( a.firstElement().isNumber() ); + int port = int( a.firstElement().number() ); + killDb( port, 0, getSignal( a ) ); + cout << "shell: stopped mongo program on port " << port << endl; + return undefined_; + } + + BSONObj StopMongoProgramByPid( const BSONObj &a ) { + assert( a.nFields() == 1 || a.nFields() == 2 ); + assert( a.firstElement().isNumber() ); + int pid = int( a.firstElement().number() ); + killDb( 0, pid, getSignal( a ) ); + cout << "shell: stopped mongo program on pid " << pid << endl; + return undefined_; + } + + void KillMongoProgramInstances() { + vector< int > ports; + for( map< int, pair< pid_t, int > >::iterator i = dbs.begin(); i != dbs.end(); ++i ) + ports.push_back( i->first ); + for( vector< int >::iterator i = ports.begin(); i != ports.end(); ++i ) + killDb( *i, 0, SIGTERM ); + vector< pid_t > pids; + for( map< pid_t, int >::iterator i = shells.begin(); i != shells.end(); ++i ) + pids.push_back( i->first ); + for( vector< pid_t >::iterator i = pids.begin(); i != pids.end(); ++i ) + killDb( 0, *i, SIGTERM ); + } + + MongoProgramScope::~MongoProgramScope() { + try { + KillMongoProgramInstances(); + } catch ( ... ) { + assert( false ); + } + } + +#else + MongoProgramScope::~MongoProgramScope() {} + void KillMongoProgramInstances() {} +#endif + + BSONObj jsmd5( const BSONObj &a ){ + uassert( 10261 , "js md5 needs a string" , a.firstElement().type() == String ); + const char * s = a.firstElement().valuestrsafe(); + + md5digest d; + md5_state_t st; + md5_init(&st); + md5_append( &st , (const md5_byte_t*)s , strlen( s ) ); + md5_finish(&st, d); + + return BSON( "" << digestToString( d ) ); + } + + unsigned _randomSeed; + + BSONObj JSSrand( const BSONObj &a ) { + uassert( 12518, "srand requires a single numeric argument", + a.nFields() == 1 && a.firstElement().isNumber() ); + _randomSeed = a.firstElement().numberLong(); // grab least significant digits + return undefined_; + } + + BSONObj JSRand( const BSONObj &a ) { + uassert( 12519, "rand accepts no arguments", a.nFields() == 0 ); + unsigned r; +#if !defined(_WIN32) + r = rand_r( &_randomSeed ); +#else + r = rand(); // seed not used in this case +#endif + return BSON( "" << double( r ) / ( double( RAND_MAX ) + 1 ) ); + } + + void installShellUtils( Scope& scope ){ + scope.injectNative( "listFiles" , listFiles ); + scope.injectNative( "sleep" , JSSleep ); + scope.injectNative( "quit", Quit ); + scope.injectNative( "getMemInfo" , JSGetMemInfo ); + scope.injectNative( "version" , JSVersion ); + scope.injectNative( "hex_md5" , jsmd5 ); + scope.injectNative( "_srand" , JSSrand ); + scope.injectNative( "_rand" , JSRand ); +#if !defined(_WIN32) + scope.injectNative( "allocatePorts", AllocatePorts ); + scope.injectNative( "_startMongoProgram", StartMongoProgram ); + scope.injectNative( "runMongoProgram", RunMongoProgram ); + scope.injectNative( "stopMongod", StopMongoProgram ); + scope.injectNative( "stopMongoProgram", StopMongoProgram ); + scope.injectNative( "stopMongoProgramByPid", StopMongoProgramByPid ); + scope.injectNative( "resetDbpath", ResetDbpath ); + scope.injectNative( "rawMongoProgramOutput", RawMongoProgramOutput ); +#endif + } + + void initScope( Scope &scope ) { + scope.externalSetup(); + mongo::shellUtils::installShellUtils( scope ); + scope.execSetup( jsconcatcode_server , "setupServerCode" ); + + if ( !_dbConnect.empty() ) { + uassert( 12513, "connect failed", scope.exec( _dbConnect , "(connect)" , false , true , false ) ); + if ( !_dbAuth.empty() ) { + uassert( 12514, "login failed", scope.exec( _dbAuth , "(auth)" , true , true , false ) ); + } + } + } + } +} diff --git a/shell/utils.h b/shell/utils.h new file mode 100644 index 0000000..7c98e2c --- /dev/null +++ b/shell/utils.h @@ -0,0 +1,27 @@ +// utils.h + +#pragma once + +#include "../scripting/engine.h" + +namespace mongo { + + namespace shellUtils { + + extern std::string _dbConnect; + extern std::string _dbAuth; + + void RecordMyLocation( const char *_argv0 ); + void installShellUtils( Scope& scope ); + + // Scoped management of mongo program instances. Simple implementation: + // destructor kills all mongod instances created by the shell. + struct MongoProgramScope { + MongoProgramScope() {} // Avoid 'unused variable' warning. + ~MongoProgramScope(); + }; + void KillMongoProgramInstances(); + + void initScope( Scope &scope ); + } +} diff --git a/shell/utils.js b/shell/utils.js new file mode 100644 index 0000000..2d59b80 --- /dev/null +++ b/shell/utils.js @@ -0,0 +1,907 @@ + +__quiet = false; + +chatty = function(s){ + if ( ! __quiet ) + print( s ); +} + +friendlyEqual = function( a , b ){ + if ( a == b ) + return true; + + if ( tojson( a ) == tojson( b ) ) + return true; + + return false; +} + + +doassert = function( msg ){ + print( "assert: " + msg ); + throw msg; +} + +assert = function( b , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( b ) + return; + + doassert( "assert failed : " + msg ); +} + +assert._debug = false; + +assert.eq = function( a , b , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( a == b ) + return; + + if ( ( a != null && b != null ) && friendlyEqual( a , b ) ) + return; + + doassert( "[" + tojson( a ) + "] != [" + tojson( b ) + "] are not equal : " + msg ); +} + +assert.neq = function( a , b , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + if ( a != b ) + return; + + doassert( "[" + a + "] != [" + b + "] are equal : " + msg ); +} + +assert.soon = function( f, msg, timeout, interval ) { + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + var start = new Date(); + timeout = timeout || 30000; + interval = interval || 200; + var last; + while( 1 ) { + + if ( typeof( f ) == "string" ){ + if ( eval( f ) ) + return; + } + else { + if ( f() ) + return; + } + + if ( ( new Date() ).getTime() - start.getTime() > timeout ) + doassert( "assert.soon failed: " + f + ", msg:" + msg ); + sleep( interval ); + } +} + +assert.throws = function( func , params , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + try { + func.apply( null , params ); + } + catch ( e ){ + return e; + } + + doassert( "did not throw exception: " + msg ); +} + +assert.commandWorked = function( res , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( res.ok == 1 ) + return; + + doassert( "command failed: " + tojson( res ) + " : " + msg ); +} + +assert.commandFailed = function( res , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( res.ok == 0 ) + return; + + doassert( "command worked when it should have failed: " + tojson( res ) + " : " + msg ); +} + +assert.isnull = function( what , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( what == null ) + return; + + doassert( "supposed to null (" + ( msg || "" ) + ") was: " + tojson( what ) ); +} + +assert.lt = function( a , b , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( a < b ) + return; + doassert( a + " is not less than " + b + " : " + msg ); +} + +assert.gt = function( a , b , msg ){ + if ( assert._debug && msg ) print( "in assert for: " + msg ); + + if ( a > b ) + return; + doassert( a + " is not greater than " + b + " : " + msg ); +} + +Object.extend = function( dst , src , deep ){ + for ( var k in src ){ + var v = src[k]; + if ( deep && typeof(v) == "object" ){ + v = Object.extend( typeof ( v.length ) == "number" ? [] : {} , v , true ); + } + dst[k] = v; + } + return dst; +} + +argumentsToArray = function( a ){ + var arr = []; + for ( var i=0; i<a.length; i++ ) + arr[i] = a[i]; + return arr; +} + +isString = function( x ){ + return typeof( x ) == "string"; +} + +isNumber = function(x){ + return typeof( x ) == "number"; +} + +isObject = function( x ){ + return typeof( x ) == "object"; +} + +String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g,""); +} +String.prototype.ltrim = function() { + return this.replace(/^\s+/,""); +} +String.prototype.rtrim = function() { + return this.replace(/\s+$/,""); +} + +Date.timeFunc = function( theFunc , numTimes ){ + + var start = new Date(); + + numTimes = numTimes || 1; + for ( var i=0; i<numTimes; i++ ){ + theFunc.apply( null , argumentsToArray( arguments ).slice( 2 ) ); + } + + return (new Date()).getTime() - start.getTime(); +} + +Date.prototype.tojson = function(){ + return "\"" + this.toString() + "\""; +} + +RegExp.prototype.tojson = RegExp.prototype.toString; + +Array.contains = function( a , x ){ + for ( var i=0; i<a.length; i++ ){ + if ( a[i] == x ) + return true; + } + return false; +} + +Array.unique = function( a ){ + var u = []; + for ( var i=0; i<a.length; i++){ + var o = a[i]; + if ( ! Array.contains( u , o ) ){ + u.push( o ); + } + } + return u; +} + +Array.shuffle = function( arr ){ + for ( var i=0; i<arr.length-1; i++ ){ + var pos = i+Random.randInt(arr.length-i); + var save = arr[i]; + arr[i] = arr[pos]; + arr[pos] = save; + } + return arr; +} + + +Array.tojson = function( a , indent ){ + if (!indent) + indent = ""; + + if (a.length == 0) { + return "[ ]"; + } + + var s = "[\n"; + indent += "\t"; + for ( var i=0; i<a.length; i++){ + s += indent + tojson( a[i], indent ); + if ( i < a.length - 1 ){ + s += ",\n"; + } + } + if ( a.length == 0 ) { + s += indent; + } + + indent = indent.substring(1); + s += "\n"+indent+"]"; + return s; +} + +Array.fetchRefs = function( arr , coll ){ + var n = []; + for ( var i=0; i<arr.length; i ++){ + var z = arr[i]; + if ( coll && coll != z.getCollection() ) + continue; + n.push( z.fetch() ); + } + + return n; +} + +Array.sum = function( arr ){ + if ( arr.length == 0 ) + return null; + var s = arr[0]; + for ( var i=1; i<arr.length; i++ ) + s += arr[i]; + return s; +} + +Array.avg = function( arr ){ + if ( arr.length == 0 ) + return null; + return Array.sum( arr ) / arr.length; +} + +Array.stdDev = function( arr ){ + var avg = Array.avg( arr ); + var sum = 0; + + for ( var i=0; i<arr.length; i++ ){ + sum += Math.pow( arr[i] - avg , 2 ); + } + + return Math.sqrt( sum / arr.length ); +} + +Object.keySet = function( o ) { + var ret = new Array(); + for( i in o ) { + if ( !( i in o.__proto__ && o[ i ] === o.__proto__[ i ] ) ) { + ret.push( i ); + } + } + return ret; +} + +if ( ! ObjectId.prototype ) + ObjectId.prototype = {} + +ObjectId.prototype.toString = function(){ + return this.str; +} + +ObjectId.prototype.tojson = function(){ + return "ObjectId(\"" + this.str + "\")"; +} + +ObjectId.prototype.isObjectId = true; + +if ( typeof( DBPointer ) != "undefined" ){ + DBPointer.prototype.fetch = function(){ + assert( this.ns , "need a ns" ); + assert( this.id , "need an id" ); + + return db[ this.ns ].findOne( { _id : this.id } ); + } + + DBPointer.prototype.tojson = function(indent){ + return tojson({"ns" : this.ns, "id" : this.id}, indent); + } + + DBPointer.prototype.getCollection = function(){ + return this.ns; + } + + DBPointer.prototype.toString = function(){ + return "DBPointer " + this.ns + ":" + this.id; + } +} +else { + print( "warning: no DBPointer" ); +} + +if ( typeof( DBRef ) != "undefined" ){ + DBRef.prototype.fetch = function(){ + assert( this.$ref , "need a ns" ); + assert( this.$id , "need an id" ); + + return db[ this.$ref ].findOne( { _id : this.$id } ); + } + + DBRef.prototype.tojson = function(indent){ + return tojson({"$ref" : this.$ref, "$id" : this.$id}, indent); + } + + DBRef.prototype.getCollection = function(){ + return this.$ref; + } + + DBRef.prototype.toString = function(){ + return this.tojson(); + } +} +else { + print( "warning: no DBRef" ); +} + +if ( typeof( BinData ) != "undefined" ){ + BinData.prototype.tojson = function(){ + return "BinData type: " + this.type + " len: " + this.len; + } +} +else { + print( "warning: no BinData" ); +} + +if ( typeof _threadInject != "undefined" ){ + print( "fork() available!" ); + + Thread = function(){ + this.init.apply( this, arguments ); + } + _threadInject( Thread.prototype ); + + ScopedThread = function() { + this.init.apply( this, arguments ); + } + ScopedThread.prototype = new Thread( function() {} ); + _scopedThreadInject( ScopedThread.prototype ); + + fork = function() { + var t = new Thread( function() {} ); + Thread.apply( t, arguments ); + return t; + } + + // Helper class to generate a list of events which may be executed by a ParallelTester + EventGenerator = function( me, collectionName, mean ) { + this.mean = mean; + this.events = new Array( me, collectionName ); + } + + EventGenerator.prototype._add = function( action ) { + this.events.push( [ Random.genExp( this.mean ), action ] ); + } + + EventGenerator.prototype.addInsert = function( obj ) { + this._add( "t.insert( " + tojson( obj ) + " )" ); + } + + EventGenerator.prototype.addRemove = function( obj ) { + this._add( "t.remove( " + tojson( obj ) + " )" ); + } + + EventGenerator.prototype.addUpdate = function( objOld, objNew ) { + this._add( "t.update( " + tojson( objOld ) + ", " + tojson( objNew ) + " )" ); + } + + EventGenerator.prototype.addCheckCount = function( count, query, shouldPrint, checkQuery ) { + query = query || {}; + shouldPrint = shouldPrint || false; + checkQuery = checkQuery || false; + var action = "assert.eq( " + count + ", t.count( " + tojson( query ) + " ) );" + if ( checkQuery ) { + action += " assert.eq( " + count + ", t.find( " + tojson( query ) + " ).toArray().length );" + } + if ( shouldPrint ) { + action += " print( me + ' ' + " + count + " );"; + } + this._add( action ); + } + + EventGenerator.prototype.getEvents = function() { + return this.events; + } + + EventGenerator.dispatch = function() { + var args = argumentsToArray( arguments ); + var me = args.shift(); + var collectionName = args.shift(); + var m = new Mongo( db.getMongo().host ); + var t = m.getDB( "test" )[ collectionName ]; + for( var i in args ) { + sleep( args[ i ][ 0 ] ); + eval( args[ i ][ 1 ] ); + } + } + + // Helper class for running tests in parallel. It assembles a set of tests + // and then calls assert.parallelests to run them. + ParallelTester = function() { + this.params = new Array(); + } + + ParallelTester.prototype.add = function( fun, args ) { + args = args || []; + args.unshift( fun ); + this.params.push( args ); + } + + ParallelTester.prototype.run = function( msg, newScopes ) { + newScopes = newScopes || false; + assert.parallelTests( this.params, msg, newScopes ); + } + + // creates lists of tests from jstests dir in a format suitable for use by + // ParallelTester.fileTester. The lists will be in random order. + // n: number of lists to split these tests into + ParallelTester.createJstestsLists = function( n ) { + var params = new Array(); + for( var i = 0; i < n; ++i ) { + params.push( [] ); + } + + var makeKeys = function( a ) { + var ret = {}; + for( var i in a ) { + ret[ a[ i ] ] = 1; + } + return ret; + } + + // some tests can't run in parallel with most others + var skipTests = makeKeys( [ "jstests/dbadmin.js", + "jstests/repair.js", + "jstests/cursor8.js", + "jstests/recstore.js", + "jstests/extent.js", + "jstests/indexb.js", + "jstests/profile1.js", + "jstests/mr3.js"] ); + + // some tests can't be run in parallel with each other + var serialTestsArr = [ "jstests/fsync.js", + "jstests/fsync2.js" ]; + var serialTests = makeKeys( serialTestsArr ); + + params[ 0 ] = serialTestsArr; + + var files = listFiles("jstests"); + files = Array.shuffle( files ); + + var i = 0; + files.forEach( + function(x) { + + if ( /_runner/.test(x.name) || + /_lodeRunner/.test(x.name) || + ( x.name in skipTests ) || + ( x.name in serialTests ) || + ! /\.js$/.test(x.name ) ){ + print(" >>>>>>>>>>>>>>> skipping " + x.name); + return; + } + + params[ i % n ].push( x.name ); + ++i; + } + ); + + // randomize ordering of the serialTests + params[ 0 ] = Array.shuffle( params[ 0 ] ); + + for( var i in params ) { + params[ i ].unshift( i ); + } + + return params; + } + + // runs a set of test files + // first argument is an identifier for this tester, remaining arguments are file names + ParallelTester.fileTester = function() { + var args = argumentsToArray( arguments ); + var suite = args.shift(); + args.forEach( + function( x ) { + print(" S" + suite + " Test : " + x + " ..."); + var time = Date.timeFunc( function() { load(x); }, 1); + print(" S" + suite + " Test : " + x + " " + time + "ms" ); + } + ); + } + + // params: array of arrays, each element of which consists of a function followed + // by zero or more arguments to that function. Each function and its arguments will + // be called in a separate thread. + // msg: failure message + // newScopes: if true, each thread starts in a fresh scope + assert.parallelTests = function( params, msg, newScopes ) { + newScopes = newScopes || false; + var wrapper = function( fun, argv ) { + eval ( + "var z = function() {" + + "var __parallelTests__fun = " + fun.toString() + ";" + + "var __parallelTests__argv = " + tojson( argv ) + ";" + + "var __parallelTests__passed = false;" + + "try {" + + "__parallelTests__fun.apply( 0, __parallelTests__argv );" + + "__parallelTests__passed = true;" + + "} catch ( e ) {" + + "print( e );" + + "}" + + "return __parallelTests__passed;" + + "}" + ); + return z; + } + var runners = new Array(); + for( var i in params ) { + var param = params[ i ]; + var test = param.shift(); + var t; + if ( newScopes ) + t = new ScopedThread( wrapper( test, param ) ); + else + t = new Thread( wrapper( test, param ) ); + runners.push( t ); + } + + runners.forEach( function( x ) { x.start(); } ); + var nFailed = 0; + // v8 doesn't like it if we exit before all threads are joined (SERVER-529) + runners.forEach( function( x ) { if( !x.returnData() ) { ++nFailed; } } ); + assert.eq( 0, nFailed, msg ); + } +} + +tojson = function( x, indent , nolint ){ + if ( x == null ) + return "null"; + + if ( x == undefined ) + return "undefined"; + + if (!indent) + indent = ""; + + switch ( typeof x ){ + + case "string": { + var s = "\""; + for ( var i=0; i<x.length; i++ ){ + if ( x[i] == '"' ){ + s += "\\\""; + } + else + s += x[i]; + } + return s + "\""; + } + + case "number": + case "boolean": + return "" + x; + + case "object":{ + var s = tojsonObject( x, indent , nolint ); + if ( ( nolint == null || nolint == true ) && s.length < 80 && ( indent == null || indent.length == 0 ) ){ + s = s.replace( /[\s\r\n ]+/gm , " " ); + } + return s; + } + + case "function": + return x.toString(); + + + default: + throw "tojson can't handle type " + ( typeof x ); + } + +} + +tojsonObject = function( x, indent , nolint ){ + var lineEnding = nolint ? " " : "\n"; + var tabSpace = nolint ? "" : "\t"; + + assert.eq( ( typeof x ) , "object" , "tojsonObject needs object, not [" + ( typeof x ) + "]" ); + + if (!indent) + indent = ""; + + if ( typeof( x.tojson ) == "function" && x.tojson != tojson ) { + return x.tojson(indent,nolint); + } + + if ( typeof( x.constructor.tojson ) == "function" && x.constructor.tojson != tojson ) { + return x.constructor.tojson( x, indent , nolint ); + } + + if ( x.toString() == "[object MaxKey]" ) + return "{ $maxKey : 1 }"; + if ( x.toString() == "[object MinKey]" ) + return "{ $minKey : 1 }"; + + var s = "{" + lineEnding; + + // push one level of indent + indent += tabSpace; + + var total = 0; + for ( var k in x ) total++; + if ( total == 0 ) { + s += indent + lineEnding; + } + + var keys = x; + if ( typeof( x._simpleKeys ) == "function" ) + keys = x._simpleKeys(); + var num = 1; + for ( var k in keys ){ + + var val = x[k]; + if ( val == DB.prototype || val == DBCollection.prototype ) + continue; + + s += indent + "\"" + k + "\" : " + tojson( val, indent , nolint ); + if (num != total) { + s += ","; + num++; + } + s += lineEnding; + } + + // pop one level of indent + indent = indent.substring(1); + return s + indent + "}"; +} + +shellPrint = function( x ){ + it = x; + if ( x != undefined ) + shellPrintHelper( x ); + + if ( db ){ + var e = db.getPrevError(); + if ( e.err ) { + if( e.nPrev <= 1 ) + print( "error on last call: " + tojson( e.err ) ); + else + print( "an error " + tojson(e.err) + " occurred " + e.nPrev + " operations back in the command invocation" ); + } + db.resetError(); + } +} + +printjson = function(x){ + print( tojson( x ) ); +} + +shellPrintHelper = function( x ){ + + if ( typeof( x ) == "undefined" ){ + + if ( typeof( db ) != "undefined" && db.getLastError ){ + var e = db.getLastError(); + if ( e != null ) + print( e ); + } + + return; + } + + if ( x == null ){ + print( "null" ); + return; + } + + if ( typeof x != "object" ) + return print( x ); + + var p = x.shellPrint; + if ( typeof p == "function" ) + return x.shellPrint(); + + var p = x.tojson; + if ( typeof p == "function" ) + print( x.tojson() ); + else + print( tojson( x ) ); +} + +shellHelper = function( command , rest , shouldPrint ){ + command = command.trim(); + var args = rest.trim().replace(/;$/,"").split( "\s+" ); + + if ( ! shellHelper[command] ) + throw "no command [" + command + "]"; + + var res = shellHelper[command].apply( null , args ); + if ( shouldPrint ){ + shellPrintHelper( res ); + } + return res; +} + +help = shellHelper.help = function(){ + print( "HELP" ); + print( "\t" + "show dbs show database names"); + print( "\t" + "show collections show collections in current database"); + print( "\t" + "show users show users in current database"); + print( "\t" + "show profile show most recent system.profile entries with time >= 1ms"); + print( "\t" + "use <db name> set curent database to <db name>" ); + print( "\t" + "db.help() help on DB methods"); + print( "\t" + "db.foo.help() help on collection methods"); + print( "\t" + "db.foo.find() list objects in collection foo" ); + print( "\t" + "db.foo.find( { a : 1 } ) list objects in foo where a == 1" ); + print( "\t" + "it result of the last line evaluated; use to further iterate"); +} + +shellHelper.use = function( dbname ){ + db = db.getMongo().getDB( dbname ); + print( "switched to db " + db.getName() ); +} + +shellHelper.it = function(){ + if ( typeof( ___it___ ) == "undefined" || ___it___ == null ){ + print( "no cursor" ); + return; + } + shellPrintHelper( ___it___ ); +} + +shellHelper.show = function( what ){ + assert( typeof what == "string" ); + + if( what == "profile" ) { + if( db.system.profile.count() == 0 ) { + print("db.system.profile is empty"); + print("Use db.setProfilingLevel(2) will enable profiling"); + print("Use db.system.profile.find() to show raw profile entries"); + } + else { + print(); + db.system.profile.find({ millis : { $gt : 0 } }).sort({$natural:-1}).limit(5).forEach( function(x){print(""+x.millis+"ms " + String(x.ts).substring(0,24)); print(x.info); print("\n");} ) + } + return ""; + } + + if ( what == "users" ){ + db.system.users.find().forEach( printjson ); + return ""; + } + + if ( what == "collections" || what == "tables" ) { + db.getCollectionNames().forEach( function(x){print(x)} ); + return ""; + } + + if ( what == "dbs" ) { + db.getMongo().getDBNames().sort().forEach( function(x){print(x)} ); + return ""; + } + + throw "don't know how to show [" + what + "]"; + +} + +if ( typeof( Map ) == "undefined" ){ + Map = function(){ + this._data = {}; + } +} + +Map.hash = function( val ){ + if ( ! val ) + return val; + + switch ( typeof( val ) ){ + case 'string': + case 'number': + case 'date': + return val.toString(); + case 'object': + case 'array': + var s = ""; + for ( var k in val ){ + s += k + val[k]; + } + return s; + } + + throw "can't hash : " + typeof( val ); +} + +Map.prototype.put = function( key , value ){ + var o = this._get( key ); + var old = o.value; + o.value = value; + return old; +} + +Map.prototype.get = function( key ){ + return this._get( key ).value; +} + +Map.prototype._get = function( key ){ + var h = Map.hash( key ); + var a = this._data[h]; + if ( ! a ){ + a = []; + this._data[h] = a; + } + + for ( var i=0; i<a.length; i++ ){ + if ( friendlyEqual( key , a[i].key ) ){ + return a[i]; + } + } + var o = { key : key , value : null }; + a.push( o ); + return o; +} + +Map.prototype.values = function(){ + var all = []; + for ( var k in this._data ){ + this._data[k].forEach( function(z){ all.push( z.value ); } ); + } + return all; +} + +if ( typeof( gc ) == "undefined" ){ + gc = function(){ + } +} + + +Math.sigFig = function( x , N ){ + if ( ! N ){ + N = 3; + } + var p = Math.pow( 10, N - Math.ceil( Math.log( Math.abs(x) ) / Math.log( 10 )) ); + return Math.round(x*p)/p; +} + +Random = function() {} + +// set random seed +Random.srand = function( s ) { _srand( s ); } + +// random number 0 <= r < 1 +Random.rand = function() { return _rand(); } + +// random integer 0 <= r < n +Random.randInt = function( n ) { return Math.floor( Random.rand() * n ); } + +Random.setRandomSeed = function( s ) { + s = s || new Date().getTime(); + print( "setting random seed: " + s ); + Random.srand( s ); +} + +// generate a random value from the exponential distribution with the specified mean +Random.genExp = function( mean ) { + return -Math.log( Random.rand() ) * mean; +} diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..4d21953 --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1,37 @@ +// stdafx.cpp : source file that includes just the standard includes + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" + +#if defined( __MSVC__ ) +// should probably check VS version here +#elif defined( __GNUC__ ) + +#if __GNUC__ < 4 +#error gcc < 4 not supported +#endif + +#else +// unknown compiler +#endif + + +namespace mongo { + + const char versionString[] = "1.3.1"; + +} // namespace mongo diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..5352c5e --- /dev/null +++ b/stdafx.h @@ -0,0 +1,156 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string> + +namespace mongo { + + using namespace std; + +#define NOMINMAX + +#if defined(_WIN32) + const bool debug=true; +#else + const bool debug=false; +#endif + + // pdfile versions + const int VERSION = 4; + const int VERSION_MINOR = 5; + + // mongo version + extern const char versionString[]; + + enum ExitCode { + EXIT_CLEAN = 0 , + EXIT_BADOPTIONS = 2 , + EXIT_REPLICATION_ERROR = 3 , + EXIT_NEED_UPGRADE = 4 , + EXIT_KILL = 12 , + EXIT_ABRUBT = 14 , + EXIT_NTSERVICE_ERROR = 20 , + EXIT_JAVA = 21 , + EXIT_OOM_MALLOC = 42 , + EXIT_OOM_REALLOC = 43 , + EXIT_FS = 45 , + EXIT_POSSIBLE_CORRUPTION = 60 , // this means we detected a possible corruption situation, like a buf overflow + EXIT_UNCAUGHT = 100 , // top level exception that wasn't caught + EXIT_TEST = 101 , + + }; + + void dbexit( ExitCode returnCode, const char *whyMsg = ""); + + /** + this is here so you can't just type exit() to quit the program + you should either use dbexit to shutdown cleanly, or ::exit to tell the system to quiy + if you use this, you'll get a link error since mongo::exit isn't defined + */ + void exit( ExitCode returnCode ); + bool inShutdown(); + +} // namespace mongo + +#include <memory> +#include <string> +#include <iostream> +#include <fstream> +#include <map> +#include <vector> +#include <set> +#include <stdio.h> +#include <stdlib.h> +#include <sstream> +#include <signal.h> + +#include "targetver.h" +#include "time.h" +#include "string.h" +#include "limits.h" + +///using namespace std; + +#undef yassert +#include <boost/archive/iterators/base64_from_binary.hpp> +#include <boost/archive/iterators/binary_from_base64.hpp> +#include <boost/archive/iterators/transform_width.hpp> +#include <boost/filesystem/convenience.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/program_options.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr.hpp> +#define BOOST_SPIRIT_THREADSAFE + +#include <boost/version.hpp> + +#if BOOST_VERSION >= 103800 +#define BOOST_SPIRIT_USE_OLD_NAMESPACE +#include <boost/spirit/include/classic_core.hpp> +#include <boost/spirit/include/classic_loops.hpp> +#include <boost/spirit/include/classic_lists.hpp> +#else +#include <boost/spirit/core.hpp> +#include <boost/spirit/utility/loops.hpp> +#include <boost/spirit/utility/lists.hpp> +#endif + +#include <boost/tuple/tuple.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/condition.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread/xtime.hpp> +#undef assert +#define assert xassert +#define yassert 1 + +namespace mongo { + using namespace boost::filesystem; +} + +#include "util/debug_util.h" +#include "util/goodies.h" +#include "util/log.h" +#include "util/allocator.h" +#include "util/assert_util.h" + +namespace mongo { + + void sayDbContext(const char *msg = 0); + void rawOut( const string &s ); + +} // namespace mongo + +namespace mongo { + + const char * gitVersion(); + const char * sysInfo(); + string mongodVersion(); + + void printGitVersion(); + void printSysInfo(); + + typedef char _TCHAR; + +#define null (0) + +} // namespace mongo diff --git a/targetver.h b/targetver.h new file mode 100644 index 0000000..eb1b69b --- /dev/null +++ b/targetver.h @@ -0,0 +1,20 @@ +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once +#ifndef _WIN32_WINNT // Allow use of features specific to Windows Vista or later. +#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. +#endif diff --git a/tools/bridge.cpp b/tools/bridge.cpp new file mode 100644 index 0000000..42c3287 --- /dev/null +++ b/tools/bridge.cpp @@ -0,0 +1,130 @@ +// bridge.cpp + +/** + * Copyright (C) 2008 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" +#include "../util/message.h" +#include "../client/dbclient.h" + +using namespace mongo; +using namespace std; + +int port = 0; +string destUri; + +class Forwarder { +public: + Forwarder( MessagingPort &mp ) : mp_( mp ) { + } + void operator()() const { + DBClientConnection dest; + string errmsg; + while( !dest.connect( destUri, errmsg ) ) + sleepmillis( 500 ); + Message m; + while( 1 ) { + m.reset(); + if ( !mp_.recv( m ) ) { + cout << "end connection " << mp_.farEnd.toString() << endl; + mp_.shutdown(); + break; + } + + int oldId = m.data->id; + if ( m.data->operation() == dbQuery || m.data->operation() == dbMsg || m.data->operation() == dbGetMore ) { + Message response; + dest.port().call( m, response ); + mp_.reply( m, response, oldId ); + } else { + dest.port().say( m, oldId ); + } + } + } +private: + MessagingPort &mp_; +}; + +set<MessagingPort*> ports; + +class MyListener : public Listener { +public: + MyListener( int port ) : Listener( "", port ) {} + virtual void accepted(MessagingPort *mp) { + ports.insert( mp ); + Forwarder f( *mp ); + boost::thread t( f ); + } +}; + +auto_ptr< MyListener > listener; + +#if !defined(_WIN32) +void cleanup( int sig ) { + close( listener->socket() ); + for ( set<MessagingPort*>::iterator i = ports.begin(); i != ports.end(); i++ ) + (*i)->shutdown(); + ::exit( 0 ); +} + +void setupSignals() { + signal( SIGINT , cleanup ); + signal( SIGTERM , cleanup ); + signal( SIGPIPE , cleanup ); + signal( SIGABRT , cleanup ); + signal( SIGSEGV , cleanup ); + signal( SIGBUS , cleanup ); + signal( SIGFPE , cleanup ); +} +#else +inline void setupSignals() {} +#endif + +void helpExit() { + cout << "usage mongobridge --port <port> --dest <destUri>" << endl; + cout << " port: port to listen for mongo messages" << endl; + cout << " destUri: uri of remote mongod instance" << endl; + ::exit( -1 ); +} + +void check( bool b ) { + if ( !b ) + helpExit(); +} + +int main( int argc, char **argv ) { + setupSignals(); + + check( argc == 5 ); + + for( int i = 1; i < 5; ++i ) { + check( i % 2 != 0 ); + if ( strcmp( argv[ i ], "--port" ) == 0 ) { + port = strtol( argv[ ++i ], 0, 10 ); + } else if ( strcmp( argv[ i ], "--dest" ) == 0 ) { + destUri = argv[ ++i ]; + } else { + check( false ); + } + } + check( port != 0 && !destUri.empty() ); + + listener.reset( new MyListener( port ) ); + listener->init(); + listener->listen(); + + return 0; +} diff --git a/tools/dump.cpp b/tools/dump.cpp new file mode 100644 index 0000000..4cbd2e1 --- /dev/null +++ b/tools/dump.cpp @@ -0,0 +1,121 @@ +// dump.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../stdafx.h" +#include "../client/dbclient.h" +#include "tool.h" + +#include <fcntl.h> + +using namespace mongo; + +namespace po = boost::program_options; + +class Dump : public Tool { +public: + Dump() : Tool( "dump" , "*" ){ + add_options() + ("out,o", po::value<string>()->default_value("dump"), "output directory") + ; + } + + void doCollection( const string coll , path outputFile ) { + cout << "\t" << coll << " to " << outputFile.string() << endl; + + ofstream out; + out.open( outputFile.string().c_str() , ios_base::out | ios_base::binary ); + uassert( 10262 , "couldn't open file" , out.good() ); + + ProgressMeter m( conn( true ).count( coll.c_str() , BSONObj() , QueryOption_SlaveOk ) ); + + auto_ptr<DBClientCursor> cursor = conn( true ).query( coll.c_str() , Query().snapshot() , 0 , 0 , 0 , QueryOption_SlaveOk | QueryOption_NoCursorTimeout ); + + while ( cursor->more() ) { + BSONObj obj = cursor->next(); + out.write( obj.objdata() , obj.objsize() ); + m.hit(); + } + + cout << "\t\t " << m.done() << " objects" << endl; + + out.close(); + } + + void go( const string db , const path outdir ) { + cout << "DATABASE: " << db << "\t to \t" << outdir.string() << endl; + + create_directories( outdir ); + + string sns = db + ".system.namespaces"; + + auto_ptr<DBClientCursor> cursor = conn( true ).query( sns.c_str() , Query() , 0 , 0 , 0 , QueryOption_SlaveOk | QueryOption_NoCursorTimeout ); + while ( cursor->more() ) { + BSONObj obj = cursor->next(); + if ( obj.toString().find( ".$" ) != string::npos ) + continue; + + const string name = obj.getField( "name" ).valuestr(); + const string filename = name.substr( db.size() + 1 ); + + if ( _coll.length() > 0 && db + "." + _coll != name && _coll != name ) + continue; + + doCollection( name.c_str() , outdir / ( filename + ".bson" ) ); + + } + + } + + int run(){ + + path root( getParam("out") ); + string db = _db; + + if ( db == "*" ){ + cout << "all dbs" << endl; + auth( "admin" ); + + BSONObj res = conn( true ).findOne( "admin.$cmd" , BSON( "listDatabases" << 1 ) ); + BSONObj dbs = res.getField( "databases" ).embeddedObjectUserCheck(); + set<string> keys; + dbs.getFieldNames( keys ); + for ( set<string>::iterator i = keys.begin() ; i != keys.end() ; i++ ) { + string key = *i; + + BSONObj dbobj = dbs.getField( key ).embeddedObjectUserCheck(); + + const char * dbName = dbobj.getField( "name" ).valuestr(); + if ( (string)dbName == "local" ) + continue; + + go ( dbName , root / dbName ); + } + } + else { + auth( db ); + go( db , root / db ); + } + return 0; + } + +}; + +int main( int argc , char ** argv ) { + Dump d; + return d.main( argc , argv ); +} diff --git a/tools/export.cpp b/tools/export.cpp new file mode 100644 index 0000000..ad27f27 --- /dev/null +++ b/tools/export.cpp @@ -0,0 +1,132 @@ +// export.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "client/dbclient.h" +#include "db/json.h" + +#include "tool.h" + +#include <fstream> +#include <iostream> + +#include <boost/program_options.hpp> + +using namespace mongo; + +namespace po = boost::program_options; + +class Export : public Tool { +public: + Export() : Tool( "export" ){ + add_options() + ("query,q" , po::value<string>() , "query filter, as a JSON string" ) + ("fields,f" , po::value<string>() , "comma seperated list of field names e.g. -f name,age" ) + ("csv","export to csv instead of json") + ("out,o", po::value<string>(), "output file; if not specified, stdout is used") + ; + } + + int run(){ + string ns; + const bool csv = hasParam( "csv" ); + ostream *outPtr = &cout; + string outfile = getParam( "out" ); + auto_ptr<ofstream> fileStream; + if ( hasParam( "out" ) ){ + size_t idx = outfile.rfind( "/" ); + if ( idx != string::npos ){ + string dir = outfile.substr( 0 , idx + 1 ); + create_directories( dir ); + } + ofstream * s = new ofstream( outfile.c_str() , ios_base::out | ios_base::binary ); + fileStream.reset( s ); + outPtr = s; + if ( ! s->good() ){ + cerr << "couldn't open [" << outfile << "]" << endl; + return -1; + } + } + ostream &out = *outPtr; + + BSONObj * fieldsToReturn = 0; + BSONObj realFieldsToReturn; + + try { + ns = getNS(); + } catch (...) { + printHelp(cerr); + return 1; + } + + auth(); + + if ( hasParam( "fields" ) ){ + needFields(); + fieldsToReturn = &_fieldsObj; + } + + + if ( csv && _fields.size() == 0 ){ + cerr << "csv mode requires a field list" << endl; + return -1; + } + + + auto_ptr<DBClientCursor> cursor = conn().query( ns.c_str() , ((Query)(getParam( "query" , "" ))).snapshot() , 0 , 0 , fieldsToReturn , QueryOption_SlaveOk | QueryOption_NoCursorTimeout ); + + if ( csv ){ + for ( vector<string>::iterator i=_fields.begin(); i != _fields.end(); i++ ){ + if ( i != _fields.begin() ) + out << ","; + out << *i; + } + out << endl; + } + + long long num = 0; + while ( cursor->more() ) { + num++; + BSONObj obj = cursor->next(); + if ( csv ){ + for ( vector<string>::iterator i=_fields.begin(); i != _fields.end(); i++ ){ + if ( i != _fields.begin() ) + out << ","; + const BSONElement & e = obj.getFieldDotted(i->c_str()); + if ( ! e.eoo() ){ + out << e.jsonString( Strict , false ); + } + } + out << endl; + } + else { + out << obj.jsonString() << endl; + } + } + + + cout << "exported " << num << " records" << endl; + + return 0; + } +}; + +int main( int argc , char ** argv ) { + Export e; + return e.main( argc , argv ); +} diff --git a/tools/files.cpp b/tools/files.cpp new file mode 100644 index 0000000..e69a070 --- /dev/null +++ b/tools/files.cpp @@ -0,0 +1,161 @@ +// files.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "client/gridfs.h" +#include "client/dbclient.h" + +#include "tool.h" + +#include <fstream> +#include <iostream> + +#include <boost/program_options.hpp> + +using namespace mongo; + +namespace po = boost::program_options; + +class Files : public Tool { +public: + Files() : Tool( "files" ){ + add_options() + ( "local,l", po::value<string>(), "local filename for put|get (default is to use the same name as 'gridfs filename')") + ( "type,t", po::value<string>(), "MIME type for put (default is to omit)") + ( "replace,r", "Remove other files with same name after PUT") + ; + add_hidden_options() + ( "command" , po::value<string>() , "command (list|search|put|get)" ) + ( "file" , po::value<string>() , "filename for get|put" ) + ; + addPositionArg( "command" , 1 ); + addPositionArg( "file" , 2 ); + } + + virtual void printExtraHelp( ostream & out ){ + out << "usage: " << _name << " [options] command [gridfs filename]" << endl; + out << "command:" << endl; + out << " one of (list|search|put|get)" << endl; + out << " list - list all files. 'gridfs filename' is an optional prefix " << endl; + out << " which listed filenames must begin with." << endl; + out << " search - search all files. 'gridfs filename' is a substring " << endl; + out << " which listed filenames must contain." << endl; + out << " put - add a file with filename 'gridfs filename'" << endl; + out << " get - get a file with filename 'gridfs filename'" << endl; + out << " delete - delete all files with filename 'gridfs filename'" << endl; + } + + void display( GridFS * grid , BSONObj obj ){ + auto_ptr<DBClientCursor> c = grid->list( obj ); + while ( c->more() ){ + BSONObj obj = c->next(); + cout + << obj["filename"].str() << "\t" + << (long)obj["length"].number() + << endl; + } + } + + int run(){ + string cmd = getParam( "command" ); + if ( cmd.size() == 0 ){ + cerr << "ERROR: need command" << endl << endl; + printHelp(cout); + return -1; + } + + GridFS g( conn() , _db ); + auth(); + + string filename = getParam( "file" ); + + if ( cmd == "list" ){ + BSONObjBuilder b; + if ( filename.size() ) + b.appendRegex( "filename" , ( (string)"^" + filename ).c_str() ); + display( &g , b.obj() ); + return 0; + } + + if ( filename.size() == 0 ){ + cerr << "ERROR: need a filename" << endl << endl; + printHelp(cout); + return -1; + } + + if ( cmd == "search" ){ + BSONObjBuilder b; + b.appendRegex( "filename" , filename.c_str() ); + display( &g , b.obj() ); + return 0; + } + + if ( cmd == "get" ){ + GridFile f = g.findFile( filename ); + if ( ! f.exists() ){ + cerr << "ERROR: file not found" << endl; + return -2; + } + + string out = getParam("local", f.getFilename()); + f.write( out ); + + if (out != "-") + cout << "done write to: " << out << endl; + + return 0; + } + + if ( cmd == "put" ){ + const string& infile = getParam("local", filename); + const string& type = getParam("type", ""); + + BSONObj file = g.storeFile(infile, filename, type); + cout << "added file: " << file << endl; + + if (hasParam("replace")){ + auto_ptr<DBClientCursor> cursor = conn().query(_db+".fs.files", BSON("filename" << filename << "_id" << NE << file["_id"] )); + while (cursor->more()){ + BSONObj o = cursor->nextSafe(); + conn().remove(_db+".fs.files", BSON("_id" << o["_id"])); + conn().remove(_db+".fs.chunks", BSON("_id" << o["_id"])); + cout << "removed file: " << o << endl; + } + + } + + cout << "done!"; + return 0; + } + + if ( cmd == "delete" ){ + g.removeFile(filename); + cout << "done!"; + return 0; + } + + cerr << "ERROR: unknown command '" << cmd << "'" << endl << endl; + printHelp(cout); + return -1; + } +}; + +int main( int argc , char ** argv ) { + Files f; + return f.main( argc , argv ); +} diff --git a/tools/import.cpp b/tools/import.cpp new file mode 100644 index 0000000..47cbe32 --- /dev/null +++ b/tools/import.cpp @@ -0,0 +1,253 @@ +// import.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "client/dbclient.h" +#include "db/json.h" + +#include "tool.h" + +#include <fstream> +#include <iostream> + +#include <boost/program_options.hpp> + +using namespace mongo; + +namespace po = boost::program_options; + +class Import : public Tool { + + enum Type { JSON , CSV , TSV }; + Type _type; + + const char * _sep; + bool _ignoreBlanks; + bool _headerLine; + + void _append( BSONObjBuilder& b , const string& fieldName , const string& data ){ + if ( b.appendAsNumber( fieldName , data ) ) + return; + + if ( _ignoreBlanks && data.size() == 0 ) + return; + + // TODO: other types? + b.append( fieldName.c_str() , data ); + } + + BSONObj parseLine( char * line ){ + if ( _type == JSON ){ + char * end = ( line + strlen( line ) ) - 1; + while ( isspace(*end) ){ + *end = 0; + end--; + } + return fromjson( line ); + } + + BSONObjBuilder b; + + unsigned int pos=0; + while ( line[0] ){ + string name; + if ( pos < _fields.size() ){ + name = _fields[pos]; + } + else { + stringstream ss; + ss << "field" << pos; + name = ss.str(); + } + pos++; + + int skip = 1; + char * end; + if ( _type == CSV && line[0] == '"' ){ + line++; + end = strstr( line , "\"" ); + skip = 2; + } + else { + end = strstr( line , _sep ); + } + + bool done = false; + string data; + + if ( ! end ){ + done = true; + data = string( line ); + } + else { + data = string( line , end - line ); + } + + if ( _headerLine ){ + while ( isspace( data[0] ) ) + data = data.substr( 1 ); + _fields.push_back( data ); + } + else + _append( b , name , data ); + + if ( done ) + break; + line = end + skip; + } + return b.obj(); + } + +public: + Import() : Tool( "import" ){ + addFieldOptions(); + add_options() + ("ignoreBlanks","if given, empty fields in csv and tsv will be ignored") + ("type",po::value<string>() , "type of file to import. default: json (json,csv,tsv)") + ("file",po::value<string>() , "file to import from; if not specified stdin is used" ) + ("drop", "drop collection first " ) + ("headerline","CSV,TSV only - use first line as headers") + ; + addPositionArg( "file" , 1 ); + _type = JSON; + _ignoreBlanks = false; + _headerLine = false; + } + + int run(){ + string filename = getParam( "file" ); + long long fileSize = -1; + + istream * in = &cin; + + ifstream file( filename.c_str() , ios_base::in | ios_base::binary); + + if ( filename.size() > 0 && filename != "-" ){ + if ( ! exists( filename ) ){ + cerr << "file doesn't exist: " << filename << endl; + return -1; + } + in = &file; + fileSize = file_size( filename ); + } + + string ns; + + try { + ns = getNS(); + } catch (...) { + printHelp(cerr); + return -1; + } + + log(1) << "ns: " << ns << endl; + + auth(); + + if ( hasParam( "drop" ) ){ + cout << "dropping: " << ns << endl; + conn().dropCollection( ns.c_str() ); + } + + if ( hasParam( "ignoreBlanks" ) ){ + _ignoreBlanks = true; + } + + if ( hasParam( "type" ) ){ + string type = getParam( "type" ); + if ( type == "json" ) + _type = JSON; + else if ( type == "csv" ){ + _type = CSV; + _sep = ","; + } + else if ( type == "tsv" ){ + _type = TSV; + _sep = "\t"; + } + else { + cerr << "don't know what type [" << type << "] is" << endl; + return -1; + } + } + + if ( _type == CSV || _type == TSV ){ + _headerLine = hasParam( "headerline" ); + if ( ! _headerLine ) + needFields(); + } + + int errors = 0; + + int num = 0; + + time_t start = time(0); + + log(1) << "filesize: " << fileSize << endl; + ProgressMeter pm( fileSize ); + const int BUF_SIZE = 1024 * 1024 * 4; + boost::scoped_array<char> line(new char[BUF_SIZE]); + while ( *in ){ + char * buf = line.get(); + in->getline( buf , BUF_SIZE ); + uassert( 10263 , "unknown error reading file" , ( in->rdstate() & ios_base::badbit ) == 0 ); + log(1) << "got line:" << buf << endl; + + while( isspace( buf[0] ) ) buf++; + + int len = strlen( buf ); + if ( ! len ) + continue; + + if ( in->rdstate() == ios_base::eofbit ) + break; + assert( in->rdstate() == 0 ); + + try { + BSONObj o = parseLine( buf ); + if ( _headerLine ) + _headerLine = false; + else + conn().insert( ns.c_str() , o ); + } + catch ( std::exception& e ){ + cout << "exception:" << e.what() << endl; + cout << buf << endl; + errors++; + } + + num++; + if ( pm.hit( len + 1 ) ){ + cout << "\t\t\t" << num << "\t" << ( num / ( time(0) - start ) ) << "/second" << endl; + } + } + + cout << "imported " << num << " objects" << endl; + + if ( errors == 0 ) + return 0; + + cerr << "encountered " << errors << " error" << ( errors == 1 ? "" : "s" ) << endl; + return -1; + } +}; + +int main( int argc , char ** argv ) { + Import import; + return import.main( argc , argv ); +} diff --git a/tools/restore.cpp b/tools/restore.cpp new file mode 100644 index 0000000..19e3a26 --- /dev/null +++ b/tools/restore.cpp @@ -0,0 +1,177 @@ +// restore.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../stdafx.h" +#include "../client/dbclient.h" +#include "../util/mmap.h" +#include "tool.h" + +#include <boost/program_options.hpp> + +#include <fcntl.h> + +using namespace mongo; + +namespace po = boost::program_options; + +class Restore : public Tool { +public: + Restore() : Tool( "restore" , "" , "" ){ + add_hidden_options() + ("dir", po::value<string>()->default_value("dump"), "directory to restore from") + ; + addPositionArg("dir", 1); + } + + virtual void printExtraHelp(ostream& out) { + out << "usage: " << _name << " [options] [directory or filename to restore from]" << endl; + } + + int run(){ + auth(); + path root = getParam("dir"); + + /* If _db is not "" then the user specified a db name to restore as. + * + * In that case we better be given either a root directory that + * contains only .bson files or a single .bson file (a db). + * + * In the case where a collection name is specified we better be + * given either a root directory that contains only a single + * .bson file, or a single .bson file itself (a collection). + */ + drillDown(root, _db != "", _coll != ""); + return EXIT_CLEAN; + } + + void drillDown( path root, bool use_db = false, bool use_coll = false ) { + log(2) << "drillDown: " << root.string() << endl; + + if ( is_directory( root ) ) { + directory_iterator end; + directory_iterator i(root); + while ( i != end ) { + path p = *i; + i++; + + if (use_db) { + if (is_directory(p)) { + cerr << "ERROR: root directory must be a dump of a single database" << endl; + cerr << " when specifying a db name with --db" << endl; + printHelp(cout); + return; + } + } + + if (use_coll) { + if (is_directory(p) || i != end) { + cerr << "ERROR: root directory must be a dump of a single collection" << endl; + cerr << " when specifying a collection name with --collection" << endl; + printHelp(cout); + return; + } + } + + drillDown(p, use_db, use_coll); + } + return; + } + + if ( ! ( endsWith( root.string().c_str() , ".bson" ) || + endsWith( root.string().c_str() , ".bin" ) ) ) { + cerr << "don't know what to do with [" << root.string() << "]" << endl; + return; + } + + out() << root.string() << endl; + + string ns; + if (use_db) { + ns += _db; + } else { + string dir = root.branch_path().string(); + if ( dir.find( "/" ) == string::npos ) + ns += dir; + else + ns += dir.substr( dir.find_last_of( "/" ) + 1 ); + } + + if (use_coll) { + ns += "." + _coll; + } else { + string l = root.leaf(); + l = l.substr( 0 , l.find_last_of( "." ) ); + ns += "." + l; + } + + long long fileLength = file_size( root ); + + if ( fileLength == 0 ) { + out() << "file " + root.native_file_string() + " empty, skipping" << endl; + return; + } + + out() << "\t going into namespace [" << ns << "]" << endl; + + string fileString = root.string(); + ifstream file( fileString.c_str() , ios_base::in | ios_base::binary); + if ( ! file.is_open() ){ + log() << "error opening file: " << fileString << endl; + return; + } + + log(1) << "\t file size: " << fileLength << endl; + + long long read = 0; + long long num = 0; + + const int BUF_SIZE = 1024 * 1024 * 5; + char * buf = (char*)malloc( BUF_SIZE ); + + ProgressMeter m( fileLength ); + + while ( read < fileLength ) { + file.read( buf , 4 ); + int size = ((int*)buf)[0]; + if ( size >= BUF_SIZE ){ + cerr << "got an object of size: " << size << " terminating..." << endl; + } + uassert( 10264 , "invalid object size" , size < BUF_SIZE ); + + file.read( buf + 4 , size - 4 ); + + BSONObj o( buf ); + conn().insert( ns.c_str() , o ); + + read += o.objsize(); + num++; + + m.hit( o.objsize() ); + } + + free( buf ); + + uassert( 10265 , "counts don't match" , m.done() == fileLength ); + out() << "\t " << m.hits() << " objects" << endl; + } +}; + +int main( int argc , char ** argv ) { + Restore restore; + return restore.main( argc , argv ); +} diff --git a/tools/sniffer.cpp b/tools/sniffer.cpp new file mode 100644 index 0000000..9590d8f --- /dev/null +++ b/tools/sniffer.cpp @@ -0,0 +1,448 @@ +// sniffer.cpp + +/* + TODO: + large messages - need to track what's left and ingore + single object over packet size - can only display begging of object + + getmore + delete + killcursors + + */ + +#include <pcap.h> + +#ifdef _WIN32 +#undef min +#undef max +#endif + +#include "../util/builder.h" +#include "../util/message.h" +#include "../db/dbmessage.h" +#include "../client/dbclient.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +#include <iostream> +#include <map> +#include <string> + +#include <boost/shared_ptr.hpp> + +using namespace std; +using mongo::asserted; +using mongo::Message; +using mongo::MsgData; +using mongo::DbMessage; +using mongo::BSONObj; +using mongo::BufBuilder; +using mongo::DBClientConnection; +using mongo::QueryResult; + +#define SNAP_LEN 65535 + +int captureHeaderSize; +set<int> serverPorts; +string forwardAddress; + +/* IP header */ +struct sniff_ip { + u_char ip_vhl; /* version << 4 | header length >> 2 */ + u_char ip_tos; /* type of service */ + u_short ip_len; /* total length */ + u_short ip_id; /* identification */ + u_short ip_off; /* fragment offset field */ +#define IP_RF 0x8000 /* reserved fragment flag */ +#define IP_DF 0x4000 /* dont fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + u_char ip_ttl; /* time to live */ + u_char ip_p; /* protocol */ + u_short ip_sum; /* checksum */ + struct in_addr ip_src,ip_dst; /* source and dest address */ +}; +#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) +#define IP_V(ip) (((ip)->ip_vhl) >> 4) + +/* TCP header */ +typedef u_int32_t tcp_seq; + +struct sniff_tcp { + u_short th_sport; /* source port */ + u_short th_dport; /* destination port */ + tcp_seq th_seq; /* sequence number */ + tcp_seq th_ack; /* acknowledgement number */ + u_char th_offx2; /* data offset, rsvd */ +#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4) + u_char th_flags; +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + +#ifndef TH_FLAGS +#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) +#endif + + u_short th_win; /* window */ + u_short th_sum; /* checksum */ + u_short th_urp; /* urgent pointer */ +}; + +#pragma pack( 1 ) +struct Connection { + struct in_addr srcAddr; + u_short srcPort; + struct in_addr dstAddr; + u_short dstPort; + bool operator<( const Connection &other ) const { + return memcmp( this, &other, sizeof( Connection ) ) < 0; + } + Connection reverse() const { + Connection c; + c.srcAddr = dstAddr; + c.srcPort = dstPort; + c.dstAddr = srcAddr; + c.dstPort = srcPort; + return c; + } +}; +#pragma pack() + +map< Connection, bool > seen; +map< Connection, int > bytesRemainingInMessage; +map< Connection, boost::shared_ptr< BufBuilder > > messageBuilder; +map< Connection, unsigned > expectedSeq; +map< Connection, DBClientConnection* > forwarder; +map< Connection, long long > lastCursor; +map< Connection, map< long long, long long > > mapCursor; + +void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){ + + const struct sniff_ip* ip = (struct sniff_ip*)(packet + captureHeaderSize); + int size_ip = IP_HL(ip)*4; + if ( size_ip < 20 ){ + cerr << "*** Invalid IP header length: " << size_ip << " bytes" << endl; + return; + } + + assert( ip->ip_p == IPPROTO_TCP ); + + const struct sniff_tcp* tcp = (struct sniff_tcp*)(packet + captureHeaderSize + size_ip); + int size_tcp = TH_OFF(tcp)*4; + if (size_tcp < 20){ + cerr << "*** Invalid TCP header length: " << size_tcp << " bytes" << endl; + return; + } + + if ( ! ( serverPorts.count( ntohs( tcp->th_sport ) ) || + serverPorts.count( ntohs( tcp->th_dport ) ) ) ){ + return; + } + + const u_char * payload = (const u_char*)(packet + captureHeaderSize + size_ip + size_tcp); + + unsigned totalSize = ntohs(ip->ip_len); + assert( totalSize <= header->caplen ); + + int size_payload = totalSize - (size_ip + size_tcp); + if (size_payload <= 0 ) + return; + + Connection c; + c.srcAddr = ip->ip_src; + c.srcPort = tcp->th_sport; + c.dstAddr = ip->ip_dst; + c.dstPort = tcp->th_dport; + + if ( seen[ c ] ) { + if ( expectedSeq[ c ] != ntohl( tcp->th_seq ) ) { + cerr << "Warning: sequence # mismatch, there may be dropped packets" << endl; + } + } else { + seen[ c ] = true; + } + + expectedSeq[ c ] = ntohl( tcp->th_seq ) + size_payload; + + Message m; + + if ( bytesRemainingInMessage[ c ] == 0 ) { + m.setData( (MsgData*)payload , false ); + if ( !m.data->valid() ) { + cerr << "Invalid message start, skipping packet." << endl; + return; + } + if ( size_payload > m.data->len ) { + cerr << "Multiple messages in packet, skipping packet." << endl; + return; + } + if ( size_payload < m.data->len ) { + bytesRemainingInMessage[ c ] = m.data->len - size_payload; + messageBuilder[ c ].reset( new BufBuilder() ); + messageBuilder[ c ]->append( (void*)payload, size_payload ); + return; + } + } else { + bytesRemainingInMessage[ c ] -= size_payload; + messageBuilder[ c ]->append( (void*)payload, size_payload ); + if ( bytesRemainingInMessage[ c ] < 0 ) { + cerr << "Received too many bytes to complete message, resetting buffer" << endl; + bytesRemainingInMessage[ c ] = 0; + messageBuilder[ c ].reset(); + return; + } + if ( bytesRemainingInMessage[ c ] > 0 ) + return; + m.setData( (MsgData*)messageBuilder[ c ]->buf(), true ); + messageBuilder[ c ]->decouple(); + messageBuilder[ c ].reset(); + } + + DbMessage d( m ); + + cout << inet_ntoa(ip->ip_src) << ":" << ntohs( tcp->th_sport ) + << ( serverPorts.count( ntohs( tcp->th_dport ) ) ? " -->> " : " <<-- " ) + << inet_ntoa(ip->ip_dst) << ":" << ntohs( tcp->th_dport ) + << " " << d.getns() + << " " << m.data->len << " bytes " + << " id:" << hex << m.data->id << dec << "\t" << m.data->id; + + if ( m.data->operation() == mongo::opReply ) + cout << " - " << m.data->responseTo; + cout << endl; + + switch( m.data->operation() ){ + case mongo::opReply:{ + mongo::QueryResult* r = (mongo::QueryResult*)m.data; + cout << "\treply" << " n:" << r->nReturned << " cursorId: " << r->cursorId << endl; + if ( r->nReturned ){ + mongo::BSONObj o( r->data() , 0 ); + cout << "\t" << o << endl; + } + break; + } + case mongo::dbQuery:{ + mongo::QueryMessage q(d); + cout << "\tquery: " << q.query << " ntoreturn: " << q.ntoreturn << " ntoskip: " << q.ntoskip << endl; + break; + } + case mongo::dbUpdate:{ + int flags = d.pullInt(); + BSONObj q = d.nextJsObj(); + BSONObj o = d.nextJsObj(); + cout << "\tupdate flags:" << flags << " q:" << q << " o:" << o << endl; + break; + } + case mongo::dbInsert:{ + cout << "\tinsert: " << d.nextJsObj() << endl; + while ( d.moreJSObjs() ) + cout << "\t\t" << d.nextJsObj() << endl; + break; + } + case mongo::dbGetMore:{ + int nToReturn = d.pullInt(); + long long cursorId = d.pullInt64(); + cout << "\tgetMore nToReturn: " << nToReturn << " cursorId: " << cursorId << endl; + break; + } + case mongo::dbDelete:{ + int flags = d.pullInt(); + BSONObj q = d.nextJsObj(); + cout << "\tdelete flags: " << flags << " q: " << q << endl; + break; + } + case mongo::dbKillCursors:{ + int *x = (int *) m.data->_data; + x++; // reserved + int n = *x; + cout << "\tkillCursors n: " << n << endl; + break; + } + default: + cerr << "*** CANNOT HANDLE TYPE: " << m.data->operation() << endl; + } + + if ( !forwardAddress.empty() ) { + if ( m.data->operation() != mongo::opReply ) { + DBClientConnection *conn = forwarder[ c ]; + if ( !conn ) { + // These won't get freed on error, oh well hopefully we'll just + // abort in that case anyway. + conn = new DBClientConnection( true ); + conn->connect( forwardAddress ); + forwarder[ c ] = conn; + } + if ( m.data->operation() == mongo::dbQuery || m.data->operation() == mongo::dbGetMore ) { + if ( m.data->operation() == mongo::dbGetMore ) { + DbMessage d( m ); + d.pullInt(); + long long &cId = d.pullInt64(); + cId = mapCursor[ c ][ cId ]; + } + Message response; + conn->port().call( m, response ); + QueryResult *qr = (QueryResult *) response.data; + if ( !( qr->resultFlags() & QueryResult::ResultFlag_CursorNotFound ) ) { + if ( qr->cursorId != 0 ) { + lastCursor[ c ] = qr->cursorId; + return; + } + } + lastCursor[ c ] = 0; + } else { + conn->port().say( m ); + } + } else { + Connection r = c.reverse(); + long long myCursor = lastCursor[ r ]; + QueryResult *qr = (QueryResult *) m.data; + long long yourCursor = qr->cursorId; + if ( ( qr->resultFlags() & QueryResult::ResultFlag_CursorNotFound ) ) + yourCursor = 0; + if ( myCursor && !yourCursor ) + cerr << "Expected valid cursor in sniffed response, found none" << endl; + if ( !myCursor && yourCursor ) + cerr << "Sniffed valid cursor when none expected" << endl; + if ( myCursor && yourCursor ) { + mapCursor[ r ][ qr->cursorId ] = lastCursor[ r ]; + lastCursor[ r ] = 0; + } + } + } +} + +void usage() { + cout << + "Usage: mongosniff [--help] [--forward host:port] [--source (NET <interface> | FILE <filename>)] [<port0> <port1> ... ]\n" + "--forward Forward all parsed request messages to mongod instance at \n" + " specified host:port\n" + "--source Source of traffic to sniff, either a network interface or a\n" + " file containing perviously captured packets, in pcap format.\n" + " If no source is specified, mongosniff will attempt to sniff\n" + " from one of the machine's network interfaces.\n" + "<port0>... These parameters are used to filter sniffing. By default, \n" + " only port 27017 is sniffed.\n" + "--help Print this help message.\n" + << endl; +} + +int main(int argc, char **argv){ + + const char *dev = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t *handle; + + struct bpf_program fp; + bpf_u_int32 mask; + bpf_u_int32 net; + + bool source = false; + bool replay = false; + const char *file = 0; + + vector< const char * > args; + for( int i = 1; i < argc; ++i ) + args.push_back( argv[ i ] ); + + try { + for( unsigned i = 0; i < args.size(); ++i ) { + const char *arg = args[ i ]; + if ( arg == string( "--help" ) ) { + usage(); + return 0; + } else if ( arg == string( "--forward" ) ) { + forwardAddress = args[ ++i ]; + } else if ( arg == string( "--source" ) ) { + uassert( 10266 , "can't use --source twice" , source == false ); + uassert( 10267 , "source needs more args" , args.size() > i + 2); + source = true; + replay = ( args[ ++i ] == string( "FILE" ) ); + if ( replay ) + file = args[ ++i ]; + else + dev = args[ ++i ]; + } else { + serverPorts.insert( atoi( args[ i ] ) ); + } + } + } catch ( ... ) { + usage(); + return -1; + } + + if ( !serverPorts.size() ) + serverPorts.insert( 27017 ); + + if ( !replay ) { + if ( !dev ) { + dev = pcap_lookupdev(errbuf); + if ( ! dev ) { + cerr << "error finding device: " << errbuf << endl; + return -1; + } + cout << "found device: " << dev << endl; + } + if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1){ + cerr << "can't get netmask: " << errbuf << endl; + return -1; + } + handle = pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf); + if ( ! handle ){ + cerr << "error opening device: " << errbuf << endl; + return -1; + } + } else { + handle = pcap_open_offline(file, errbuf); + if ( ! handle ){ + cerr << "error opening capture file!" << endl; + return -1; + } + } + + switch ( pcap_datalink( handle ) ){ + case DLT_EN10MB: + captureHeaderSize = 14; + break; + case DLT_NULL: + captureHeaderSize = 4; + break; + default: + cerr << "don't know how to handle datalink type: " << pcap_datalink( handle ) << endl; + } + + assert( pcap_compile(handle, &fp, const_cast< char * >( "tcp" ) , 0, net) != -1 ); + assert( pcap_setfilter(handle, &fp) != -1 ); + + cout << "sniffing... "; + for ( set<int>::iterator i = serverPorts.begin(); i != serverPorts.end(); i++ ) + cout << *i << " "; + cout << endl; + + pcap_loop(handle, 0 , got_packet, NULL); + + pcap_freecode(&fp); + pcap_close(handle); + + for( map< Connection, DBClientConnection* >::iterator i = forwarder.begin(); i != forwarder.end(); ++i ) + free( i->second ); + + return 0; +} + diff --git a/tools/tool.cpp b/tools/tool.cpp new file mode 100644 index 0000000..8243a45 --- /dev/null +++ b/tools/tool.cpp @@ -0,0 +1,238 @@ +// Tool.cpp + +#include "tool.h" + +#include <iostream> + +#include <boost/filesystem/operations.hpp> +#include <pcrecpp.h> + +#include "util/file_allocator.h" + +using namespace std; +using namespace mongo; + +namespace po = boost::program_options; + +mongo::Tool::Tool( string name , string defaultDB , string defaultCollection ) : + _name( name ) , _db( defaultDB ) , _coll( defaultCollection ) , _conn(0), _paired(false) { + + _options = new po::options_description( "options" ); + _options->add_options() + ("help","produce help message") + ("host,h",po::value<string>(), "mongo host to connect to" ) + ("db,d",po::value<string>(), "database to use" ) + ("collection,c",po::value<string>(), "collection to use (some commands)" ) + ("username,u",po::value<string>(), "username" ) + ("password,p",po::value<string>(), "password" ) + ("dbpath",po::value<string>(), "directly access mongod data files in this path, instead of connecting to a mongod instance" ) + ("verbose,v", "be more verbose (include multiple times for more verbosity e.g. -vvvvv)") + ; + + _hidden_options = new po::options_description( name + " hidden options" ); + + /* support for -vv -vvvv etc. */ + for (string s = "vv"; s.length() <= 10; s.append("v")) { + _hidden_options->add_options()(s.c_str(), "verbose"); + } +} + +mongo::Tool::~Tool(){ + delete( _options ); + delete( _hidden_options ); + if ( _conn ) + delete _conn; +} + +void mongo::Tool::printExtraHelp( ostream & out ){ +} + +void mongo::Tool::printHelp(ostream &out) { + printExtraHelp(out); + _options->print(out); +} + +int mongo::Tool::main( int argc , char ** argv ){ + boost::filesystem::path::default_name_check( boost::filesystem::no_check ); + + _name = argv[0]; + + /* using the same style as db.cpp */ + int command_line_style = (((po::command_line_style::unix_style ^ + po::command_line_style::allow_guessing) | + po::command_line_style::allow_long_disguise) ^ + po::command_line_style::allow_sticky); + try { + po::options_description all_options("all options"); + all_options.add(*_options).add(*_hidden_options); + + po::store( po::command_line_parser( argc , argv ). + options(all_options). + positional( _positonalOptions ). + style(command_line_style).run() , _params ); + + po::notify( _params ); + } catch (po::error &e) { + cout << "ERROR: " << e.what() << endl << endl; + printHelp(cout); + return EXIT_BADOPTIONS; + } + + if ( _params.count( "help" ) ){ + printHelp(cerr); + return 0; + } + + if ( _params.count( "verbose" ) ) { + logLevel = 1; + } + + for (string s = "vv"; s.length() <= 10; s.append("v")) { + if (_params.count(s)) { + logLevel = s.length(); + } + } + + if ( ! hasParam( "dbpath" ) ) { + _host = "127.0.0.1"; + if ( _params.count( "host" ) ) + _host = _params["host"].as<string>(); + + if ( _host.find( "," ) == string::npos ){ + DBClientConnection * c = new DBClientConnection(); + _conn = c; + + string errmsg; + if ( ! c->connect( _host , errmsg ) ){ + cerr << "couldn't connect to [" << _host << "] " << errmsg << endl; + return -1; + } + } + else { + log(1) << "using pairing" << endl; + DBClientPaired * c = new DBClientPaired(); + _paired = true; + _conn = c; + + if ( ! c->connect( _host ) ){ + cerr << "couldn't connect to paired server: " << _host << endl; + return -1; + } + } + + cerr << "connected to: " << _host << endl; + } + else { + Client::initThread("tools"); + _conn = new DBDirectClient(); + _host = "DIRECT"; + static string myDbpath = getParam( "dbpath" ); + mongo::dbpath = myDbpath.c_str(); + mongo::acquirePathLock(); + theFileAllocator().start(); + } + + if ( _params.count( "db" ) ) + _db = _params["db"].as<string>(); + + if ( _params.count( "collection" ) ) + _coll = _params["collection"].as<string>(); + + if ( _params.count( "username" ) ) + _username = _params["username"].as<string>(); + + if ( _params.count( "password" ) ) + _password = _params["password"].as<string>(); + + int ret = -1; + try { + ret = run(); + } + catch ( DBException& e ){ + cerr << "assertion: " << e.toString() << endl; + ret = -1; + } + + if ( currentClient.get() ) + currentClient->shutdown(); + + return ret; +} + +mongo::DBClientBase& mongo::Tool::conn( bool slaveIfPaired ){ + if ( _paired && slaveIfPaired ) + return ((DBClientPaired*)_conn)->slaveConn(); + return *_conn; +} + +void mongo::Tool::addFieldOptions(){ + add_options() + ("fields,f" , po::value<string>() , "comma seperated list of field names e.g. -f name,age" ) + ("fieldFile" , po::value<string>() , "file with fields names - 1 per line" ) + ; +} + +void mongo::Tool::needFields(){ + + if ( hasParam( "fields" ) ){ + BSONObjBuilder b; + + string fields_arg = getParam("fields"); + pcrecpp::StringPiece input(fields_arg); + + string f; + pcrecpp::RE re("([\\w\\.]+),?" ); + while ( re.Consume( &input, &f ) ){ + _fields.push_back( f ); + b.append( f.c_str() , 1 ); + } + + _fieldsObj = b.obj(); + return; + } + + if ( hasParam( "fieldFile" ) ){ + string fn = getParam( "fieldFile" ); + if ( ! exists( fn ) ) + throw UserException( 9999 , ((string)"file: " + fn ) + " doesn't exist" ); + + const int BUF_SIZE = 1024; + char line[ 1024 + 128]; + ifstream file( fn.c_str() ); + + BSONObjBuilder b; + while ( file.rdstate() == ios_base::goodbit ){ + file.getline( line , BUF_SIZE ); + const char * cur = line; + while ( isspace( cur[0] ) ) cur++; + if ( strlen( cur ) == 0 ) + continue; + + _fields.push_back( cur ); + b.append( cur , 1 ); + } + _fieldsObj = b.obj(); + return; + } + + throw UserException( 9998 , "you need to specify fields" ); +} + +void mongo::Tool::auth( string dbname ){ + if ( ! dbname.size() ) + dbname = _db; + + if ( ! ( _username.size() || _password.size() ) ) + return; + + string errmsg; + if ( _conn->auth( dbname , _username , _password , errmsg ) ) + return; + + // try against the admin db + string err2; + if ( _conn->auth( "admin" , _username , _password , errmsg ) ) + return; + + throw mongo::UserException( 9997 , (string)"auth failed: " + errmsg ); +} diff --git a/tools/tool.h b/tools/tool.h new file mode 100644 index 0000000..18996ec --- /dev/null +++ b/tools/tool.h @@ -0,0 +1,93 @@ +// Tool.h + +#pragma once + +#include <string> + +#include <boost/program_options.hpp> + +#if defined(_WIN32) +#include <io.h> +#endif + +#include "client/dbclient.h" +#include "db/instance.h" + +using std::string; + +namespace mongo { + + class Tool { + public: + Tool( string name , string defaultDB="test" , string defaultCollection=""); + virtual ~Tool(); + + int main( int argc , char ** argv ); + + boost::program_options::options_description_easy_init add_options(){ + return _options->add_options(); + } + boost::program_options::options_description_easy_init add_hidden_options(){ + return _hidden_options->add_options(); + } + void addPositionArg( const char * name , int pos ){ + _positonalOptions.add( name , pos ); + } + + string getParam( string name , string def="" ){ + if ( _params.count( name ) ) + return _params[name.c_str()].as<string>(); + return def; + } + bool hasParam( string name ){ + return _params.count( name ); + } + + string getNS(){ + if ( _coll.size() == 0 ){ + cerr << "no collection specified!" << endl; + throw -1; + } + return _db + "." + _coll; + } + + virtual int run() = 0; + + virtual void printHelp(ostream &out); + + virtual void printExtraHelp( ostream & out ); + + protected: + + mongo::DBClientBase &conn( bool slaveIfPaired = false ); + void auth( string db = "" ); + + string _name; + + string _db; + string _coll; + + string _username; + string _password; + + void addFieldOptions(); + void needFields(); + + vector<string> _fields; + BSONObj _fieldsObj; + + + private: + string _host; + mongo::DBClientBase * _conn; + bool _paired; + + boost::program_options::options_description * _options; + boost::program_options::options_description * _hidden_options; + boost::program_options::positional_options_description _positonalOptions; + + boost::program_options::variables_map _params; + + }; + +} diff --git a/util/allocator.h b/util/allocator.h new file mode 100644 index 0000000..af8032c --- /dev/null +++ b/util/allocator.h @@ -0,0 +1,49 @@ +// allocator.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + + inline void * ourmalloc(size_t size) { + void *x = malloc(size); + if ( x == 0 ) dbexit( EXIT_OOM_MALLOC , "malloc fails"); + return x; + } + + inline void * ourrealloc(void *ptr, size_t size) { + void *x = realloc(ptr, size); + if ( x == 0 ) dbexit( EXIT_OOM_REALLOC , "realloc fails"); + return x; + } + +#define malloc mongo::ourmalloc +#define realloc mongo::ourrealloc + +#if defined(_WIN32) + inline void our_debug_free(void *p) { +#if 0 + // this is not safe if you malloc < 4 bytes so we don't use anymore + unsigned *u = (unsigned *) p; + u[0] = 0xEEEEEEEE; +#endif + free(p); + } +#define free our_debug_free +#endif + +} // namespace mongo diff --git a/util/assert_util.cpp b/util/assert_util.cpp new file mode 100644 index 0000000..d1d85b2 --- /dev/null +++ b/util/assert_util.cpp @@ -0,0 +1,170 @@ +// assert_util.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "assert_util.h" +#include "assert.h" +#include "file.h" + +namespace mongo { + + string getDbContext(); + + Assertion lastAssert[4]; + + /* "warning" assert -- safe to continue, so we don't throw exception. */ + void wasserted(const char *msg, const char *file, unsigned line) { + problem() << "Assertion failure " << msg << ' ' << file << ' ' << dec << line << endl; + sayDbContext(); + raiseError(0,msg && *msg ? msg : "wassertion failure"); + lastAssert[1].set(msg, getDbContext().c_str(), file, line); + } + + void asserted(const char *msg, const char *file, unsigned line) { + problem() << "Assertion failure " << msg << ' ' << file << ' ' << dec << line << endl; + sayDbContext(); + raiseError(0,msg && *msg ? msg : "assertion failure"); + lastAssert[0].set(msg, getDbContext().c_str(), file, line); + stringstream temp; + temp << "assertion " << file << ":" << line; + AssertionException e; + e.msg = temp.str(); + breakpoint(); + throw e; + } + + void uassert_nothrow(const char *msg) { + lastAssert[3].set(msg, getDbContext().c_str(), "", 0); + raiseError(0,msg); + } + + int uacount = 0; + void uasserted(int msgid, const char *msg) { + if ( ++uacount < 100 ) + log() << "User Exception " << msgid << ":" << msg << endl; + else + RARELY log() << "User Exception " << msg << endl; + lastAssert[3].set(msg, getDbContext().c_str(), "", 0); + raiseError(msgid,msg); + throw UserException(msgid, msg); + } + + void msgasserted(int msgid, const char *msg) { + log() << "Assertion: " << msgid << ":" << msg << endl; + lastAssert[2].set(msg, getDbContext().c_str(), "", 0); + raiseError(msgid,msg && *msg ? msg : "massert failure"); + breakpoint(); + printStackTrace(); // TEMP?? should we get rid of this? TODO + throw MsgAssertionException(msgid, msg); + } + + boost::mutex *Assertion::_mutex = new boost::mutex(); + + string Assertion::toString() { + if( _mutex == 0 ) + return ""; + + boostlock lk(*_mutex); + + if ( !isSet() ) + return ""; + + stringstream ss; + ss << msg << '\n'; + if ( *context ) + ss << context << '\n'; + if ( *file ) + ss << file << ' ' << line << '\n'; + return ss.str(); + } + + + class LoggingManager { + public: + LoggingManager() + : _enabled(0) , _file(0) { + } + + void start( const string& lp , bool append ){ + uassert( 10268 , "LoggingManager already started" , ! _enabled ); + _append = append; + + // test path + FILE * test = fopen( lp.c_str() , _append ? "a" : "w" ); + if ( ! test ){ + cout << "can't open [" << lp << "] for log file" << endl; + dbexit( EXIT_BADOPTIONS ); + assert( 0 ); + } + + _path = lp; + _enabled = 1; + rotate(); + } + + void rotate(){ + if ( ! _enabled ){ + cout << "LoggingManager not enabled" << endl; + return; + } + + if ( _file ){ +#ifdef _WIN32 + cout << "log rotation doesn't work on windows" << endl; + return; +#else + struct tm t; + localtime_r( &_opened , &t ); + + stringstream ss; + ss << _path << "." << ( 1900 + t.tm_year ) << "-" << t.tm_mon << "-" << t.tm_mday + << "_" << t.tm_hour << "-" << t.tm_min << "-" << t.tm_sec; + string s = ss.str(); + rename( _path.c_str() , s.c_str() ); +#endif + } + + _file = freopen( _path.c_str() , _append ? "a" : "w" , stdout ); + if ( ! _file ){ + cerr << "can't open: " << _path.c_str() << " for log file" << endl; + dbexit( EXIT_BADOPTIONS ); + assert(0); + } + _opened = time(0); + } + + private: + + bool _enabled; + string _path; + bool _append; + + FILE * _file; + time_t _opened; + + } loggingManager; + + void initLogging( const string& lp , bool append ){ + cout << "all output going to: " << lp << endl; + loggingManager.start( lp , append ); + } + + void rotateLogs( int signal ){ + loggingManager.rotate(); + } +} + diff --git a/util/assert_util.h b/util/assert_util.h new file mode 100644 index 0000000..ccb60a0 --- /dev/null +++ b/util/assert_util.h @@ -0,0 +1,186 @@ +// assert_util.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#pragma once + +#include "../db/lasterror.h" + +namespace mongo { + + /* these are manipulated outside of mutexes, so be careful */ + struct Assertion { + Assertion() { + msg[0] = msg[127] = 0; + context[0] = context[127] = 0; + file = ""; + line = 0; + when = 0; + } + private: + static boost::mutex *_mutex; + char msg[128]; + char context[128]; + const char *file; + unsigned line; + time_t when; + public: + void set(const char *m, const char *ctxt, const char *f, unsigned l) { + if( _mutex == 0 ) { + /* asserted during global variable initialization */ + return; + } + boostlock lk(*_mutex); + strncpy(msg, m, 127); + strncpy(context, ctxt, 127); + file = f; + line = l; + when = time(0); + } + std::string toString(); + bool isSet() { + return when != 0; + } + }; + + enum { + AssertRegular = 0, + AssertW = 1, + AssertMsg = 2, + AssertUser = 3 + }; + + /* last assert of diff types: regular, wassert, msgassert, uassert: */ + extern Assertion lastAssert[4]; + + class DBException : public std::exception { + public: + virtual const char* what() const throw() = 0; + virtual string toString() const { + return what(); + } + virtual int getCode() = 0; + operator string() const { return toString(); } + }; + + class AssertionException : public DBException { + public: + int code; + string msg; + AssertionException() { code = 0; } + virtual ~AssertionException() throw() { } + virtual bool severe() { + return true; + } + virtual bool isUserAssertion() { + return false; + } + virtual int getCode(){ return code; } + virtual const char* what() const throw() { return msg.c_str(); } + }; + + /* UserExceptions are valid errors that a user can cause, like out of disk space or duplicate key */ + class UserException : public AssertionException { + public: + UserException(int c , const string& m) { + code = c; + msg = m; + } + virtual bool severe() { + return false; + } + virtual bool isUserAssertion() { + return true; + } + virtual string toString() const { + return "userassert:" + msg; + } + }; + + class MsgAssertionException : public AssertionException { + public: + MsgAssertionException(int c, const char *m) { + code = c; + msg = m; + } + virtual bool severe() { + return false; + } + virtual string toString() const { + return "massert:" + msg; + } + }; + + void asserted(const char *msg, const char *file, unsigned line); + void wasserted(const char *msg, const char *file, unsigned line); + void uasserted(int msgid, const char *msg); + inline void uasserted(int msgid , string msg) { uasserted(msgid, msg.c_str()); } + void uassert_nothrow(const char *msg); // reported via lasterror, but don't throw exception + void msgasserted(int msgid, const char *msg); + inline void msgasserted(int msgid, string msg) { msgasserted(msgid, msg.c_str()); } + +#ifdef assert +#undef assert +#endif + +#define assert(_Expression) (void)( (!!(_Expression)) || (mongo::asserted(#_Expression, __FILE__, __LINE__), 0) ) + + /* "user assert". if asserts, user did something wrong, not our code */ +//#define uassert( 10269 , _Expression) (void)( (!!(_Expression)) || (uasserted(#_Expression, __FILE__, __LINE__), 0) ) +#define uassert(msgid, msg,_Expression) (void)( (!!(_Expression)) || (mongo::uasserted(msgid, msg), 0) ) + +#define xassert(_Expression) (void)( (!!(_Expression)) || (mongo::asserted(#_Expression, __FILE__, __LINE__), 0) ) + +#define yassert 1 + + /* warning only - keeps going */ +#define wassert(_Expression) (void)( (!!(_Expression)) || (mongo::wasserted(#_Expression, __FILE__, __LINE__), 0) ) + + /* display a message, no context, and throw assertionexception + + easy way to throw an exception and log something without our stack trace + display happening. + */ +#define massert(msgid, msg,_Expression) (void)( (!!(_Expression)) || (mongo::msgasserted(msgid, msg), 0) ) + + /* dassert is 'debug assert' -- might want to turn off for production as these + could be slow. + */ +#if defined(_DEBUG) +#define dassert assert +#else +#define dassert(x) +#endif + + // some special ids that we want to duplicate + + // > 10000 asserts + // < 10000 UserException + +#define ASSERT_ID_DUPKEY 11000 + +} // namespace mongo + +#define BOOST_CHECK_EXCEPTION( expression ) \ + try { \ + expression; \ + } catch ( const std::exception &e ) { \ + problem() << "caught boost exception: " << e.what() << endl; \ + assert( false ); \ + } catch ( ... ) { \ + massert( 10437 , "unknown boost failed" , false ); \ + } diff --git a/util/background.cpp b/util/background.cpp new file mode 100644 index 0000000..ac3a48c --- /dev/null +++ b/util/background.cpp @@ -0,0 +1,64 @@ +//background.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "goodies.h" +#include "background.h" + +namespace mongo { + + BackgroundJob *BackgroundJob::grab = 0; + boost::mutex &BackgroundJob::mutex = *( new boost::mutex ); + + /* static */ + void BackgroundJob::thr() { + assert( grab ); + BackgroundJob *us = grab; + assert( us->state == NotStarted ); + us->state = Running; + grab = 0; + us->run(); + us->state = Done; + if ( us->deleteSelf ) + delete us; + } + + BackgroundJob& BackgroundJob::go() { + boostlock bl(mutex); + assert( grab == 0 ); + grab = this; + boost::thread t(thr); + while ( grab ) + sleepmillis(2); + return *this; + } + + bool BackgroundJob::wait(int msMax) { + assert( state != NotStarted ); + int ms = 1; + Date_t start = jsTime(); + while ( state != Done ) { + sleepmillis(ms); + if ( ms < 1000 ) + ms = ms * 2; + if ( msMax && ( int( jsTime() - start ) > msMax) ) + return false; + } + return true; + } + +} // namespace mongo diff --git a/util/background.h b/util/background.h new file mode 100644 index 0000000..ff044cb --- /dev/null +++ b/util/background.h @@ -0,0 +1,73 @@ +// background.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + + /* object-orienty background thread dispatching. + + subclass and define run() + + It is ok to call go() more than once -- if the previous invocation + has finished. Thus one pattern of use is to embed a backgroundjob + in your object and reuse it (or same thing with inheritance). + */ + + class BackgroundJob { + protected: + /* define this to do your work! */ + virtual void run() = 0; + + public: + enum State { + NotStarted, + Running, + Done + }; + State getState() const { + return state; + } + bool running() const { + return state == Running; + } + + bool deleteSelf; // delete self when Done? + + BackgroundJob() { + deleteSelf = false; + state = NotStarted; + } + virtual ~BackgroundJob() { } + + // start job. returns before it's finished. + BackgroundJob& go(); + + // wait for completion. this spins with sleep() so not terribly efficient. + // returns true if did not time out. + // + // note you can call wait() more than once if the first call times out. + bool wait(int msMax = 0); + + private: + static BackgroundJob *grab; + static boost::mutex &mutex; + static void thr(); + volatile State state; + }; + +} // namespace mongo diff --git a/util/base64.cpp b/util/base64.cpp new file mode 100644 index 0000000..cf2f485 --- /dev/null +++ b/util/base64.cpp @@ -0,0 +1,144 @@ +// util/base64.cpp + + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" + +namespace mongo { + namespace base64 { + + class Alphabet { + public: + Alphabet(){ + encode = (unsigned char*) + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/"; + + decode = (unsigned char*)malloc(257); + memset( decode , 0 , 256 ); + for ( int i=0; i<64; i++ ){ + decode[ encode[i] ] = i; + } + + test(); + } + ~Alphabet(){ + free( decode ); + } + + void test(){ + assert( strlen( (char*)encode ) == 64 ); + for ( int i=0; i<26; i++ ) + assert( encode[i] == toupper( encode[i+26] ) ); + } + + char e( int x ){ + return encode[x&0x3f]; + } + + private: + const unsigned char * encode; + public: + unsigned char * decode; + } alphabet; + + + void encode( stringstream& ss , const char * data , int size ){ + for ( int i=0; i<size; i+=3 ){ + int left = size - i; + const unsigned char * start = (const unsigned char*)data + i; + + // byte 0 + ss << alphabet.e(start[0]>>2); + + // byte 1 + unsigned char temp = ( start[0] << 4 ); + if ( left == 1 ){ + ss << alphabet.e(temp); + break; + } + temp |= ( ( start[1] >> 4 ) & 0xF ); + ss << alphabet.e(temp); + + // byte 2 + temp = ( start[1] & 0xF ) << 2; + if ( left == 2 ){ + ss << alphabet.e(temp); + break; + } + temp |= ( ( start[2] >> 6 ) & 0x3 ); + ss << alphabet.e(temp); + + // byte 3 + ss << alphabet.e(start[2] & 0x3f); + } + + int mod = size % 3; + if ( mod == 1 ){ + ss << "=="; + } + else if ( mod == 2 ){ + ss << "="; + } + } + + + string encode( const char * data , int size ){ + stringstream ss; + encode( ss , data ,size ); + return ss.str(); + } + + string encode( const string& s ){ + return encode( s.c_str() , s.size() ); + } + + + void decode( stringstream& ss , const string& s ){ + uassert( 10270 , "invalid base64" , s.size() % 4 == 0 ); + const unsigned char * data = (const unsigned char*)s.c_str(); + int size = s.size(); + + unsigned char buf[3]; + for ( int i=0; i<size; i+=4){ + const unsigned char * start = data + i; + buf[0] = ( ( alphabet.decode[start[0]] << 2 ) & 0xFC ) | ( ( alphabet.decode[start[1]] >> 4 ) & 0x3 ); + buf[1] = ( ( alphabet.decode[start[1]] << 4 ) & 0xF0 ) | ( ( alphabet.decode[start[2]] >> 2 ) & 0xF ); + buf[2] = ( ( alphabet.decode[start[2]] << 6 ) & 0xC0 ) | ( ( alphabet.decode[start[3]] & 0x3F ) ); + + int len = 3; + if ( start[3] == '=' ){ + len = 2; + if ( start[2] == '=' ){ + len = 1; + } + } + ss.write( (const char*)buf , len ); + } + } + + string decode( const string& s ){ + stringstream ss; + decode( ss , s ); + return ss.str(); + } + + } +} + diff --git a/util/base64.h b/util/base64.h new file mode 100644 index 0000000..62caceb --- /dev/null +++ b/util/base64.h @@ -0,0 +1,32 @@ +// util/base64.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +namespace mongo { + namespace base64 { + + void encode( stringstream& ss , const char * data , int size ); + string encode( const char * data , int size ); + string encode( const string& s ); + + void decode( stringstream& ss , const string& s ); + string decode( const string& s ); + + + void testAlphabet(); + } +} diff --git a/util/builder.h b/util/builder.h new file mode 100644 index 0000000..5046b72 --- /dev/null +++ b/util/builder.h @@ -0,0 +1,207 @@ +/* builder.h */ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include <string.h> + +namespace mongo { + + class StringBuilder; + + class BufBuilder { + public: + BufBuilder(int initsize = 512) : size(initsize) { + if ( size > 0 ) { + data = (char *) malloc(size); + assert(data); + } else { + data = 0; + } + l = 0; + } + ~BufBuilder() { + kill(); + } + + void kill() { + if ( data ) { + free(data); + data = 0; + } + } + + void reset( int maxSize = 0 ){ + l = 0; + if ( maxSize && size > maxSize ){ + free(data); + data = (char*)malloc(maxSize); + size = maxSize; + } + + } + + /* leave room for some stuff later */ + void skip(int n) { + grow(n); + } + + /* note this may be deallocated (realloced) if you keep writing. */ + char* buf() { + return data; + } + + /* assume ownership of the buffer - you must then free it */ + void decouple() { + data = 0; + } + + template<class T> void append(T j) { + *((T*)grow(sizeof(T))) = j; + } + void append(short j) { + append<short>(j); + } + void append(int j) { + append<int>(j); + } + void append(unsigned j) { + append<unsigned>(j); + } + void append(bool j) { + append<bool>(j); + } + void append(double j) { + append<double>(j); + } + + void append(const void *src, int len) { + memcpy(grow(len), src, len); + } + + void append(const char *str) { + append((void*) str, strlen(str)+1); + } + + void append(const string &str) { + append( (void *)str.c_str(), str.length() + 1 ); + } + + int len() const { + return l; + } + + void setlen( int newLen ){ + l = newLen; + } + + private: + /* returns the pre-grow write position */ + char* grow(int by) { + int oldlen = l; + l += by; + if ( l > size ) { + int a = size * 2; + if ( a == 0 ) + a = 512; + if ( l > a ) + a = l + 16 * 1024; + assert( a < 64 * 1024 * 1024 ); + data = (char *) realloc(data, a); + size= a; + } + return data + oldlen; + } + + char *data; + int l; + int size; + + friend class StringBuilder; + }; + + class StringBuilder { + public: + StringBuilder( int initsize=256 ) + : _buf( initsize ){ + } + +#define SBNUM(val,maxSize,macro) \ + int prev = _buf.l; \ + int z = sprintf( _buf.grow(maxSize) , macro , (val) ); \ + _buf.l = prev + z; \ + return *this; + + + StringBuilder& operator<<( double x ){ + SBNUM( x , 25 , "%g" ); + } + StringBuilder& operator<<( int x ){ + SBNUM( x , 11 , "%d" ); + } + StringBuilder& operator<<( unsigned x ){ + SBNUM( x , 11 , "%u" ); + } + StringBuilder& operator<<( long x ){ + SBNUM( x , 22 , "%ld" ); + } + StringBuilder& operator<<( unsigned long x ){ + SBNUM( x , 22 , "%lu" ); + } + StringBuilder& operator<<( long long x ){ + SBNUM( x , 22 , "%lld" ); + } + StringBuilder& operator<<( unsigned long long x ){ + SBNUM( x , 22 , "%llu" ); + } + StringBuilder& operator<<( short x ){ + SBNUM( x , 8 , "%hd" ); + } + StringBuilder& operator<<( char c ){ + _buf.grow( 1 )[0] = c; + return *this; + } + + void append( const char * str ){ + int x = strlen( str ); + memcpy( _buf.grow( x ) , str , x ); + } + StringBuilder& operator<<( const char * str ){ + append( str ); + return *this; + } + StringBuilder& operator<<( const string& s ){ + append( s.c_str() ); + return *this; + } + + // access + + void reset( int maxSize = 0 ){ + _buf.reset( maxSize ); + } + + string str(){ + return string(_buf.data,0,_buf.l); + } + + private: + BufBuilder _buf; + }; + +} // namespace mongo diff --git a/util/debug_util.cpp b/util/debug_util.cpp new file mode 100644 index 0000000..283053f --- /dev/null +++ b/util/debug_util.cpp @@ -0,0 +1,59 @@ +// debug_util.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "../db/cmdline.h" +#include "../db/jsobj.h" + +namespace mongo { + +#if defined(_DEBUG) && !defined(_WIN32) + /* Magic gdb trampoline + * Do not call directly! call setupSIGTRAPforGDB() + * Assumptions: + * 1) gdbserver is on your path + * 2) You have run "handle SIGSTOP noprint" in gdb + * 3) cmdLine.port + 2000 is free + */ + void launchGDB(int){ + // Don't come back here + signal(SIGTRAP, SIG_IGN); + + int newPort = cmdLine.port + 2000; + string newPortStr = "localhost:" + BSONObjBuilder::numStr(newPort); + string pidToDebug = BSONObjBuilder::numStr(getpid()); + + cout << "\n\n\t**** Launching gdbserver on " << newPortStr << " ****" << endl << endl; + if (fork() == 0){ + //child + execlp("gdbserver", "gdbserver", "--attach", newPortStr.c_str(), pidToDebug.c_str(), NULL); + perror(NULL); + }else{ + //parent + raise(SIGSTOP); // pause all threads until gdb connects and continues + raise(SIGTRAP); // break inside gdbserver + } + } + + void setupSIGTRAPforGDB(){ + assert( signal(SIGTRAP , launchGDB ) != SIG_ERR ); + } +#else + void setupSIGTRAPforGDB() { + } +#endif +} diff --git a/util/debug_util.h b/util/debug_util.h new file mode 100644 index 0000000..6d633c5 --- /dev/null +++ b/util/debug_util.h @@ -0,0 +1,97 @@ +// debug_util.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef _WIN32 +#include <signal.h> +#endif // ndef _WIN32 + +namespace mongo { + +// for debugging + typedef struct _Ints { + int i[100]; + } *Ints; + typedef struct _Chars { + char c[200]; + } *Chars; + + typedef char CHARS[400]; + + typedef struct _OWS { + int size; + char type; + char string[400]; + } *OWS; + +// for now, running on win32 means development not production -- +// use this to log things just there. +#if defined(_WIN32) +#define WIN if( 1 ) +#else +#define WIN if( 0 ) +#endif + +#if defined(_DEBUG) +#define DEV if( 1 ) +#else +#define DEV if( 0 ) +#endif + +#define DEBUGGING if( 0 ) + +// The following declare one unique counter per enclosing function. +// NOTE The implementation double-increments on a match, but we don't really care. +#define SOMETIMES( occasion, howOften ) for( static unsigned occasion = 0; ++occasion % howOften == 0; ) +#define OCCASIONALLY SOMETIMES( occasionally, 16 ) +#define RARELY SOMETIMES( rarely, 128 ) +#define ONCE for( static bool undone = true; undone; undone = false ) + +#if defined(_WIN32) +#define strcasecmp _stricmp +#endif + + // Sets SIGTRAP handler to launch GDB + // Noop unless on *NIX and compiled with _DEBUG + void setupSIGTRAPforGDB(); + +#if defined(_WIN32) + inline void breakpoint() {} //noop +#else // defined(_WIN32) + // code to raise a breakpoint in GDB + inline void breakpoint(){ + ONCE { + //prevent SIGTRAP from crashing the program if default action is specified and we are not in gdb + struct sigaction current; + sigaction(SIGTRAP, NULL, ¤t); + if (current.sa_handler == SIG_DFL){ + signal(SIGTRAP, SIG_IGN); + } + } + + raise(SIGTRAP); + } +#endif // defined(_WIN32) + + // conditional breakpoint + inline void breakif(bool test){ + if (test) + breakpoint(); + } + +} // namespace mongo diff --git a/util/embedded_builder.h b/util/embedded_builder.h new file mode 100644 index 0000000..d945bfb --- /dev/null +++ b/util/embedded_builder.h @@ -0,0 +1,91 @@ +// embedded_builder.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + // utility class for assembling hierarchical objects + class EmbeddedBuilder { + public: + EmbeddedBuilder( BSONObjBuilder *b ) { + _builders.push_back( make_pair( "", b ) ); + } + // It is assumed that the calls to prepareContext will be made with the 'name' + // parameter in lex ascending order. + void prepareContext( string &name ) { + int i = 1, n = _builders.size(); + while( i < n && + name.substr( 0, _builders[ i ].first.length() ) == _builders[ i ].first && + ( name[ _builders[i].first.length() ] == '.' || name[ _builders[i].first.length() ] == 0 ) + ){ + name = name.substr( _builders[ i ].first.length() + 1 ); + ++i; + } + for( int j = n - 1; j >= i; --j ) { + popBuilder(); + } + for( string next = splitDot( name ); !next.empty(); next = splitDot( name ) ) { + addBuilder( next ); + } + } + void appendAs( const BSONElement &e, string name ) { + if ( e.type() == Object && e.valuesize() == 5 ) { // empty object -- this way we can add to it later + string dummyName = name + ".foo"; + prepareContext( dummyName ); + return; + } + prepareContext( name ); + back()->appendAs( e, name.c_str() ); + } + BufBuilder &subarrayStartAs( string name ) { + prepareContext( name ); + return back()->subarrayStart( name.c_str() ); + } + void done() { + while( ! _builderStorage.empty() ) + popBuilder(); + } + + static string splitDot( string & str ) { + size_t pos = str.find( '.' ); + if ( pos == string::npos ) + return ""; + string ret = str.substr( 0, pos ); + str = str.substr( pos + 1 ); + return ret; + } + + private: + void addBuilder( const string &name ) { + shared_ptr< BSONObjBuilder > newBuilder( new BSONObjBuilder( back()->subobjStart( name.c_str() ) ) ); + _builders.push_back( make_pair( name, newBuilder.get() ) ); + _builderStorage.push_back( newBuilder ); + } + void popBuilder() { + back()->done(); + _builders.pop_back(); + _builderStorage.pop_back(); + } + + BSONObjBuilder *back() { return _builders.back().second; } + + vector< pair< string, BSONObjBuilder * > > _builders; + vector< shared_ptr< BSONObjBuilder > > _builderStorage; + + }; + +} //namespace mongo diff --git a/util/file.h b/util/file.h new file mode 100644 index 0000000..347e2d6 --- /dev/null +++ b/util/file.h @@ -0,0 +1,168 @@ +// file.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if !defined(_WIN32) +#include "errno.h" +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#else +#include <windows.h> +#endif + +namespace mongo { + +#ifndef __sunos__ +typedef uint64_t fileofs; +#else +typedef boost::uint64_t fileofs; +#endif + +class FileInterface { +public: + void open(const char *fn) {} + void write(fileofs o, const char *data, unsigned len) {} + void read(fileofs o, char *data, unsigned len) {} + bool bad() {return false;} + bool is_open() {return false;} + fileofs len() { return 0; } +}; + +#if defined(_WIN32) +#include <io.h> +std::wstring toWideString(const char *s); + +class File : public FileInterface { + HANDLE fd; + bool _bad; + void err(BOOL b=false) { /* false = error happened */ + if( !b && !_bad ) { + _bad = true; + log() << "File I/O error " << GetLastError() << '\n'; + } + } +public: + File() { + fd = INVALID_HANDLE_VALUE; + _bad = true; + } + ~File() { + if( is_open() ) CloseHandle(fd); + fd = INVALID_HANDLE_VALUE; + } + void open(const char *filename, bool readOnly=false ) { + std::wstring filenamew = toWideString(filename); + fd = CreateFile( + filenamew.c_str(), ( readOnly ? 0 : GENERIC_WRITE ) | GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if( !is_open() ) { + out() << "CreateFile failed " << filename << endl; + } + else + _bad = false; + } + void write(fileofs o, const char *data, unsigned len) { + LARGE_INTEGER li; + li.QuadPart = o; + SetFilePointerEx(fd, li, NULL, FILE_BEGIN); + DWORD written; + err( WriteFile(fd, data, len, &written, NULL) ); + } + void read(fileofs o, char *data, unsigned len) { + DWORD read; + LARGE_INTEGER li; + li.QuadPart = o; + SetFilePointerEx(fd, li, NULL, FILE_BEGIN); + int ok = ReadFile(fd, data, len, &read, 0); + if( !ok ) + err(ok); + else + massert( 10438 , "ReadFile error - truncated file?", read == len); + } + bool bad() { return _bad; } + bool is_open() { return fd != INVALID_HANDLE_VALUE; } + fileofs len() { + LARGE_INTEGER li; + li.LowPart = GetFileSize(fd, (DWORD *) &li.HighPart); + if( li.HighPart == 0 && li.LowPart == INVALID_FILE_SIZE ) { + err( false ); + return 0; + } + return li.QuadPart; + } + void fsync() { FlushFileBuffers(fd); } +}; + +#else + +class File : public FileInterface { + int fd; + bool _bad; + void err(bool ok) { + if( !ok && !_bad ) { + _bad = true; + log() << "File I/O " << OUTPUT_ERRNO << '\n'; + } + } +public: + File() { + fd = -1; + _bad = true; + } + ~File() { + if( is_open() ) ::close(fd); + fd = -1; + } + +#ifndef O_NOATIME +#define O_NOATIME 0 +#define lseek64 lseek +#endif + + void open(const char *filename, bool readOnly=false ) { + fd = ::open(filename, O_CREAT | ( readOnly ? 0 : O_RDWR ) | O_NOATIME, S_IRUSR | S_IWUSR); + if ( fd <= 0 ) { + out() << "couldn't open " << filename << ' ' << OUTPUT_ERRNO << endl; + return; + } + _bad = false; + } + void write(fileofs o, const char *data, unsigned len) { + lseek64(fd, o, SEEK_SET); + err( ::write(fd, data, len) == (int) len ); + } + void read(fileofs o, char *data, unsigned len) { + lseek(fd, o, SEEK_SET); + err( ::read(fd, data, len) == (int) len ); + } + bool bad() { return _bad; } + bool is_open() { return fd > 0; } + fileofs len() { + return lseek(fd, 0, SEEK_END); + } + void fsync() { ::fsync(fd); } +}; + + +#endif + + +} + diff --git a/util/file_allocator.h b/util/file_allocator.h new file mode 100644 index 0000000..73159d3 --- /dev/null +++ b/util/file_allocator.h @@ -0,0 +1,229 @@ +//file_allocator.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../stdafx.h" +#include <fcntl.h> +#include <errno.h> +#if defined(__freebsd__) +#include <sys/stat.h> +#endif + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + +namespace mongo { + + /* Handles allocation of contiguous files on disk. Allocation may be + requested asynchronously or synchronously. + */ + class FileAllocator { + /* The public functions may not be called concurrently. The allocation + functions may be called multiple times per file, but only the first + size specified per file will be used. + */ + public: +#if !defined(_WIN32) + FileAllocator() : failed_() {} +#endif + void start() { +#if !defined(_WIN32) + Runner r( *this ); + boost::thread t( r ); +#endif + } + // May be called if file exists. If file exists, or its allocation has + // been requested, size is updated to match existing file size. + void requestAllocation( const string &name, long &size ) { + /* Some of the system calls in the file allocator don't work in win, + so no win support - 32 or 64 bit. Plus we don't seem to need preallocation + on windows anyway as we don't have to pre-zero the file there. + */ +#if !defined(_WIN32) + boostlock lk( pendingMutex_ ); + if ( failed_ ) + return; + long oldSize = prevSize( name ); + if ( oldSize != -1 ) { + size = oldSize; + return; + } + pending_.push_back( name ); + pendingSize_[ name ] = size; + pendingUpdated_.notify_all(); +#endif + } + // Returns when file has been allocated. If file exists, size is + // updated to match existing file size. + void allocateAsap( const string &name, long &size ) { +#if !defined(_WIN32) + boostlock lk( pendingMutex_ ); + long oldSize = prevSize( name ); + if ( oldSize != -1 ) { + size = oldSize; + if ( !inProgress( name ) ) + return; + } + checkFailure(); + pendingSize_[ name ] = size; + if ( pending_.size() == 0 ) + pending_.push_back( name ); + else if ( pending_.front() != name ) { + pending_.remove( name ); + list< string >::iterator i = pending_.begin(); + ++i; + pending_.insert( i, name ); + } + pendingUpdated_.notify_all(); + while( inProgress( name ) ) { + checkFailure(); + pendingUpdated_.wait( lk ); + } +#endif + } + + void waitUntilFinished() const { +#if !defined(_WIN32) + if ( failed_ ) + return; + boostlock lk( pendingMutex_ ); + while( pending_.size() != 0 ) + pendingUpdated_.wait( lk ); +#endif + } + + private: +#if !defined(_WIN32) + void checkFailure() { + massert( 12520, "file allocation failure", !failed_ ); + } + + // caller must hold pendingMutex_ lock. Returns size if allocated or + // allocation requested, -1 otherwise. + long prevSize( const string &name ) const { + if ( pendingSize_.count( name ) > 0 ) + return pendingSize_[ name ]; + if ( boost::filesystem::exists( name ) ) + return boost::filesystem::file_size( name ); + return -1; + } + + // caller must hold pendingMutex_ lock. + bool inProgress( const string &name ) const { + for( list< string >::const_iterator i = pending_.begin(); i != pending_.end(); ++i ) + if ( *i == name ) + return true; + return false; + } + + mutable boost::mutex pendingMutex_; + mutable boost::condition pendingUpdated_; + list< string > pending_; + mutable map< string, long > pendingSize_; + bool failed_; + + struct Runner { + Runner( FileAllocator &allocator ) : a_( allocator ) {} + FileAllocator &a_; + void operator()() { + while( 1 ) { + { + boostlock lk( a_.pendingMutex_ ); + if ( a_.pending_.size() == 0 ) + a_.pendingUpdated_.wait( lk ); + } + while( 1 ) { + string name; + long size; + { + boostlock lk( a_.pendingMutex_ ); + if ( a_.pending_.size() == 0 ) + break; + name = a_.pending_.front(); + size = a_.pendingSize_[ name ]; + } + try { + long fd = open(name.c_str(), O_CREAT | O_RDWR | O_NOATIME, S_IRUSR | S_IWUSR); + if ( fd <= 0 ) { + stringstream ss; + ss << "couldn't open " << name << ' ' << OUTPUT_ERRNO; + massert( 10439 , ss.str(), fd <= 0 ); + } + +#if defined(POSIX_FADV_DONTNEED) + if( posix_fadvise(fd, 0, size, POSIX_FADV_DONTNEED) ) { + log() << "warning: posix_fadvise fails " << name << ' ' << OUTPUT_ERRNO << endl; + } +#endif + + /* make sure the file is the full desired length */ + off_t filelen = lseek(fd, 0, SEEK_END); + if ( filelen < size ) { + massert( 10440 , "failure creating new datafile", filelen == 0 ); + // Check for end of disk. + massert( 10441 , "Unable to allocate file of desired size", + size - 1 == lseek(fd, size - 1, SEEK_SET) ); + massert( 10442 , "Unable to allocate file of desired size", + 1 == write(fd, "", 1) ); + lseek(fd, 0, SEEK_SET); + log() << "allocating new datafile " << name << ", filling with zeroes..." << endl; + Timer t; + long z = 256 * 1024; + char buf[z]; + memset(buf, 0, z); + long left = size; + while ( 1 ) { + if ( left <= z ) { + massert( 10443 , "write failed", left == write(fd, buf, left) ); + break; + } + massert( 10444 , "write failed", z == write(fd, buf, z) ); + left -= z; + } + log() << "done allocating datafile " << name << ", size: " << size/1024/1024 << "MB, took " << ((double)t.millis())/1000.0 << " secs" << endl; + } + close( fd ); + + } catch ( ... ) { + problem() << "Failed to allocate new file: " << name + << ", size: " << size << ", aborting." << endl; + try { + BOOST_CHECK_EXCEPTION( boost::filesystem::remove( name ) ); + } catch ( ... ) { + } + boostlock lk( a_.pendingMutex_ ); + a_.failed_ = true; + // not erasing from pending + a_.pendingUpdated_.notify_all(); + return; // no more allocation + } + + { + boostlock lk( a_.pendingMutex_ ); + a_.pendingSize_.erase( name ); + a_.pending_.pop_front(); + a_.pendingUpdated_.notify_all(); + } + } + } + } + }; +#endif + }; + + FileAllocator &theFileAllocator(); +} // namespace mongo diff --git a/util/goodies.h b/util/goodies.h new file mode 100644 index 0000000..7eebc0a --- /dev/null +++ b/util/goodies.h @@ -0,0 +1,526 @@ +// goodies.h +// miscellaneous junk + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if defined(_WIN32) +# include <windows.h> +#endif + +namespace mongo { + +#if !defined(_WIN32) && !defined(NOEXECINFO) + +} // namespace mongo + +#include <pthread.h> +#include <execinfo.h> + +namespace mongo { + + inline pthread_t GetCurrentThreadId() { + return pthread_self(); + } + + /* use "addr2line -CFe <exe>" to parse. */ + inline void printStackTrace( ostream &o = cout ) { + void *b[20]; + size_t size; + char **strings; + size_t i; + + size = backtrace(b, 20); + strings = backtrace_symbols(b, size); + + for (i = 0; i < size; i++) + o << hex << b[i] << dec << ' '; + o << '\n'; + for (i = 0; i < size; i++) + o << ' ' << strings[i] << '\n'; + + free (strings); + } +#else + inline void printStackTrace( ostream &o = cout ) { } +#endif + + /* set to TRUE if we are exiting */ + extern bool goingAway; + + /* find the multimap member which matches a particular key and value. + + note this can be slow if there are a lot with the same key. + */ + template<class C,class K,class V> inline typename C::iterator kv_find(C& c, const K& k,const V& v) { + pair<typename C::iterator,typename C::iterator> p = c.equal_range(k); + + for ( typename C::iterator it=p.first; it!=p.second; ++it) + if ( it->second == v ) + return it; + + return c.end(); + } + + bool isPrime(int n); + int nextPrime(int n); + + inline void dumpmemory(const char *data, int len) { + if ( len > 1024 ) + len = 1024; + try { + const char *q = data; + const char *p = q; + while ( len > 0 ) { + for ( int i = 0; i < 16; i++ ) { + if ( *p >= 32 && *p <= 126 ) + cout << *p; + else + cout << '.'; + p++; + } + cout << " "; + p -= 16; + for ( int i = 0; i < 16; i++ ) + cout << (unsigned) ((unsigned char)*p++) << ' '; + cout << endl; + len -= 16; + } + } catch (...) { + } + } + +// PRINT(2+2); prints "2+2: 4" +#define PRINT(x) cout << #x ": " << (x) << endl +// PRINTFL; prints file:line +#define PRINTFL cout << __FILE__ ":" << __LINE__ << endl + +#undef yassert + +#undef assert +#define assert xassert +#define yassert 1 + + struct WrappingInt { + WrappingInt() { + x = 0; + } + WrappingInt(unsigned z) : x(z) { } + volatile unsigned x; + operator unsigned() const { + return x; + } + + // returns original value (like x++) + WrappingInt atomicIncrement(){ +#if defined(_WIN32) + // InterlockedIncrement returns the new value + return InterlockedIncrement((volatile long*)&x)-1; //long is 32bits in Win64 +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + // this is in GCC >= 4.1 + return __sync_fetch_and_add(&x, 1); +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + // from boost 1.39 interprocess/detail/atomic.hpp + int r; + int val = 1; + asm volatile + ( + "lock\n\t" + "xadd %1, %0": + "+m"( x ), "=r"( r ): // outputs (%0, %1) + "1"( val ): // inputs (%2 == %1) + "memory", "cc" // clobbers + ); + return r; +#else +# error "unsupported compiler or platform" +#endif + } + + static int diff(unsigned a, unsigned b) { + return a-b; + } + bool operator<=(WrappingInt r) { + // platform dependent + int df = (r.x - x); + return df >= 0; + } + bool operator>(WrappingInt r) { + return !(r<=*this); + } + }; + +} // namespace mongo + +#include <ctime> + +namespace mongo { + + inline void time_t_to_String(time_t t, char *buf) { +#if defined(_WIN32) + ctime_s(buf, 64, &t); +#else + ctime_r(&t, buf); +#endif + buf[24] = 0; // don't want the \n + } + +#define asctime _asctime_not_threadsafe_ +#define gmtime _gmtime_not_threadsafe_ +#define localtime _localtime_not_threadsafe_ +#define ctime _ctime_is_not_threadsafe_ + +#if defined(_WIN32) || defined(__sunos__) + inline void sleepsecs(int s) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += s; + boost::thread::sleep(xt); + } + inline void sleepmillis(int s) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += ( s / 1000 ); + xt.nsec += ( s % 1000 ) * 1000000; + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } + inline void sleepmicros(int s) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += ( s / 1000000 ); + xt.nsec += ( s % 1000000 ) * 1000; + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } +#else + inline void sleepsecs(int s) { + struct timespec t; + t.tv_sec = s; + t.tv_nsec = 0; + if ( nanosleep( &t , 0 ) ){ + cout << "nanosleep failed" << endl; + } + } + inline void sleepmicros(int s) { + struct timespec t; + t.tv_sec = (int)(s / 1000000); + t.tv_nsec = s % 1000000; + if ( nanosleep( &t , 0 ) ){ + cout << "nanosleep failed" << endl; + } + } + inline void sleepmillis(int s) { + sleepmicros( s * 1000 ); + } +#endif + +// note this wraps + inline int tdiff(unsigned told, unsigned tnew) { + return WrappingInt::diff(tnew, told); + } + inline unsigned curTimeMillis() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned t = xt.nsec / 1000000; + return (xt.sec & 0xfffff) * 1000 + t; + } + + struct Date_t { + // TODO: make signed (and look for related TODO's) + unsigned long long millis; + Date_t(): millis(0) {} + Date_t(unsigned long long m): millis(m) {} + operator unsigned long long&() { return millis; } + operator const unsigned long long&() const { return millis; } + }; + + inline Date_t jsTime() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned long long t = xt.nsec / 1000000; + return ((unsigned long long) xt.sec * 1000) + t; + } + + inline unsigned long long curTimeMicros64() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned long long t = xt.nsec / 1000; + return (((unsigned long long) xt.sec) * 1000000) + t; + } + +// measures up to 1024 seconds. or, 512 seconds with tdiff that is... + inline unsigned curTimeMicros() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned t = xt.nsec / 1000; + unsigned secs = xt.sec % 1024; + return secs*1000000 + t; + } + using namespace boost; + typedef boost::mutex::scoped_lock boostlock; + typedef boost::recursive_mutex::scoped_lock recursive_boostlock; + +// simple scoped timer + class Timer { + public: + Timer() { + reset(); + } + Timer( unsigned long long start ) { + old = start; + } + int seconds(){ + return (int)(micros() / 1000000); + } + int millis() { + return (long)(micros() / 1000); + } + unsigned long long micros() { + unsigned long long n = curTimeMicros64(); + return n - old; + } + unsigned long long micros(unsigned long long & n) { // returns cur time in addition to timer result + n = curTimeMicros64(); + return n - old; + } + unsigned long long startTime(){ + return old; + } + void reset() { + old = curTimeMicros64(); + } + private: + unsigned long long old; + }; + + /* + + class DebugMutex : boost::noncopyable { + friend class lock; + boost::mutex m; + int locked; + public: + DebugMutex() : locked(0); { } + bool isLocked() { return locked; } + }; + + */ + +//typedef boostlock lock; + + inline bool startsWith(const char *str, const char *prefix) { + unsigned l = strlen(prefix); + if ( strlen(str) < l ) return false; + return strncmp(str, prefix, l) == 0; + } + + inline bool endsWith(const char *p, const char *suffix) { + int a = strlen(p); + int b = strlen(suffix); + if ( b > a ) return false; + return strcmp(p + a - b, suffix) == 0; + } + +} // namespace mongo + +#include "boost/detail/endian.hpp" + +namespace mongo { + + inline unsigned long swapEndian(unsigned long x) { + return + ((x & 0xff) << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000) >> 8) | + ((x & 0xff000000) >> 24); + } + +#if defined(BOOST_LITTLE_ENDIAN) + inline unsigned long fixEndian(unsigned long x) { + return x; + } +#else + inline unsigned long fixEndian(unsigned long x) { + return swapEndian(x); + } +#endif + + // Like strlen, but only scans up to n bytes. + // Returns -1 if no '0' found. + inline int strnlen( const char *s, int n ) { + for( int i = 0; i < n; ++i ) + if ( !s[ i ] ) + return i; + return -1; + } + +#if !defined(_WIN32) + typedef int HANDLE; + inline void strcpy_s(char *dst, unsigned len, const char *src) { + strcpy(dst, src); + } +#else + typedef void *HANDLE; +#endif + + /* thread local "value" rather than a pointer + good for things which have copy constructors (and the copy constructor is fast enough) + e.g. + ThreadLocalValue<int> myint; + */ + template<class T> + class ThreadLocalValue { + public: + ThreadLocalValue( T def = 0 ) : _default( def ) { } + + T get() { + T * val = _val.get(); + if ( val ) + return *val; + return _default; + } + + void set( const T& i ) { + T *v = _val.get(); + if( v ) { + *v = i; + return; + } + v = new T(i); + _val.reset( v ); + } + + private: + T _default; + boost::thread_specific_ptr<T> _val; + }; + + class ProgressMeter { + public: + ProgressMeter( long long total , int secondsBetween = 3 , int checkInterval = 100 ) + : _total( total ) , _secondsBetween( secondsBetween ) , _checkInterval( checkInterval ) , + _done(0) , _hits(0) , _lastTime( (int) time(0) ){ + } + + bool hit( int n = 1 ){ + _done += n; + _hits++; + if ( _hits % _checkInterval ) + return false; + + int t = (int) time(0); + if ( t - _lastTime < _secondsBetween ) + return false; + + if ( _total > 0 ){ + int per = (int)( ( (double)_done * 100.0 ) / (double)_total ); + cout << "\t\t" << _done << "/" << _total << "\t" << per << "%" << endl; + } + _lastTime = t; + return true; + } + + long long done(){ + return _done; + } + + long long hits(){ + return _hits; + } + + private: + + long long _total; + int _secondsBetween; + int _checkInterval; + + long long _done; + long long _hits; + int _lastTime; + }; + + class TicketHolder { + public: + TicketHolder( int num ){ + _outof = num; + _num = num; + } + + bool tryAcquire(){ + boostlock lk( _mutex ); + if ( _num <= 0 ){ + if ( _num < 0 ){ + cerr << "DISASTER! in TicketHolder" << endl; + } + return false; + } + _num--; + return true; + } + + void release(){ + boostlock lk( _mutex ); + _num++; + } + + void resize( int newSize ){ + boostlock lk( _mutex ); + int used = _outof - _num; + if ( used > newSize ){ + cout << "ERROR: can't resize since we're using (" << used << ") more than newSize(" << newSize << ")" << endl; + return; + } + + _outof = newSize; + _num = _outof - used; + } + + int available(){ + return _num; + } + + int used(){ + return _outof - _num; + } + + private: + int _outof; + int _num; + boost::mutex _mutex; + }; + + class TicketHolderReleaser { + public: + TicketHolderReleaser( TicketHolder * holder ){ + _holder = holder; + } + + ~TicketHolderReleaser(){ + _holder->release(); + } + private: + TicketHolder * _holder; + }; + +} // namespace mongo diff --git a/util/hashtab.h b/util/hashtab.h new file mode 100644 index 0000000..214c0ae --- /dev/null +++ b/util/hashtab.h @@ -0,0 +1,164 @@ +/* hashtab.h + + Simple, fixed size hash table. Darn simple. + + Uses a contiguous block of memory, so you can put it in a memory mapped file very easily. +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include <map> + +namespace mongo { + +#pragma pack(1) + + /* you should define: + + int Key::hash() return > 0 always. + */ + + template < + class Key, + class Type + > + class HashTable : boost::noncopyable { + public: + const char *name; + struct Node { + int hash; + Key k; + Type value; + bool inUse() { + return hash != 0; + } + void setUnused() { + hash = 0; + } + } *nodes; + int n; + int maxChain; + + int _find(const Key& k, bool& found) { + found = false; + int h = k.hash(); + int i = h % n; + int start = i; + int chain = 0; + int firstNonUsed = -1; + while ( 1 ) { + if ( !nodes[i].inUse() ) { + if ( firstNonUsed < 0 ) + firstNonUsed = i; + } + + if ( nodes[i].hash == h && nodes[i].k == k ) { + if ( chain >= 200 ) + out() << "warning: hashtable " << name << " long chain " << endl; + found = true; + return i; + } + chain++; + i = (i+1) % n; + if ( i == start ) { + // shouldn't get here / defensive for infinite loops + out() << "error: hashtable " << name << " is full n:" << n << endl; + return -1; + } + if( chain >= maxChain ) { + if ( firstNonUsed >= 0 ) + return firstNonUsed; + out() << "error: hashtable " << name << " max chain n:" << n << endl; + return -1; + } + } + } + + public: + /* buf must be all zeroes on initialization. */ + HashTable(void *buf, int buflen, const char *_name) : name(_name) { + int m = sizeof(Node); + // out() << "hashtab init, buflen:" << buflen << " m:" << m << endl; + n = buflen / m; + if ( (n & 1) == 0 ) + n--; + maxChain = (int) (n * 0.05); + nodes = (Node *) buf; + + assert( sizeof(Node) == 628 ); + //out() << "HashTable() " << _name << " sizeof(node):" << sizeof(Node) << " n:" << n << endl; + } + + Type* get(const Key& k) { + bool found; + int i = _find(k, found); + if ( found ) + return &nodes[i].value; + return 0; + } + + void kill(const Key& k) { + bool found; + int i = _find(k, found); + if ( i >= 0 && found ) { + nodes[i].k.kill(); + nodes[i].setUnused(); + } + } +/* + void drop(const Key& k) { + bool found; + int i = _find(k, found); + if ( i >= 0 && found ) { + nodes[i].setUnused(); + } + } +*/ + /** returns false if too full */ + bool put(const Key& k, const Type& value) { + bool found; + int i = _find(k, found); + if ( i < 0 ) + return false; + if ( !found ) { + nodes[i].k = k; + nodes[i].hash = k.hash(); + } + else { + assert( nodes[i].hash == k.hash() ); + } + nodes[i].value = value; + return true; + } + + typedef void (*IteratorCallback)( const Key& k , Type& v ); + + void iterall( IteratorCallback callback ){ + for ( int i=0; i<n; i++ ){ + if ( ! nodes[i].inUse() ) + continue; + callback( nodes[i].k , nodes[i].value ); + } + } + + }; + +#pragma pack() + +} // namespace mongo diff --git a/util/httpclient.cpp b/util/httpclient.cpp new file mode 100644 index 0000000..284bb63 --- /dev/null +++ b/util/httpclient.cpp @@ -0,0 +1,61 @@ +// httpclient.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "httpclient.h" + +namespace mongo { + + int HttpClient::get( string url , map<string,string>& headers, stringstream& data ){ + uassert( 10271 , "invalid url" , url.find( "http://" ) == 0 ); + url = url.substr( 7 ); + + string host , path; + if ( url.find( "/" ) == string::npos ){ + host = url; + path = "/"; + } + else { + host = url.substr( 0 , url.find( "/" ) ); + path = url.substr( url.find( "/" ) ); + } + + int port = 80; + uassert( 10272 , "non standard port not supported yet" , host.find( ":" ) == string::npos ); + + cout << "host [" << host << "]" << endl; + cout << "path [" << path << "]" << endl; + cout << "port: " << port << endl; + + string req; + { + stringstream ss; + ss << "GET " << path << " HTTP/1.1\r\n"; + ss << "Host: " << host << "\r\n"; + ss << "Connection: Close\r\n"; + ss << "User-Agent: mongodb http client\r\n"; + ss << "\r\n"; + + req = ss.str(); + } + + cout << req << endl; + + return -1; + } + +} diff --git a/util/httpclient.h b/util/httpclient.h new file mode 100644 index 0000000..14f0d87 --- /dev/null +++ b/util/httpclient.h @@ -0,0 +1,29 @@ +// httpclient.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" + +namespace mongo { + + class HttpClient { + public: + int get( string url , map<string,string>& headers, stringstream& data ); + }; +} + diff --git a/util/log.h b/util/log.h new file mode 100644 index 0000000..a9f43c8 --- /dev/null +++ b/util/log.h @@ -0,0 +1,247 @@ +// log.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string.h> + +namespace mongo { + + using boost::shared_ptr; + + // Utility interface for stringifying object only when val() called. + class LazyString { + public: + virtual ~LazyString() {} + virtual string val() const = 0; + }; + + // Utility class for stringifying object only when val() called. + template< class T > + class LazyStringImpl : public LazyString { + public: + LazyStringImpl( const T &t ) : t_( t ) {} + virtual string val() const { return (string)t_; } + private: + const T& t_; + }; + + class Nullstream { + public: + virtual ~Nullstream() {} + virtual Nullstream& operator<<(const char *) { + return *this; + } + virtual Nullstream& operator<<(char *) { + return *this; + } + virtual Nullstream& operator<<(char) { + return *this; + } + virtual Nullstream& operator<<(int) { + return *this; + } + virtual Nullstream& operator<<(ExitCode) { + return *this; + } + virtual Nullstream& operator<<(unsigned long) { + return *this; + } + virtual Nullstream& operator<<(long) { + return *this; + } + virtual Nullstream& operator<<(unsigned) { + return *this; + } + virtual Nullstream& operator<<(double) { + return *this; + } + virtual Nullstream& operator<<(void *) { + return *this; + } + virtual Nullstream& operator<<(const void *) { + return *this; + } + virtual Nullstream& operator<<(long long) { + return *this; + } + virtual Nullstream& operator<<(unsigned long long) { + return *this; + } + virtual Nullstream& operator<<(bool) { + return *this; + } + virtual Nullstream& operator<<(const LazyString&) { + return *this; + } + template< class T > + Nullstream& operator<<(T *t) { + return operator<<( static_cast<void*>( t ) ); + } + template< class T > + Nullstream& operator<<(const T *t) { + return operator<<( static_cast<const void*>( t ) ); + } + template< class T > + Nullstream& operator<<(const shared_ptr<T> p ){ + return *this; + } + template< class T > + Nullstream& operator<<(const T &t) { + return operator<<( static_cast<const LazyString&>( LazyStringImpl< T >( t ) ) ); + } + virtual Nullstream& operator<< (ostream& ( *endl )(ostream&)) { + return *this; + } + virtual Nullstream& operator<< (ios_base& (*hex)(ios_base&)) { + return *this; + } + virtual void flush(){} + }; + extern Nullstream nullstream; + +#define LOGIT { ss << x; return *this; } + + class Logstream : public Nullstream { + static boost::mutex &mutex; + static int doneSetup; + stringstream ss; + public: + static int magicNumber(){ + return 1717; + } + void flush() { + // this ensures things are sane + if ( doneSetup == 1717 ){ + boostlock lk(mutex); + cout << ss.str(); + cout.flush(); + } + ss.str(""); + } + Logstream& operator<<(const char *x) LOGIT + Logstream& operator<<(char *x) LOGIT + Logstream& operator<<(char x) LOGIT + Logstream& operator<<(int x) LOGIT + Logstream& operator<<(ExitCode x) LOGIT + Logstream& operator<<(long x) LOGIT + Logstream& operator<<(unsigned long x) LOGIT + Logstream& operator<<(unsigned x) LOGIT + Logstream& operator<<(double x) LOGIT + Logstream& operator<<(void *x) LOGIT + Logstream& operator<<(const void *x) LOGIT + Logstream& operator<<(long long x) LOGIT + Logstream& operator<<(unsigned long long x) LOGIT + Logstream& operator<<(bool x) LOGIT + Logstream& operator<<(const LazyString& x) { + ss << x.val(); + return *this; + } + Logstream& operator<< (ostream& ( *_endl )(ostream&)) { + ss << '\n'; + flush(); + return *this; + } + Logstream& operator<< (ios_base& (*_hex)(ios_base&)) { + ss << _hex; + return *this; + } + + template< class T > + Nullstream& operator<<(const shared_ptr<T> p ){ + T * t = p.get(); + if ( ! t ) + *this << "null"; + else + *this << t; + return *this; + } + + Logstream& prolog() { + char now[64]; + time_t_to_String(time(0), now); + now[20] = 0; + ss << now; + return *this; + } + + private: + static thread_specific_ptr<Logstream> tsp; + public: + static Logstream& get() { + Logstream *p = tsp.get(); + if( p == 0 ) + tsp.reset( p = new Logstream() ); + return *p; + } + }; + + extern int logLevel; + + inline Nullstream& out( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + return Logstream::get(); + } + + /* flush the log stream if the log level is + at the specified level or higher. */ + inline void logflush(int level = 0) { + if( level > logLevel ) + Logstream::get().flush(); + } + + /* without prolog */ + inline Nullstream& _log( int level = 0 ){ + if ( level > logLevel ) + return nullstream; + return Logstream::get(); + } + + inline Nullstream& log( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + return Logstream::get().prolog(); + } + + /* TODOCONCURRENCY */ + inline ostream& stdcout() { + return cout; + } + + /* default impl returns "" -- mongod overrides */ + extern const char * (*getcurns)(); + + inline Nullstream& problem( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + Logstream& l = Logstream::get().prolog(); + l << ' ' << getcurns() << ' '; + return l; + } + + /** + log to a file rather than stdout + defined in assert_util.cpp + */ + void initLogging( const string& logpath , bool append ); + void rotateLogs( int signal = 0 ); + +#define OUTPUT_ERRNOX(x) "errno:" << x << " " << strerror(x) +#define OUTPUT_ERRNO OUTPUT_ERRNOX(errno) + +} // namespace mongo diff --git a/util/lruishmap.h b/util/lruishmap.h new file mode 100644 index 0000000..c390cb2 --- /dev/null +++ b/util/lruishmap.h @@ -0,0 +1,78 @@ +// lru-ish map.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include "../util/goodies.h" + +namespace mongo { + + /* Your K object must define: + int hash() - must always return > 0. + operator== + */ + + template <class K, class V, int MaxChain> + class LRUishMap { + public: + LRUishMap(int _n) { + n = nextPrime(_n); + keys = new K[n]; + hashes = new int[n]; + for ( int i = 0; i < n; i++ ) hashes[i] = 0; + } + ~LRUishMap() { + delete[] keys; + delete[] hashes; + } + + int _find(const K& k, bool& found) { + int h = k.hash(); + assert( h > 0 ); + int j = h % n; + int first = j; + for ( int i = 0; i < MaxChain; i++ ) { + if ( hashes[j] == h ) { + if ( keys[j] == k ) { + found = true; + return j; + } + } + else if ( hashes[j] == 0 ) { + found = false; + return j; + } + } + found = false; + return first; + } + + V* find(const K& k) { + bool found; + int j = _find(k, found); + return found ? &values[j] : 0; + } + + private: + int n; + K *keys; + int *hashes; + V *values; + }; + +} // namespace mongo diff --git a/util/md5.c b/util/md5.c new file mode 100644 index 0000000..c35d96c --- /dev/null +++ b/util/md5.c @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include <string.h> + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include <stdio.h> in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include <string.h> + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/util/md5.h b/util/md5.h new file mode 100644 index 0000000..d001234 --- /dev/null +++ b/util/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke <purschke@bnl.gov>. + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* Initialize the algorithm. */ + void md5_init(md5_state_t *pms); + + /* Append a string to the message. */ + void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + + /* Finish the message and return the digest. */ + void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/util/md5.hpp b/util/md5.hpp new file mode 100644 index 0000000..d955910 --- /dev/null +++ b/util/md5.hpp @@ -0,0 +1,53 @@ +// md5.hpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "md5.h" + +namespace mongo { + + typedef unsigned char md5digest[16]; + + inline void md5(const void *buf, int nbytes, md5digest digest) { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t *) buf, nbytes); + md5_finish(&st, digest); + } + + inline void md5(const char *str, md5digest digest) { + md5(str, strlen(str), digest); + } + + inline std::string digestToString( md5digest digest ){ + static const char * letters = "0123456789abcdef"; + stringstream ss; + for ( int i=0; i<16; i++){ + unsigned char c = digest[i]; + ss << letters[ ( c >> 4 ) & 0xf ] << letters[ c & 0xf ]; + } + return ss.str(); + } + + inline std::string md5simpledigest( string s ){ + md5digest d; + md5( s.c_str() , d ); + return digestToString( d ); + } + +} // namespace mongo diff --git a/util/md5main.cpp b/util/md5main.cpp new file mode 100644 index 0000000..d5b4982 --- /dev/null +++ b/util/md5main.cpp @@ -0,0 +1,144 @@ +/* + Copyright (C) 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5main.c,v 1.1 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Splits off main program into a separate file, md5main.c. + */ + +#include "stdafx.h" +#include "md5.h" +#include <math.h> +#include <stdio.h> +#include <string.h> + +/* + * This file builds an executable that performs various functions related + * to the MD5 library. Typical compilation: + * gcc -o md5main -lm md5main.c md5.c + */ +static const char *const usage = "\ +Usage:\n\ + md5main --test # run the self-test (A.5 of RFC 1321)\n\ + md5main --t-values # print the T values for the library\n\ + md5main --version # print the version of the package\n\ +"; +static const char *const version = "2002-04-13"; + +/* modified: not static, renamed */ +/* Run the self-test. */ +/*static*/ int +//do_test(void) +do_md5_test(void) +{ + static const char *const test[7*2] = { + "", "d41d8cd98f00b204e9800998ecf8427e", + "a", "0cc175b9c0f1b6a831c399e269772661", + "abc", "900150983cd24fb0d6963f7d28e17f72", + "message digest", "f96b697d7cb7938d525a2f31aaf161d0", + "abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "d174ab98d277d9f5a5611c2c9f419d9f", + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", "57edf4a22be3c955ac49da2e2107b67a" + }; + int i; + int status = 0; + + for (i = 0; i < 7*2; i += 2) { + md5_state_t state; + md5_byte_t digest[16]; + char hex_output[16*2 + 1]; + int di; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)test[i], strlen(test[i])); + md5_finish(&state, digest); + for (di = 0; di < 16; ++di) + sprintf(hex_output + di * 2, "%02x", digest[di]); + if (strcmp(hex_output, test[i + 1])) { + printf("MD5 (\"%s\") = ", test[i]); + puts(hex_output); + printf("**** ERROR, should be: %s\n", test[i + 1]); + status = 1; + } + } +// if (status == 0) +/*modified commented out: puts("md5 self-test completed successfully."); */ + return status; +} + +/* Print the T values. */ +static int +do_t_values(void) +{ + int i; + for (i = 1; i <= 64; ++i) { + unsigned long v = (unsigned long)(4294967296.0 * fabs(sin((double)i))); + + /* + * The following nonsense is only to avoid compiler warnings about + * "integer constant is unsigned in ANSI C, signed with -traditional". + */ + if (v >> 31) { + printf("#define T%d /* 0x%08lx */ (T_MASK ^ 0x%08lx)\n", i, + v, (unsigned long)(unsigned int)(~v)); + } else { + printf("#define T%d 0x%08lx\n", i, v); + } + } + return 0; +} + +/* modified from original code changed function name main->md5main */ +/* Main program */ +int +md5main(int argc, char *argv[]) +{ + if (argc == 2) { + if (!strcmp(argv[1], "--test")) + return do_md5_test(); + if (!strcmp(argv[1], "--t-values")) + return do_t_values(); + if (!strcmp(argv[1], "--version")) { + puts(version); + return 0; + } + } + puts(usage); + return 0; +} + diff --git a/util/message.cpp b/util/message.cpp new file mode 100644 index 0000000..0fbc2d2 --- /dev/null +++ b/util/message.cpp @@ -0,0 +1,466 @@ +/* message + + todo: authenticate; encrypt? +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "message.h" +#include <time.h> +#include "../util/goodies.h" +#include "../util/background.h" +#include <fcntl.h> +#include <errno.h> +#include "../db/cmdline.h" + +namespace mongo { + + bool objcheck = false; + +// if you want trace output: +#define mmm(x) + +#ifdef MSG_NOSIGNAL + const int portSendFlags = MSG_NOSIGNAL; +#else + const int portSendFlags = 0; +#endif + + /* listener ------------------------------------------------------------------- */ + + bool Listener::init() { + SockAddr me; + if ( ip.empty() ) + me = SockAddr( port ); + else + me = SockAddr( ip.c_str(), port ); + sock = ::socket(AF_INET, SOCK_STREAM, 0); + if ( sock == INVALID_SOCKET ) { + log() << "ERROR: listen(): invalid socket? " << OUTPUT_ERRNO << endl; + return false; + } + prebindOptions( sock ); + if ( ::bind(sock, (sockaddr *) &me.sa, me.addressSize) != 0 ) { + log() << "listen(): bind() failed " << OUTPUT_ERRNO << " for port: " << port << endl; + closesocket(sock); + return false; + } + + if ( ::listen(sock, 128) != 0 ) { + log() << "listen(): listen() failed " << OUTPUT_ERRNO << endl; + closesocket(sock); + return false; + } + + return true; + } + + void Listener::listen() { + static long connNumber = 0; + SockAddr from; + while ( 1 ) { + int s = accept(sock, (sockaddr *) &from.sa, &from.addressSize); + if ( s < 0 ) { + if ( errno == ECONNABORTED || errno == EBADF ) { + log() << "Listener on port " << port << " aborted" << endl; + return; + } + log() << "Listener: accept() returns " << s << " " << OUTPUT_ERRNO << endl; + continue; + } + disableNagle(s); + if ( ! cmdLine.quiet ) log() << "connection accepted from " << from.toString() << " #" << ++connNumber << endl; + accepted( new MessagingPort(s, from) ); + } + } + + /* messagingport -------------------------------------------------------------- */ + + class PiggyBackData { + public: + PiggyBackData( MessagingPort * port ) { + _port = port; + _buf = new char[1300]; + _cur = _buf; + } + + ~PiggyBackData() { + flush(); + delete( _cur ); + } + + void append( Message& m ) { + assert( m.data->len <= 1300 ); + + if ( len() + m.data->len > 1300 ) + flush(); + + memcpy( _cur , m.data , m.data->len ); + _cur += m.data->len; + } + + int flush() { + if ( _buf == _cur ) + return 0; + + int x = ::send( _port->sock , _buf , len() , 0 ); + _cur = _buf; + return x; + } + + int len() { + return _cur - _buf; + } + + private: + + MessagingPort* _port; + + char * _buf; + char * _cur; + }; + + class Ports { + set<MessagingPort*>& ports; + boost::mutex& m; + public: + // we "new" this so it is still be around when other automatic global vars + // are being destructed during termination. + Ports() : ports( *(new set<MessagingPort*>()) ), + m( *(new boost::mutex()) ) { } + void closeAll() { \ + boostlock bl(m); + for ( set<MessagingPort*>::iterator i = ports.begin(); i != ports.end(); i++ ) + (*i)->shutdown(); + } + void insert(MessagingPort* p) { + boostlock bl(m); + ports.insert(p); + } + void erase(MessagingPort* p) { + boostlock bl(m); + ports.erase(p); + } + } ports; + + + + void closeAllSockets() { + ports.closeAll(); + } + + MessagingPort::MessagingPort(int _sock, SockAddr& _far) : sock(_sock), piggyBackData(0), farEnd(_far) { + ports.insert(this); + } + + MessagingPort::MessagingPort() { + ports.insert(this); + sock = -1; + piggyBackData = 0; + } + + void MessagingPort::shutdown() { + if ( sock >= 0 ) { + closesocket(sock); + sock = -1; + } + } + + MessagingPort::~MessagingPort() { + if ( piggyBackData ) + delete( piggyBackData ); + shutdown(); + ports.erase(this); + } + + class ConnectBG : public BackgroundJob { + public: + int sock; + int res; + SockAddr farEnd; + void run() { + res = ::connect(sock, (sockaddr *) &farEnd.sa, farEnd.addressSize); + } + }; + + bool MessagingPort::connect(SockAddr& _far) + { + farEnd = _far; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if ( sock == INVALID_SOCKET ) { + log() << "ERROR: connect(): invalid socket? " << OUTPUT_ERRNO << endl; + return false; + } + +#if 0 + long fl = fcntl(sock, F_GETFL, 0); + assert( fl >= 0 ); + fl |= O_NONBLOCK; + fcntl(sock, F_SETFL, fl); + + int res = ::connect(sock, (sockaddr *) &farEnd.sa, farEnd.addressSize); + if ( res ) { + if ( errno == EINPROGRESS ) + closesocket(sock); + sock = -1; + return false; + } + +#endif + + ConnectBG bg; + bg.sock = sock; + bg.farEnd = farEnd; + bg.go(); + + // int res = ::connect(sock, (sockaddr *) &farEnd.sa, farEnd.addressSize); + if ( bg.wait(5000) ) { + if ( bg.res ) { + closesocket(sock); + sock = -1; + return false; + } + } + else { + // time out the connect + closesocket(sock); + sock = -1; + bg.wait(); // so bg stays in scope until bg thread terminates + return false; + } + + disableNagle(sock); + +#ifdef SO_NOSIGPIPE + // osx + const int one = 1; + setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(int)); +#endif + + return true; + } + + bool MessagingPort::recv(Message& m) { +again: + mmm( out() << "* recv() sock:" << this->sock << endl; ) + int len = -1; + + char *lenbuf = (char *) &len; + int lft = 4; + while ( 1 ) { + int x = ::recv(sock, lenbuf, lft, 0); + if ( x == 0 ) { + DEV out() << "MessagingPort recv() conn closed? " << farEnd.toString() << endl; + m.reset(); + return false; + } + if ( x < 0 ) { + log() << "MessagingPort recv() " << OUTPUT_ERRNO << " " << farEnd.toString()<<endl; + m.reset(); + return false; + } + lft -= x; + if ( lft == 0 ) + break; + lenbuf += x; + log() << "MessagingPort recv() got " << x << " bytes wanted 4, lft=" << lft << endl; + assert( lft > 0 ); + } + + if ( len < 0 || len > 16000000 ) { + if ( len == -1 ) { + // Endian check from the database, after connecting, to see what mode server is running in. + unsigned foo = 0x10203040; + int x = ::send(sock, (char *) &foo, 4, portSendFlags ); + if ( x <= 0 ) { + log() << "MessagingPort endian send() " << OUTPUT_ERRNO << ' ' << farEnd.toString() << endl; + return false; + } + goto again; + } + + if ( len == 542393671 ){ + // an http GET + log() << "looks like you're trying to access db over http on native driver port. please add 1000 for webserver" << endl; + string msg = "You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number\n"; + stringstream ss; + ss << "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: " << msg.size() << "\r\n\r\n" << msg; + string s = ss.str(); + ::send( sock , s.c_str(), s.size(), 0 ); + return false; + } + log() << "bad recv() len: " << len << '\n'; + return false; + } + + int z = (len+1023)&0xfffffc00; + assert(z>=len); + MsgData *md = (MsgData *) malloc(z); + md->len = len; + + if ( len <= 0 ) { + out() << "got a length of " << len << ", something is wrong" << endl; + return false; + } + + char *p = (char *) &md->id; + int left = len -4; + while ( 1 ) { + int x = ::recv(sock, p, left, 0); + if ( x == 0 ) { + DEV out() << "MessagingPort::recv(): conn closed? " << farEnd.toString() << endl; + m.reset(); + return false; + } + if ( x < 0 ) { + log() << "MessagingPort recv() " << OUTPUT_ERRNO << ' ' << farEnd.toString() << endl; + m.reset(); + return false; + } + left -= x; + p += x; + if ( left <= 0 ) + break; + } + + m.setData(md, true); + return true; + } + + void MessagingPort::reply(Message& received, Message& response) { + say(/*received.from, */response, received.data->id); + } + + void MessagingPort::reply(Message& received, Message& response, MSGID responseTo) { + say(/*received.from, */response, responseTo); + } + + bool MessagingPort::call(Message& toSend, Message& response) { + mmm( out() << "*call()" << endl; ) + MSGID old = toSend.data->id; + say(/*to,*/ toSend); + while ( 1 ) { + bool ok = recv(response); + if ( !ok ) + return false; + //out() << "got response: " << response.data->responseTo << endl; + if ( response.data->responseTo == toSend.data->id ) + break; + out() << "********************" << endl; + out() << "ERROR: MessagingPort::call() wrong id got:" << (unsigned)response.data->responseTo << " expect:" << (unsigned)toSend.data->id << endl; + out() << " toSend op: " << toSend.data->operation() << " old id:" << (unsigned)old << endl; + out() << " response msgid:" << (unsigned)response.data->id << endl; + out() << " response len: " << (unsigned)response.data->len << endl; + out() << " response op: " << response.data->operation() << endl; + out() << " farEnd: " << farEnd << endl; + assert(false); + response.reset(); + } + mmm( out() << "*call() end" << endl; ) + return true; + } + + void MessagingPort::say(Message& toSend, int responseTo) { + mmm( out() << "* say() sock:" << this->sock << " thr:" << GetCurrentThreadId() << endl; ) + toSend.data->id = nextMessageId(); + toSend.data->responseTo = responseTo; + + int x = -100; + + if ( piggyBackData && piggyBackData->len() ) { + mmm( out() << "* have piggy back" << endl; ) + if ( ( piggyBackData->len() + toSend.data->len ) > 1300 ) { + // won't fit in a packet - so just send it off + piggyBackData->flush(); + } + else { + piggyBackData->append( toSend ); + x = piggyBackData->flush(); + } + } + + if ( x == -100 ) + x = ::send(sock, (char*)toSend.data, toSend.data->len , portSendFlags ); + + if ( x <= 0 ) { + log() << "MessagingPort say send() " << OUTPUT_ERRNO << ' ' << farEnd.toString() << endl; + throw SocketException(); + } + + } + + void MessagingPort::piggyBack( Message& toSend , int responseTo ) { + + if ( toSend.data->len > 1300 ) { + // not worth saving because its almost an entire packet + say( toSend ); + return; + } + + // we're going to be storing this, so need to set it up + toSend.data->id = nextMessageId(); + toSend.data->responseTo = responseTo; + + if ( ! piggyBackData ) + piggyBackData = new PiggyBackData( this ); + + piggyBackData->append( toSend ); + } + + unsigned MessagingPort::remotePort(){ + return farEnd.getPort(); + } + + MSGID NextMsgId; + bool usingClientIds = 0; + ThreadLocalValue<int> clientId; + + struct MsgStart { + MsgStart() { + NextMsgId = (((unsigned) time(0)) << 16) ^ curTimeMillis(); + assert(MsgDataHeaderSize == 16); + } + } msgstart; + + MSGID nextMessageId(){ + MSGID msgid = NextMsgId.atomicIncrement(); + + if ( usingClientIds ){ + msgid = msgid & 0xFFFF; + msgid = msgid | clientId.get(); + } + + return msgid; + } + + bool doesOpGetAResponse( int op ){ + return op == dbQuery || op == dbGetMore; + } + + void setClientId( int id ){ + usingClientIds = true; + id = id & 0xFFFF0000; + massert( 10445 , "invalid id" , id ); + clientId.set( id ); + } + + int getClientId(){ + return clientId.get(); + } + +} // namespace mongo diff --git a/util/message.h b/util/message.h new file mode 100644 index 0000000..8d6a46e --- /dev/null +++ b/util/message.h @@ -0,0 +1,207 @@ +// message.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../util/sock.h" + +namespace mongo { + + class Message; + class MessagingPort; + class PiggyBackData; + typedef WrappingInt MSGID; + + class Listener { + public: + Listener(const string &_ip, int p) : ip(_ip), port(p) { } + virtual ~Listener() {} + bool init(); // set up socket + int socket() const { return sock; } + void listen(); // never returns (start a thread) + + /* spawn a thread, etc., then return */ + virtual void accepted(MessagingPort *mp) = 0; + private: + string ip; + int port; + int sock; + }; + + class AbstractMessagingPort { + public: + virtual ~AbstractMessagingPort() { } + virtual void reply(Message& received, Message& response, MSGID responseTo) = 0; // like the reply below, but doesn't rely on received.data still being available + virtual void reply(Message& received, Message& response) = 0; + + virtual unsigned remotePort() = 0 ; + }; + + class MessagingPort : public AbstractMessagingPort { + public: + MessagingPort(int sock, SockAddr& farEnd); + MessagingPort(); + virtual ~MessagingPort(); + + void shutdown(); + + bool connect(SockAddr& farEnd); + + /* it's assumed if you reuse a message object, that it doesn't cross MessagingPort's. + also, the Message data will go out of scope on the subsequent recv call. + */ + bool recv(Message& m); + void reply(Message& received, Message& response, MSGID responseTo); + void reply(Message& received, Message& response); + bool call(Message& toSend, Message& response); + void say(Message& toSend, int responseTo = -1); + + void piggyBack( Message& toSend , int responseTo = -1 ); + + virtual unsigned remotePort(); + private: + int sock; + PiggyBackData * piggyBackData; + public: + SockAddr farEnd; + + friend class PiggyBackData; + }; + + //#pragma pack() +#pragma pack(1) + + enum Operations { + opReply = 1, /* reply. responseTo is set. */ + dbMsg = 1000, /* generic msg command followed by a string */ + dbUpdate = 2001, /* update object */ + dbInsert = 2002, + //dbGetByOID = 2003, + dbQuery = 2004, + dbGetMore = 2005, + dbDelete = 2006, + dbKillCursors = 2007 + }; + + bool doesOpGetAResponse( int op ); + + struct MsgData { + int len; /* len of the msg, including this field */ + MSGID id; /* request/reply id's match... */ + MSGID responseTo; /* id of the message we are responding to */ + int _operation; + int operation() const { + return _operation; + } + void setOperation(int o) { + _operation = o; + } + char _data[4]; + + int& dataAsInt() { + return *((int *) _data); + } + + bool valid(){ + if ( len <= 0 || len > ( 1024 * 1024 * 10 ) ) + return false; + if ( _operation < 0 || _operation > 100000 ) + return false; + return true; + } + + int dataLen(); // len without header + }; + const int MsgDataHeaderSize = sizeof(MsgData) - 4; + inline int MsgData::dataLen() { + return len - MsgDataHeaderSize; + } + +#pragma pack() + + class Message { + public: + Message() { + data = 0; + freeIt = false; + } + Message( void * _data , bool _freeIt ) { + data = (MsgData*)_data; + freeIt = _freeIt; + }; + ~Message() { + reset(); + } + + SockAddr from; + MsgData *data; + + Message& operator=(Message& r) { + assert( data == 0 ); + data = r.data; + assert( r.freeIt ); + r.freeIt = false; + r.data = 0; + freeIt = true; + return *this; + } + + void reset() { + if ( freeIt && data ) + free(data); + data = 0; + freeIt = false; + } + + void setData(MsgData *d, bool _freeIt) { + assert( data == 0 ); + freeIt = _freeIt; + data = d; + } + void setData(int operation, const char *msgtxt) { + setData(operation, msgtxt, strlen(msgtxt)+1); + } + void setData(int operation, const char *msgdata, int len) { + assert(data == 0); + int dataLen = len + sizeof(MsgData) - 4; + MsgData *d = (MsgData *) malloc(dataLen); + memcpy(d->_data, msgdata, len); + d->len = fixEndian(dataLen); + d->setOperation(operation); + freeIt= true; + data = d; + } + + bool doIFreeIt() { + return freeIt; + } + + private: + bool freeIt; + }; + + class SocketException : public DBException { + public: + virtual const char* what() const throw() { return "socket exception"; } + virtual int getCode(){ return 9001; } + }; + + MSGID nextMessageId(); + + void setClientId( int id ); + int getClientId(); +} // namespace mongo diff --git a/util/message_server.h b/util/message_server.h new file mode 100644 index 0000000..cc40b76 --- /dev/null +++ b/util/message_server.h @@ -0,0 +1,49 @@ +// message_server.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + abstract database server + async io core, worker thread system + */ + +#pragma once + +#include "../stdafx.h" + +namespace mongo { + + class MessageHandler { + public: + virtual ~MessageHandler(){} + virtual void process( Message& m , AbstractMessagingPort* p ) = 0; + }; + + class MessageServer { + public: + MessageServer( int port , MessageHandler * handler ) : _port( port ) , _handler( handler ){} + virtual ~MessageServer(){} + + virtual void run() = 0; + + protected: + + int _port; + MessageHandler* _handler; + }; + + MessageServer * createServer( int port , MessageHandler * handler ); +} diff --git a/util/message_server_asio.cpp b/util/message_server_asio.cpp new file mode 100644 index 0000000..4d5fab0 --- /dev/null +++ b/util/message_server_asio.cpp @@ -0,0 +1,191 @@ +// message_server_asio.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef USE_ASIO + +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/shared_ptr.hpp> + +#include <iostream> +#include <vector> + +#include "message.h" +#include "message_server.h" +#include "../util/thread_pool.h" + +using namespace boost; +using namespace boost::asio; +using namespace boost::asio::ip; +//using namespace std; + +namespace mongo { + namespace { + ThreadPool tp; + } + + class MessageServerSession : public boost::enable_shared_from_this<MessageServerSession> , public AbstractMessagingPort { + public: + MessageServerSession( MessageHandler * handler , io_service& ioservice ) : _handler( handler ) , _socket( ioservice ){ + + } + ~MessageServerSession(){ + cout << "disconnect from: " << _socket.remote_endpoint() << endl; + } + + tcp::socket& socket(){ + return _socket; + } + + void start(){ + cout << "MessageServerSession start from:" << _socket.remote_endpoint() << endl; + _startHeaderRead(); + } + + void handleReadHeader( const boost::system::error_code& error ){ + if ( _inHeader.len == 0 ) + return; + + if ( ! _inHeader.valid() ){ + cout << " got invalid header from: " << _socket.remote_endpoint() << " closing connected" << endl; + return; + } + + char * raw = (char*)malloc( _inHeader.len ); + + MsgData * data = (MsgData*)raw; + memcpy( data , &_inHeader , sizeof( _inHeader ) ); + assert( data->len == _inHeader.len ); + + uassert( 10273 , "_cur not empty! pipelining requests not supported" , ! _cur.data ); + + _cur.setData( data , true ); + async_read( _socket , + buffer( raw + sizeof( _inHeader ) , _inHeader.len - sizeof( _inHeader ) ) , + boost::bind( &MessageServerSession::handleReadBody , shared_from_this() , boost::asio::placeholders::error ) ); + } + + void handleReadBody( const boost::system::error_code& error ){ + tp.schedule(&MessageServerSession::process, shared_from_this()); + } + + void process(){ + _handler->process( _cur , this ); + + if (_reply.data){ + async_write( _socket , + buffer( (char*)_reply.data , _reply.data->len ) , + boost::bind( &MessageServerSession::handleWriteDone , shared_from_this() , boost::asio::placeholders::error ) ); + } else { + _cur.reset(); + _startHeaderRead(); + } + } + + void handleWriteDone( const boost::system::error_code& error ){ + _cur.reset(); + _reply.reset(); + _startHeaderRead(); + } + + virtual void reply( Message& received, Message& response ){ + reply( received , response , received.data->id ); + } + + virtual void reply( Message& query , Message& toSend, MSGID responseTo ){ + _reply = toSend; + + _reply.data->id = nextMessageId(); + _reply.data->responseTo = responseTo; + uassert( 10274 , "pipelining requests doesn't work yet" , query.data->id == _cur.data->id ); + } + + + virtual unsigned remotePort(){ + return _socket.remote_endpoint().port(); + } + + private: + + void _startHeaderRead(){ + _inHeader.len = 0; + async_read( _socket , + buffer( &_inHeader , sizeof( _inHeader ) ) , + boost::bind( &MessageServerSession::handleReadHeader , shared_from_this() , boost::asio::placeholders::error ) ); + } + + MessageHandler * _handler; + tcp::socket _socket; + MsgData _inHeader; + Message _cur; + Message _reply; + }; + + + class AsyncMessageServer : public MessageServer { + public: + AsyncMessageServer( int port , MessageHandler * handler ) + : MessageServer( port , handler ) + , _endpoint( tcp::v4() , port ) + , _acceptor( _ioservice , _endpoint ) + { + _accept(); + } + virtual ~AsyncMessageServer(){ + + } + + void run(){ + cout << "AsyncMessageServer starting to listen on: " << _port << endl; + _ioservice.run(); + cout << "AsyncMessageServer done listening on: " << _port << endl; + } + + void handleAccept( shared_ptr<MessageServerSession> session , + const boost::system::error_code& error ){ + if ( error ){ + cout << "handleAccept error!" << endl; + return; + } + session->start(); + _accept(); + } + + void _accept(){ + shared_ptr<MessageServerSession> session( new MessageServerSession( _handler , _ioservice ) ); + _acceptor.async_accept( session->socket() , + boost::bind( &AsyncMessageServer::handleAccept, + this, + session, + boost::asio::placeholders::error ) + ); + } + + private: + io_service _ioservice; + tcp::endpoint _endpoint; + tcp::acceptor _acceptor; + }; + + MessageServer * createServer( int port , MessageHandler * handler ){ + return new AsyncMessageServer( port , handler ); + } + +} + +#endif diff --git a/util/message_server_port.cpp b/util/message_server_port.cpp new file mode 100644 index 0000000..e5becc9 --- /dev/null +++ b/util/message_server_port.cpp @@ -0,0 +1,90 @@ +// message_server_port.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USE_ASIO + +#include "message.h" +#include "message_server.h" + +namespace mongo { + + namespace pms { + + MessagingPort * grab = 0; + MessageHandler * handler; + + void threadRun(){ + assert( grab ); + MessagingPort * p = grab; + grab = 0; + + Message m; + try { + while ( 1 ){ + m.reset(); + + if ( ! p->recv(m) ) { + log() << "end connection " << p->farEnd.toString() << endl; + p->shutdown(); + break; + } + + handler->process( m , p ); + } + } + catch ( ... ){ + problem() << "uncaught exception in PortMessageServer::threadRun, closing connection" << endl; + delete p; + } + + } + + } + + class PortMessageServer : public MessageServer , public Listener { + public: + PortMessageServer( int port , MessageHandler * handler ) : + MessageServer( port , handler ) , + Listener( "", port ){ + + uassert( 10275 , "multiple PortMessageServer not supported" , ! pms::handler ); + pms::handler = handler; + } + + virtual void accepted(MessagingPort * p) { + assert( ! pms::grab ); + pms::grab = p; + boost::thread thr( pms::threadRun ); + while ( pms::grab ) + sleepmillis(1); + } + + void run(){ + assert( init() ); + listen(); + } + + }; + + + MessageServer * createServer( int port , MessageHandler * handler ){ + return new PortMessageServer( port , handler ); + } + +} + +#endif diff --git a/util/miniwebserver.cpp b/util/miniwebserver.cpp new file mode 100644 index 0000000..b492153 --- /dev/null +++ b/util/miniwebserver.cpp @@ -0,0 +1,224 @@ +// miniwebserver.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "miniwebserver.h" + +#include <pcrecpp.h> + +namespace mongo { + + MiniWebServer::MiniWebServer() { + sock = 0; + } + + bool MiniWebServer::init(const string &ip, int _port) { + port = _port; + SockAddr me; + if ( ip.empty() ) + me = SockAddr( port ); + else + me = SockAddr( ip.c_str(), port ); + sock = ::socket(AF_INET, SOCK_STREAM, 0); + if ( sock == INVALID_SOCKET ) { + log() << "ERROR: MiniWebServer listen(): invalid socket? " << OUTPUT_ERRNO << endl; + return false; + } + prebindOptions( sock ); + if ( ::bind(sock, (sockaddr *) &me.sa, me.addressSize) != 0 ) { + log() << "MiniWebServer: bind() failed port:" << port << " " << OUTPUT_ERRNO << endl; + if ( errno == EADDRINUSE ) + log() << " addr already in use" << endl; + closesocket(sock); + return false; + } + + if ( ::listen(sock, 16) != 0 ) { + log() << "MiniWebServer: listen() failed " << OUTPUT_ERRNO << endl; + closesocket(sock); + return false; + } + + return true; + } + + string MiniWebServer::parseURL( const char * buf ) { + const char * urlStart = strstr( buf , " " ); + if ( ! urlStart ) + return "/"; + + urlStart++; + + const char * end = strstr( urlStart , " " ); + if ( ! end ) { + end = strstr( urlStart , "\r" ); + if ( ! end ) { + end = strstr( urlStart , "\n" ); + } + } + + if ( ! end ) + return "/"; + + int diff = (int)(end-urlStart); + if ( diff < 0 || diff > 255 ) + return "/"; + + return string( urlStart , (int)(end-urlStart) ); + } + + void MiniWebServer::parseParams( map<string,string> & params , string query ) { + if ( query.size() == 0 ) + return; + + while ( query.size() ) { + + string::size_type amp = query.find( "&" ); + + string cur; + if ( amp == string::npos ) { + cur = query; + query = ""; + } + else { + cur = query.substr( 0 , amp ); + query = query.substr( amp + 1 ); + } + + string::size_type eq = cur.find( "=" ); + if ( eq == string::npos ) + continue; + + params[cur.substr(0,eq)] = cur.substr(eq+1); + } + return; + } + + string MiniWebServer::parseMethod( const char * headers ) { + const char * end = strstr( headers , " " ); + if ( ! end ) + return "GET"; + return string( headers , (int)(end-headers) ); + } + + const char *MiniWebServer::body( const char *buf ) { + const char *ret = strstr( buf, "\r\n\r\n" ); + return ret ? ret + 4 : ret; + } + + bool MiniWebServer::fullReceive( const char *buf ) { + const char *bod = body( buf ); + if ( !bod ) + return false; + const char *lenString = "Content-Length:"; + const char *lengthLoc = strstr( buf, lenString ); + if ( !lengthLoc ) + return true; + lengthLoc += strlen( lenString ); + long len = strtol( lengthLoc, 0, 10 ); + if ( long( strlen( bod ) ) == len ) + return true; + return false; + } + + void MiniWebServer::accepted(int s, const SockAddr &from) { + char buf[4096]; + int len = 0; + while ( 1 ) { + int x = ::recv(s, buf + len, sizeof(buf) - 1 - len, 0); + if ( x <= 0 ) { + return; + } + len += x; + buf[ len ] = 0; + if ( fullReceive( buf ) ) + break; + } + buf[len] = 0; + + string responseMsg; + int responseCode = 599; + vector<string> headers; + + try { + doRequest(buf, parseURL( buf ), responseMsg, responseCode, headers, from); + } + catch ( std::exception& e ){ + responseCode = 500; + responseMsg = "error loading page: "; + responseMsg += e.what(); + } + catch ( ... ){ + responseCode = 500; + responseMsg = "unknown error loading page"; + } + + stringstream ss; + ss << "HTTP/1.0 " << responseCode; + if ( responseCode == 200 ) ss << " OK"; + ss << "\r\n"; + if ( headers.empty() ) { + ss << "Content-Type: text/html\r\n"; + } + else { + for ( vector<string>::iterator i = headers.begin(); i != headers.end(); i++ ) + ss << *i << "\r\n"; + } + ss << "\r\n"; + ss << responseMsg; + string response = ss.str(); + + ::send(s, response.c_str(), response.size(), 0); + } + + string MiniWebServer::getHeader( const char * req , string wanted ){ + const char * headers = strstr( req , "\n" ); + if ( ! headers ) + return ""; + pcrecpp::StringPiece input( headers + 1 ); + + string name; + string val; + pcrecpp::RE re("([\\w\\-]+): (.*?)\r?\n"); + while ( re.Consume( &input, &name, &val) ){ + if ( name == wanted ) + return val; + } + return ""; + } + + void MiniWebServer::run() { + SockAddr from; + while ( 1 ) { + int s = accept(sock, (sockaddr *) &from.sa, &from.addressSize); + if ( s < 0 ) { + if ( errno == ECONNABORTED ) { + log() << "Listener on port " << port << " aborted." << endl; + return; + } + log() << "MiniWebServer: accept() returns " << s << " " << OUTPUT_ERRNO << endl; + sleepmillis(200); + continue; + } + disableNagle(s); + RARELY log() << "MiniWebServer: connection accepted from " << from.toString() << endl; + accepted( s, from ); + closesocket(s); + } + } + +} // namespace mongo diff --git a/util/miniwebserver.h b/util/miniwebserver.h new file mode 100644 index 0000000..27476d6 --- /dev/null +++ b/util/miniwebserver.h @@ -0,0 +1,59 @@ +// miniwebserver.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "message.h" + +namespace mongo { + + class MiniWebServer { + public: + MiniWebServer(); + virtual ~MiniWebServer() {} + + bool init(const string &ip, int _port); + void run(); + + virtual void doRequest( + const char *rq, // the full request + string url, + // set these and return them: + string& responseMsg, + int& responseCode, + vector<string>& headers, // if completely empty, content-type: text/html will be added + const SockAddr &from + ) = 0; + + int socket() const { return sock; } + + protected: + string parseURL( const char * buf ); + string parseMethod( const char * headers ); + string getHeader( const char * headers , string name ); + void parseParams( map<string,string> & params , string query ); + static const char *body( const char *buf ); + + private: + void accepted(int s, const SockAddr &from); + static bool fullReceive( const char *buf ); + + int port; + int sock; + }; + +} // namespace mongo diff --git a/util/mmap.cpp b/util/mmap.cpp new file mode 100644 index 0000000..f3103d0 --- /dev/null +++ b/util/mmap.cpp @@ -0,0 +1,95 @@ +// mmap.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "mmap.h" + +namespace mongo { + + set<MemoryMappedFile*> mmfiles; + boost::mutex mmmutex; + + MemoryMappedFile::~MemoryMappedFile() { + close(); + boostlock lk( mmmutex ); + mmfiles.erase(this); + } + + void MemoryMappedFile::created(){ + boostlock lk( mmmutex ); + mmfiles.insert(this); + } + + /*static*/ + int closingAllFiles = 0; + void MemoryMappedFile::closeAllFiles( stringstream &message ) { + if ( closingAllFiles ) { + message << "warning closingAllFiles=" << closingAllFiles << endl; + return; + } + ++closingAllFiles; + ProgressMeter pm( mmfiles.size() , 2 , 1 ); + for ( set<MemoryMappedFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ){ + (*i)->close(); + pm.hit(); + } + message << " closeAllFiles() finished" << endl; + --closingAllFiles; + } + + long long MemoryMappedFile::totalMappedLength(){ + unsigned long long total = 0; + + boostlock lk( mmmutex ); + for ( set<MemoryMappedFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) + total += (*i)->length(); + + return total; + } + + int MemoryMappedFile::flushAll( bool sync ){ + int num = 0; + + boostlock lk( mmmutex ); + for ( set<MemoryMappedFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ){ + num++; + MemoryMappedFile * mmf = *i; + if ( ! mmf ) + continue; + mmf->flush( sync ); + } + return num; + } + + + void MemoryMappedFile::updateLength( const char *filename, long &length ) { + if ( !boost::filesystem::exists( filename ) ) + return; + // make sure we map full length if preexisting file. + boost::uintmax_t l = boost::filesystem::file_size( filename ); + assert( l <= 0x7fffffff ); + length = (long) l; + } + + void* MemoryMappedFile::map(const char *filename) { + boost::uintmax_t l = boost::filesystem::file_size( filename ); + assert( l <= 0x7fffffff ); + long i = (long)l; + return map( filename , i ); + } + +} // namespace mongo diff --git a/util/mmap.h b/util/mmap.h new file mode 100644 index 0000000..ed4ca99 --- /dev/null +++ b/util/mmap.h @@ -0,0 +1,63 @@ +// mmap.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + + class MemoryMappedFile { + public: + + MemoryMappedFile(); + ~MemoryMappedFile(); /* closes the file if open */ + void close(); + + // Throws exception if file doesn't exist. + void* map( const char *filename ); + + /* Creates with length if DNE, otherwise uses existing file length, + passed length. + */ + void* map(const char *filename, long &length); + + void flush(bool sync); + + void* viewOfs() { + return view; + } + + long length() { + return len; + } + + static void updateLength( const char *filename, long &length ); + + static long long totalMappedLength(); + static void closeAllFiles( stringstream &message ); + static int flushAll( bool sync ); + + private: + void created(); + + HANDLE fd; + HANDLE maphandle; + void *view; + long len; + }; + + +} // namespace mongo diff --git a/util/mmap_mm.cpp b/util/mmap_mm.cpp new file mode 100644 index 0000000..aa9b275 --- /dev/null +++ b/util/mmap_mm.cpp @@ -0,0 +1,51 @@ +// mmap_mm.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "mmap.h" + +/* in memory (no file) version */ + +namespace mongo { + + MemoryMappedFile::MemoryMappedFile() { + fd = 0; + maphandle = 0; + view = 0; + len = 0; + } + + void MemoryMappedFile::close() { + if ( view ) + delete( view ); + view = 0; + len = 0; + } + + void* MemoryMappedFile::map(const char *filename, size_t length) { + path p( filename ); + + view = malloc( length ); + return view; + } + + void MemoryMappedFile::flush(bool sync) { + } + + +} + diff --git a/util/mmap_posix.cpp b/util/mmap_posix.cpp new file mode 100644 index 0000000..1237220 --- /dev/null +++ b/util/mmap_posix.cpp @@ -0,0 +1,94 @@ +// mmap_posix.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "mmap.h" +#include "file_allocator.h" + +#include <errno.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace mongo { + + MemoryMappedFile::MemoryMappedFile() { + fd = 0; + maphandle = 0; + view = 0; + len = 0; + created(); + } + + void MemoryMappedFile::close() { + if ( view ) + munmap(view, len); + view = 0; + + if ( fd ) + ::close(fd); + fd = 0; + } + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + + void* MemoryMappedFile::map(const char *filename, long &length) { + // length may be updated by callee. + theFileAllocator().allocateAsap( filename, length ); + len = length; + + massert( 10446 , (string)"mmap() can't map area of size 0 [" + filename + "]" , length > 0 ); + + + fd = open(filename, O_RDWR | O_NOATIME); + if ( fd <= 0 ) { + out() << "couldn't open " << filename << ' ' << OUTPUT_ERRNO << endl; + return 0; + } + + off_t filelen = lseek(fd, 0, SEEK_END); + if ( filelen != length ){ + cout << "wanted length: " << length << " filelen: " << filelen << endl; + cout << sizeof(size_t) << endl; + massert( 10447 , "file size allocation failed", filelen == length ); + } + lseek( fd, 0, SEEK_SET ); + + view = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if ( view == MAP_FAILED ) { + out() << " mmap() failed for " << filename << " len:" << length << " " << OUTPUT_ERRNO << endl; + if ( errno == ENOMEM ){ + out() << " mmap failed with out of memory, if you're using 32-bits, then you probably need to upgrade to 64" << endl; + } + return 0; + } + return view; + } + + void MemoryMappedFile::flush(bool sync) { + if ( view == 0 || fd == 0 ) + return; + if ( msync(view, len, sync ? MS_SYNC : MS_ASYNC) ) + problem() << "msync " << OUTPUT_ERRNO << endl; + } + + +} // namespace mongo + diff --git a/util/mmap_win.cpp b/util/mmap_win.cpp new file mode 100644 index 0000000..8a0d306 --- /dev/null +++ b/util/mmap_win.cpp @@ -0,0 +1,101 @@ +// mmap_win.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "mmap.h" +#include <windows.h> + +namespace mongo { + + MemoryMappedFile::MemoryMappedFile() { + fd = 0; + maphandle = 0; + view = 0; + len = 0; + created(); + } + + void MemoryMappedFile::close() { + if ( view ) + UnmapViewOfFile(view); + view = 0; + if ( maphandle ) + CloseHandle(maphandle); + maphandle = 0; + if ( fd ) + CloseHandle(fd); + fd = 0; + } + + std::wstring toWideString(const char *s) { + std::basic_ostringstream<TCHAR> buf; + buf << s; + return buf.str(); + } + + unsigned mapped = 0; + + void* MemoryMappedFile::map(const char *_filename, long &length) { + /* big hack here: Babble uses db names with colons. doesn't seem to work on windows. temporary perhaps. */ + char filename[256]; + strncpy(filename, _filename, 255); + filename[255] = 0; + { + size_t len = strlen( filename ); + for ( size_t i=len-1; i>=0; i-- ){ + if ( filename[i] == '/' || + filename[i] == '\\' ) + break; + + if ( filename[i] == ':' ) + filename[i] = '_'; + } + } + + updateLength( filename, length ); + std::wstring filenamew = toWideString(filename); + + fd = CreateFile( + filenamew.c_str(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if ( fd == INVALID_HANDLE_VALUE ) { + out() << "Create/OpenFile failed " << filename << ' ' << GetLastError() << endl; + return 0; + } + + mapped += length; + + maphandle = CreateFileMapping(fd, NULL, PAGE_READWRITE, 0, length, NULL); + if ( maphandle == NULL ) { + out() << "CreateFileMapping failed " << filename << ' ' << GetLastError() << endl; + return 0; + } + + view = MapViewOfFile(maphandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if ( view == 0 ) { + out() << "MapViewOfFile failed " << filename << " " << OUTPUT_ERRNO << " "; + out() << GetLastError(); + out() << endl; + } + len = length; + return view; + } + + void MemoryMappedFile::flush(bool) { + } + +} diff --git a/util/mvar.h b/util/mvar.h new file mode 100644 index 0000000..7d17051 --- /dev/null +++ b/util/mvar.h @@ -0,0 +1,116 @@ +// mvar.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace mongo { + + /* This is based on haskell's MVar synchronization primitive: + * http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.2.0.0/Control-Concurrent-MVar.html + * + * It is a thread-safe queue that can hold at most one object. + * You can also think of it as a box that can be either full or empty. + */ + + template <typename T> + class MVar { + public: + enum State {EMPTY=0, FULL}; + + // create an empty MVar + MVar() + : _state(EMPTY) + {} + + // creates a full MVar + MVar(const T& val) + : _state(FULL) + , _value(val) + {} + + // puts val into the MVar and returns true or returns false if full + // never blocks + bool tryPut(const T& val){ + // intentionally repeat test before and after lock + if (_state == FULL) return false; + Mutex::scoped_lock lock(_mutex); + if (_state == FULL) return false; + + _state = FULL; + _value = val; + + // unblock threads waiting to 'take' + _condition.notify_all(); + + return true; + } + + // puts val into the MVar + // will block if the MVar is already full + void put(const T& val){ + Mutex::scoped_lock lock(_mutex); + while (!tryPut(val)){ + // unlocks lock while waiting and relocks before returning + _condition.wait(lock); + } + } + + // takes val out of the MVar and returns true or returns false if empty + // never blocks + bool tryTake(T& out){ + // intentionally repeat test before and after lock + if (_state == EMPTY) return false; + Mutex::scoped_lock lock(_mutex); + if (_state == EMPTY) return false; + + _state = EMPTY; + out = _value; + + // unblock threads waiting to 'put' + _condition.notify_all(); + + return true; + } + + // takes val out of the MVar + // will block if the MVar is empty + T take(){ + T ret = T(); + + Mutex::scoped_lock lock(_mutex); + while (!tryTake(ret)){ + // unlocks lock while waiting and relocks before returning + _condition.wait(lock); + } + + return ret; + } + + + // Note: this is fast because there is no locking, but state could + // change before you get a chance to act on it. + // Mainly useful for sanity checks / asserts. + State getState(){ return _state; } + + + private: + State _state; + T _value; + typedef boost::recursive_mutex Mutex; + Mutex _mutex; + boost::condition _condition; + }; + +} diff --git a/util/ntservice.cpp b/util/ntservice.cpp new file mode 100644 index 0000000..602d98a --- /dev/null +++ b/util/ntservice.cpp @@ -0,0 +1,175 @@ +// ntservice.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "ntservice.h" + +#if defined(_WIN32) + +namespace mongo { + + void shutdown(); + + SERVICE_STATUS_HANDLE ServiceController::_statusHandle = null; + std::wstring ServiceController::_serviceName; + ServiceCallback ServiceController::_serviceCallback = null; + + ServiceController::ServiceController() { + } + + bool ServiceController::installService( const std::wstring& serviceName, const std::wstring& displayName, const std::wstring& serviceDesc, int argc, char* argv[] ) { + + std::string commandLine; + + for ( int i = 0; i < argc; i++ ) { + std::string arg( argv[ i ] ); + + // replace install command to indicate process is being started as a service + if ( arg == "--install" ) + arg = "--service"; + + commandLine += arg + " "; + } + + SC_HANDLE schSCManager = ::OpenSCManager( null, null, SC_MANAGER_ALL_ACCESS ); + if ( schSCManager == null ) + return false; + + std::basic_ostringstream< TCHAR > commandLineWide; + commandLineWide << commandLine.c_str(); + + // create new service + SC_HANDLE schService = ::CreateService( schSCManager, serviceName.c_str(), displayName.c_str(), + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, + commandLineWide.str().c_str(), null, null, L"\0\0", null, null ); + + if ( schService == null ) { + ::CloseServiceHandle( schSCManager ); + return false; + } + + SERVICE_DESCRIPTION serviceDescription; + serviceDescription.lpDescription = (LPTSTR)serviceDesc.c_str(); + + // set new service description + bool serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_DESCRIPTION, &serviceDescription ); + + if ( serviceInstalled ) { + SC_ACTION aActions[ 3 ] = { { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 } }; + + SERVICE_FAILURE_ACTIONS serviceFailure; + ZeroMemory( &serviceFailure, sizeof( SERVICE_FAILURE_ACTIONS ) ); + serviceFailure.cActions = 3; + serviceFailure.lpsaActions = aActions; + + // set service recovery options + serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_FAILURE_ACTIONS, &serviceFailure ); + } + + ::CloseServiceHandle( schService ); + ::CloseServiceHandle( schSCManager ); + + return serviceInstalled; + } + + bool ServiceController::removeService( const std::wstring& serviceName ) { + SC_HANDLE schSCManager = ::OpenSCManager( null, null, SC_MANAGER_ALL_ACCESS ); + if ( schSCManager == null ) + return false; + + SC_HANDLE schService = ::OpenService( schSCManager, serviceName.c_str(), SERVICE_ALL_ACCESS ); + + if ( schService == null ) { + ::CloseServiceHandle( schSCManager ); + return false; + } + + SERVICE_STATUS serviceStatus; + + // stop service if running + if ( ::ControlService( schService, SERVICE_CONTROL_STOP, &serviceStatus ) ) { + while ( ::QueryServiceStatus( schService, &serviceStatus ) ) { + if ( serviceStatus.dwCurrentState == SERVICE_STOP_PENDING ) + Sleep( 1000 ); + } + } + + bool serviceRemoved = ::DeleteService( schService ); + + ::CloseServiceHandle( schService ); + ::CloseServiceHandle( schSCManager ); + + return serviceRemoved; + } + + bool ServiceController::startService( const std::wstring& serviceName, ServiceCallback startService ) { + _serviceName = serviceName; + _serviceCallback = startService; + + SERVICE_TABLE_ENTRY dispTable[] = { + { (LPTSTR)serviceName.c_str(), (LPSERVICE_MAIN_FUNCTION)ServiceController::initService }, + { null, null } + }; + + return StartServiceCtrlDispatcher( dispTable ); + } + + bool ServiceController::reportStatus( DWORD reportState, DWORD waitHint ) { + if ( _statusHandle == null ) + return false; + + static DWORD checkPoint = 1; + + SERVICE_STATUS ssStatus; + + ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ssStatus.dwServiceSpecificExitCode = 0; + ssStatus.dwControlsAccepted = reportState == SERVICE_START_PENDING ? 0 : SERVICE_ACCEPT_STOP; + ssStatus.dwCurrentState = reportState; + ssStatus.dwWin32ExitCode = NO_ERROR; + ssStatus.dwWaitHint = waitHint; + ssStatus.dwCheckPoint = ( reportState == SERVICE_RUNNING || reportState == SERVICE_STOPPED ) ? 0 : checkPoint++; + + return SetServiceStatus( _statusHandle, &ssStatus ); + } + + void WINAPI ServiceController::initService( DWORD argc, LPTSTR *argv ) { + _statusHandle = RegisterServiceCtrlHandler( _serviceName.c_str(), serviceCtrl ); + if ( !_statusHandle ) + return; + + reportStatus( SERVICE_START_PENDING, 1000 ); + + _serviceCallback(); + + reportStatus( SERVICE_STOPPED ); + } + + void WINAPI ServiceController::serviceCtrl( DWORD ctrlCode ) { + switch ( ctrlCode ) { + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + shutdown(); + reportStatus( SERVICE_STOPPED ); + return; + } + } + +} // namespace mongo + +#endif diff --git a/util/ntservice.h b/util/ntservice.h new file mode 100644 index 0000000..00b8a0a --- /dev/null +++ b/util/ntservice.h @@ -0,0 +1,48 @@ +// ntservice.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if defined(_WIN32) +#include <windows.h> + +namespace mongo { + + typedef bool ( *ServiceCallback )( void ); + + class ServiceController { + public: + ServiceController(); + virtual ~ServiceController() {} + + static bool installService( const std::wstring& serviceName, const std::wstring& displayName, const std::wstring& serviceDesc, int argc, char* argv[] ); + static bool removeService( const std::wstring& serviceName ); + static bool startService( const std::wstring& serviceName, ServiceCallback startService ); + static bool reportStatus( DWORD reportState, DWORD waitHint = 0 ); + + static void WINAPI initService( DWORD argc, LPTSTR *argv ); + static void WINAPI serviceCtrl( DWORD ctrlCode ); + + protected: + static std::wstring _serviceName; + static SERVICE_STATUS_HANDLE _statusHandle; + static ServiceCallback _serviceCallback; + }; + +} // namespace mongo + +#endif diff --git a/util/optime.h b/util/optime.h new file mode 100644 index 0000000..b7d4f61 --- /dev/null +++ b/util/optime.h @@ -0,0 +1,104 @@ +// optime.h - OpTime class + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../db/concurrency.h" + +namespace mongo { + + /* Operation sequence #. A combination of current second plus an ordinal value. + */ +#pragma pack(4) + class OpTime { + unsigned i; + unsigned secs; + static OpTime last; + public: + unsigned getSecs() const { + return secs; + } + OpTime(Date_t date) { + reinterpret_cast<unsigned long long&>(*this) = date.millis; + } + OpTime(unsigned long long date) { + reinterpret_cast<unsigned long long&>(*this) = date; + } + OpTime(unsigned a, unsigned b) { + secs = a; + i = b; + } + OpTime() { + secs = 0; + i = 0; + } + static OpTime now() { + unsigned t = (unsigned) time(0); +// DEV assertInWriteLock(); + if ( last.secs == t ) { + last.i++; + return last; + } + last = OpTime(t, 1); + return last; + } + + /* We store OpTime's in the database as BSON Date datatype -- we needed some sort of + 64 bit "container" for these values. While these are not really "Dates", that seems a + better choice for now than say, Number, which is floating point. Note the BinData type + is perhaps the cleanest choice, lacking a true unsigned64 datatype, but BinData has 5 + bytes of overhead. + */ + unsigned long long asDate() const { + return *((unsigned long long *) &i); + } + // unsigned long long& asDate() { return *((unsigned long long *) &i); } + + bool isNull() { + return secs == 0; + } + + string toStringLong() const { + char buf[64]; + time_t_to_String(secs, buf); + stringstream ss; + ss << buf << ' '; + ss << hex << secs << ':' << i; + return ss.str(); + } + + string toString() const { + stringstream ss; + ss << hex << secs << ':' << i; + return ss.str(); + } + operator string() const { return toString(); } + bool operator==(const OpTime& r) const { + return i == r.i && secs == r.secs; + } + bool operator!=(const OpTime& r) const { + return !(*this == r); + } + bool operator<(const OpTime& r) const { + if ( secs != r.secs ) + return secs < r.secs; + return i < r.i; + } + }; +#pragma pack() + +} // namespace mongo diff --git a/util/processinfo.h b/util/processinfo.h new file mode 100644 index 0000000..83c3bcf --- /dev/null +++ b/util/processinfo.h @@ -0,0 +1,59 @@ +// processinfo.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <sys/types.h> + +#ifndef _WIN32 +#include <unistd.h> +#else +typedef int pid_t; +int getpid(); +#endif + +namespace mongo { + + class BSONObjBuilder; + + class ProcessInfo { + public: + ProcessInfo( pid_t pid = getpid() ); + ~ProcessInfo(); + + /** + * @return mbytes + */ + int getVirtualMemorySize(); + + /** + * @return mbytes + */ + int getResidentSize(); + + /** + * Append platform-specific data to obj + */ + void getExtraInfo(BSONObjBuilder& info); + + bool supported(); + + private: + pid_t _pid; + }; + +} diff --git a/util/processinfo_darwin.cpp b/util/processinfo_darwin.cpp new file mode 100644 index 0000000..904f967 --- /dev/null +++ b/util/processinfo_darwin.cpp @@ -0,0 +1,95 @@ +// processinfo_darwin.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "processinfo.h" + + + +#include <mach/task_info.h> + +#include <mach/mach_init.h> +#include <mach/mach_host.h> +#include <mach/mach_traps.h> +#include <mach/task.h> +#include <mach/vm_map.h> +#include <mach/shared_memory_server.h> +#include <iostream> + +using namespace std; + +namespace mongo { + + ProcessInfo::ProcessInfo( pid_t pid ) : _pid( pid ){ + } + + ProcessInfo::~ProcessInfo(){ + } + + bool ProcessInfo::supported(){ + return true; + } + + int ProcessInfo::getVirtualMemorySize(){ + task_t result; + + mach_port_t task; + + if ( ( result = task_for_pid( mach_task_self() , _pid , &task) ) != KERN_SUCCESS ){ + cout << "error getting task\n"; + return 0; + } + +#if !defined(__LP64__) + task_basic_info_32 ti; +#else + task_basic_info_64 ti; +#endif + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if ( ( result = task_info( task , TASK_BASIC_INFO , (task_info_t)&ti, &count ) ) != KERN_SUCCESS ){ + cout << "error getting task_info: " << result << endl; + return 0; + } + return (int)((double)ti.virtual_size / (1024.0 * 1024 * 2 ) ); + } + + int ProcessInfo::getResidentSize(){ + task_t result; + + mach_port_t task; + + if ( ( result = task_for_pid( mach_task_self() , _pid , &task) ) != KERN_SUCCESS ){ + cout << "error getting task\n"; + return 0; + } + + +#if !defined(__LP64__) + task_basic_info_32 ti; +#else + task_basic_info_64 ti; +#endif + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if ( ( result = task_info( task , TASK_BASIC_INFO , (task_info_t)&ti, &count ) ) != KERN_SUCCESS ){ + cout << "error getting task_info: " << result << endl; + return 0; + } + return (int)( ti.resident_size / (1024 * 1024 ) ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} + +} diff --git a/util/processinfo_linux2.cpp b/util/processinfo_linux2.cpp new file mode 100644 index 0000000..3e00c06 --- /dev/null +++ b/util/processinfo_linux2.cpp @@ -0,0 +1,215 @@ +// processinfo_linux2.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "processinfo.h" + +#include <iostream> +#include <stdio.h> +#include <malloc.h> +#include <db/jsobj.h> + +using namespace std; + +#define KLONG long +#define KLF "l" + +namespace mongo { + + class LinuxProc { + public: + LinuxProc( pid_t pid = getpid() ){ + char name[128]; + sprintf( name , "/proc/%d/stat" , pid ); + + FILE * f = fopen( name , "r"); + + int found = fscanf(f, + "%d %s %c " + "%d %d %d %d %d " + "%lu %lu %lu %lu %lu " + "%lu %lu %ld %ld " /* utime stime cutime cstime */ + "%ld %ld " + "%ld " + "%ld " + "%lu " /* start_time */ + "%lu " + "%ld " // rss + "%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u " + /* + "%*s %*s %*s %*s " + "%"KLF"u %*lu %*lu " + "%d %d " + "%lu %lu" + */ + + , + + &_pid, + _comm, + &_state, + &_ppid, &_pgrp, &_session, &_tty, &_tpgid, + &_flags, &_min_flt, &_cmin_flt, &_maj_flt, &_cmaj_flt, + &_utime, &_stime, &_cutime, &_cstime, + &_priority, &_nice, + &_alarm, + &_nlwp, + &_start_time, + &_vsize, + &_rss, + &_rss_rlim, &_start_code, &_end_code, &_start_stack, &_kstk_esp, &_kstk_eip + + /* + &_wchan, + &_exit_signal, &_processor, + &_rtprio, &_sched + */ + ); + if ( found == 0 ){ + cout << "system error: reading proc info" << endl; + } + fclose( f ); + } + + unsigned long getVirtualMemorySize(){ + return _vsize; + } + + unsigned long getResidentSize(){ + return (unsigned long)_rss * 4 * 1024; + } + + int _pid; + // The process ID. + + char _comm[128]; + // The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out. + + char _state; + //One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible + // disk sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging. + + int _ppid; + // The PID of the parent. + + int _pgrp; + // The process group ID of the process. + + int _session; + // The session ID of the process. + + int _tty; + // The tty the process uses. + + int _tpgid; + // The process group ID of the process which currently owns the tty that the process is connected to. + + unsigned long _flags; // %lu + // The kernel flags word of the process. For bit meanings, see the PF_* defines in <linux/sched.h>. Details depend on the kernel version. + + unsigned long _min_flt; // %lu + // The number of minor faults the process has made which have not required loading a memory page from disk. + + unsigned long _cmin_flt; // %lu + // The number of minor faults that the process + + unsigned long _maj_flt; // %lu + // The number of major faults the process has made which have required loading a memory page from disk. + + unsigned long _cmaj_flt; // %lu + // The number of major faults that the process + + unsigned long _utime; // %lu + // The number of jiffies that this process has been scheduled in user mode. + + unsigned long _stime; // %lu + // The number of jiffies that this process has been scheduled in kernel mode. + + long _cutime; // %ld + // The number of jiffies that this removed field. + + long _cstime; // %ld + + long _priority; + long _nice; + + long _nlwp; // %ld + // The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. + + unsigned long _alarm; + + unsigned long _start_time; // %lu + // The time in jiffies the process started after system boot. + + unsigned long _vsize; // %lu + // Virtual memory size in bytes. + + long _rss; // %ld + // Resident Set Size: number of pages the process has in real memory, minus 3 for administrative purposes. This is just the pages which + // count towards text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out + + unsigned long _rss_rlim; // %lu + // Current limit in bytes on the rss of the process (usually 4294967295 on i386). + + unsigned long _start_code; // %lu + // The address above which program text can run. + + unsigned long _end_code; // %lu + // The address below which program text can run. + + unsigned long _start_stack; // %lu + // The address of the start of the stack. + + unsigned long _kstk_esp; // %lu + // The current value of esp (stack pointer), as found in the kernel stack page for the process. + + unsigned long _kstk_eip; // %lu + // The current EIP (instruction pointer). + + + + }; + + + ProcessInfo::ProcessInfo( pid_t pid ) : _pid( pid ){ + } + + ProcessInfo::~ProcessInfo(){ + } + + bool ProcessInfo::supported(){ + return true; + } + + int ProcessInfo::getVirtualMemorySize(){ + LinuxProc p(_pid); + return (int)( p.getVirtualMemorySize() / ( 1024.0 * 1024 ) ); + } + + int ProcessInfo::getResidentSize(){ + LinuxProc p(_pid); + return (int)( p.getResidentSize() / ( 1024.0 * 1024 ) ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info){ + struct mallinfo malloc_info = mallinfo(); // structure has same name as function that returns it. (see malloc.h) + info.append("heap_usage_bytes", malloc_info.uordblks); + + LinuxProc p(_pid); + info.append("page_faults", (int)p._maj_flt); + } + +} diff --git a/util/processinfo_none.cpp b/util/processinfo_none.cpp new file mode 100644 index 0000000..57f4ca3 --- /dev/null +++ b/util/processinfo_none.cpp @@ -0,0 +1,46 @@ +// processinfo_none.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "processinfo.h" + +#include <iostream> +using namespace std; + +namespace mongo { + + ProcessInfo::ProcessInfo( pid_t pid ){ + } + + ProcessInfo::~ProcessInfo(){ + } + + bool ProcessInfo::supported(){ + return false; + } + + int ProcessInfo::getVirtualMemorySize(){ + return -1; + } + + int ProcessInfo::getResidentSize(){ + return -1; + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} + +} diff --git a/util/processinfo_win32.cpp b/util/processinfo_win32.cpp new file mode 100644 index 0000000..0f0bf2e --- /dev/null +++ b/util/processinfo_win32.cpp @@ -0,0 +1,64 @@ +// processinfo_win32.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "processinfo.h" + +#include <iostream> + +#include <windows.h> +#include <psapi.h> + +using namespace std; + +int getpid(){ + return GetCurrentProcessId(); +} + +namespace mongo { + + int _wconvertmtos( SIZE_T s ){ + return (int)( s / ( 1024 * 1024 ) ); + } + + ProcessInfo::ProcessInfo( pid_t pid ){ + } + + ProcessInfo::~ProcessInfo(){ + } + + bool ProcessInfo::supported(){ + return true; + } + + int ProcessInfo::getVirtualMemorySize(){ + MEMORYSTATUSEX mse; + mse.dwLength = sizeof(mse); + assert( GlobalMemoryStatusEx( &mse ) ); + DWORDLONG x = (mse.ullTotalVirtual - mse.ullAvailVirtual) / (1024 * 1024) ; + assert( x <= 0x7fffffff ); + return (int) x; + } + + int ProcessInfo::getResidentSize(){ + PROCESS_MEMORY_COUNTERS pmc; + assert( GetProcessMemoryInfo( GetCurrentProcess() , &pmc, sizeof(pmc) ) ); + return _wconvertmtos( pmc.WorkingSetSize ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} +} diff --git a/util/queue.h b/util/queue.h new file mode 100644 index 0000000..8f4fbaf --- /dev/null +++ b/util/queue.h @@ -0,0 +1,72 @@ +// queue.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" +#include "../util/goodies.h" + +#include <queue> + +namespace mongo { + + /** + * simple blocking queue + */ + template<typename T> class BlockingQueue : boost::noncopyable { + public: + void push(T const& t){ + boostlock l( _lock ); + _queue.push( t ); + _condition.notify_one(); + } + + bool empty() const { + boostlock l( _lock ); + return _queue.empty(); + } + + bool tryPop( T & t ){ + boostlock l( _lock ); + if ( _queue.empty() ) + return false; + + t = _queue.front(); + _queue.pop(); + + return true; + } + + T blockingPop(){ + + boostlock l( _lock ); + while( _queue.empty() ) + _condition.wait( l ); + + T t = _queue.front(); + _queue.pop(); + return t; + } + + private: + std::queue<T> _queue; + + mutable boost::mutex _lock; + boost::condition _condition; + }; + +} diff --git a/util/sock.cpp b/util/sock.cpp new file mode 100644 index 0000000..5172692 --- /dev/null +++ b/util/sock.cpp @@ -0,0 +1,209 @@ +// sock.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "sock.h" + +namespace mongo { + + static boost::mutex sock_mutex; + + string hostbyname(const char *hostname) { + static string unknown = "0.0.0.0"; + if ( unknown == hostname ) + return unknown; + + boostlock lk(sock_mutex); +#if defined(_WIN32) + if( inet_addr(hostname) != INADDR_NONE ) + return hostname; +#else + struct in_addr temp; + if ( inet_aton( hostname, &temp ) ) + return hostname; +#endif + struct hostent *h; + h = gethostbyname(hostname); + if ( h == 0 ) return ""; + return inet_ntoa( *((struct in_addr *)(h->h_addr)) ); + } + + void sendtest() { + out() << "sendtest\n"; + SockAddr me(27016); + SockAddr dest("127.0.0.1", 27015); + UDPConnection c; + if ( c.init(me) ) { + char buf[256]; + out() << "sendto: "; + out() << c.sendto(buf, sizeof(buf), dest) << " " << OUTPUT_ERRNO << endl; + } + out() << "end\n"; + } + + void listentest() { + out() << "listentest\n"; + SockAddr me(27015); + SockAddr sender; + UDPConnection c; + if ( c.init(me) ) { + char buf[256]; + out() << "recvfrom: "; + out() << c.recvfrom(buf, sizeof(buf), sender) << " " << OUTPUT_ERRNO << endl; + } + out() << "end listentest\n"; + } + + void xmain(); + struct SockStartupTests { + SockStartupTests() { +#if defined(_WIN32) + WSADATA d; + if ( WSAStartup(MAKEWORD(2,2), &d) != 0 ) { + out() << "ERROR: wsastartup failed " << OUTPUT_ERRNO << endl; + problem() << "ERROR: wsastartup failed " << OUTPUT_ERRNO << endl; + dbexit( EXIT_NTSERVICE_ERROR ); + } +#endif + //out() << "ntohl:" << ntohl(256) << endl; + //sendtest(); + //listentest(); + } + } sstests; + +#if 0 + void smain() { + + WSADATA wsaData; + SOCKET RecvSocket; + sockaddr_in RecvAddr; + int Port = 27015; + char RecvBuf[1024]; + int BufLen = 1024; + sockaddr_in SenderAddr; + int SenderAddrSize = sizeof(SenderAddr); + + //----------------------------------------------- + // Initialize Winsock + WSAStartup(MAKEWORD(2,2), &wsaData); + + //----------------------------------------------- + // Create a receiver socket to receive datagrams + RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + prebindOptions( RecvSocket ); + + //----------------------------------------------- + // Bind the socket to any address and the specified port. + RecvAddr.sin_family = AF_INET; + RecvAddr.sin_port = htons(Port); + RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY); + + ::bind(RecvSocket, (SOCKADDR *) &RecvAddr, sizeof(RecvAddr)); + + //----------------------------------------------- + // Call the recvfrom function to receive datagrams + // on the bound socket. + printf("Receiving datagrams...\n"); + recvfrom(RecvSocket, + RecvBuf, + BufLen, + 0, + (SOCKADDR *)&SenderAddr, + &SenderAddrSize); + + //----------------------------------------------- + // Close the socket when finished receiving datagrams + printf("Finished receiving. Closing socket.\n"); + closesocket(RecvSocket); + + //----------------------------------------------- + // Clean up and exit. + printf("Exiting.\n"); + WSACleanup(); + return; + } + + + + + void xmain() { + + WSADATA wsaData; + SOCKET RecvSocket; + sockaddr_in RecvAddr; + int Port = 27015; + char RecvBuf[1024]; + int BufLen = 1024; + sockaddr_in SenderAddr; + int SenderAddrSize = sizeof(SenderAddr); + + //----------------------------------------------- + // Initialize Winsock + WSAStartup(MAKEWORD(2,2), &wsaData); + + //----------------------------------------------- + // Create a receiver socket to receive datagrams + + RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + prebindOptions( RecvSocket ); + + //----------------------------------------------- + // Bind the socket to any address and the specified port. + RecvAddr.sin_family = AF_INET; + RecvAddr.sin_port = htons(Port); + RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY); + + SockAddr a(Port); + ::bind(RecvSocket, (SOCKADDR *) &a.sa, a.addressSize); +// bind(RecvSocket, (SOCKADDR *) &RecvAddr, sizeof(RecvAddr)); + + SockAddr b; + + //----------------------------------------------- + // Call the recvfrom function to receive datagrams + // on the bound socket. + printf("Receiving datagrams...\n"); + recvfrom(RecvSocket, + RecvBuf, + BufLen, + 0, + (SOCKADDR *) &b.sa, &b.addressSize); +// (SOCKADDR *)&SenderAddr, +// &SenderAddrSize); + + //----------------------------------------------- + // Close the socket when finished receiving datagrams + printf("Finished receiving. Closing socket.\n"); + closesocket(RecvSocket); + + //----------------------------------------------- + // Clean up and exit. + printf("Exiting.\n"); + WSACleanup(); + return; + } + +#endif + + ListeningSockets* ListeningSockets::_instance = new ListeningSockets(); + + ListeningSockets* ListeningSockets::get(){ + return _instance; + } + + +} // namespace mongo diff --git a/util/sock.h b/util/sock.h new file mode 100644 index 0000000..5798a71 --- /dev/null +++ b/util/sock.h @@ -0,0 +1,280 @@ +// sock.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../stdafx.h" + +#include <stdio.h> +#include <sstream> +#include "goodies.h" + +#ifdef _WIN32 +#include <windows.h> +#include <winsock.h> +#endif + +namespace mongo { + +#if defined(_WIN32) + + typedef int socklen_t; + inline int getLastError() { + return WSAGetLastError(); + } + inline void disableNagle(int sock) { + int x = 1; + if ( setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &x, sizeof(x)) ) + out() << "ERROR: disableNagle failed" << endl; + } + inline void prebindOptions( int sock ) { + } +#else + +} // namespace mongo + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> + +namespace mongo { + + inline void closesocket(int s) { + close(s); + } + const int INVALID_SOCKET = -1; + typedef int SOCKET; + + inline void disableNagle(int sock) { + int x = 1; + +#ifdef SOL_TCP + int level = SOL_TCP; +#else + int level = SOL_SOCKET; +#endif + + if ( setsockopt(sock, level, TCP_NODELAY, (char *) &x, sizeof(x)) ) + log() << "ERROR: disableNagle failed" << endl; + + } + inline void prebindOptions( int sock ) { + DEV log() << "doing prebind option" << endl; + int x = 1; + if ( setsockopt( sock , SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)) < 0 ) + out() << "Failed to set socket opt, SO_REUSEADDR" << endl; + } + + +#endif + + inline void setSockReceiveTimeout(int sock, int secs) { +// todo - finish - works? + struct timeval tv; + tv.tv_sec = 0;//secs; + tv.tv_usec = 1000; + int rc = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv)); + if ( rc ) { + out() << "ERROR: setsockopt RCVTIMEO failed rc:" << rc << " " << OUTPUT_ERRNO << " secs:" << secs << " sock:" << sock << endl; + } + } + + // If an ip address is passed in, just return that. If a hostname is passed + // in, look up its ip and return that. Returns "" on failure. + string hostbyname(const char *hostname); + + struct SockAddr { + SockAddr() { + addressSize = sizeof(sockaddr_in); + memset(&sa, 0, sizeof(sa)); + } + SockAddr(int sourcePort); /* listener side */ + SockAddr(const char *ip, int port); /* EndPoint (remote) side, or if you want to specify which interface locally */ + + struct sockaddr_in sa; + socklen_t addressSize; + + bool isLocalHost() const { +#if defined(_WIN32) + return sa.sin_addr.S_un.S_addr == 0x100007f; +#else + return sa.sin_addr.s_addr == 0x100007f; +#endif + } + + string toString() const{ + stringstream out; + out << inet_ntoa(sa.sin_addr) << ':' + << ntohs(sa.sin_port); + return out.str(); + } + + operator string() const{ + return toString(); + } + + unsigned getPort() { + return sa.sin_port; + } + + bool localhost() const { return inet_addr( "127.0.0.1" ) == sa.sin_addr.s_addr; } + + bool operator==(const SockAddr& r) const { + return sa.sin_addr.s_addr == r.sa.sin_addr.s_addr && + sa.sin_port == r.sa.sin_port; + } + bool operator!=(const SockAddr& r) const { + return !(*this == r); + } + bool operator<(const SockAddr& r) const { + if ( sa.sin_port >= r.sa.sin_port ) + return false; + return sa.sin_addr.s_addr < r.sa.sin_addr.s_addr; + } + }; + + const int MaxMTU = 16384; + + class UDPConnection { + public: + UDPConnection() { + sock = 0; + } + ~UDPConnection() { + if ( sock ) { + closesocket(sock); + sock = 0; + } + } + bool init(const SockAddr& myAddr); + int recvfrom(char *buf, int len, SockAddr& sender); + int sendto(char *buf, int len, const SockAddr& EndPoint); + int mtu(const SockAddr& sa) { + return sa.isLocalHost() ? 16384 : 1480; + } + + SOCKET sock; + }; + + inline int UDPConnection::recvfrom(char *buf, int len, SockAddr& sender) { + return ::recvfrom(sock, buf, len, 0, (sockaddr *) &sender.sa, &sender.addressSize); + } + + inline int UDPConnection::sendto(char *buf, int len, const SockAddr& EndPoint) { + if ( 0 && rand() < (RAND_MAX>>4) ) { + out() << " NOTSENT "; + // out() << curTimeMillis() << " .TEST: NOT SENDING PACKET" << endl; + return 0; + } + return ::sendto(sock, buf, len, 0, (sockaddr *) &EndPoint.sa, EndPoint.addressSize); + } + + inline bool UDPConnection::init(const SockAddr& myAddr) { + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if ( sock == INVALID_SOCKET ) { + out() << "invalid socket? " << OUTPUT_ERRNO << endl; + return false; + } + //out() << sizeof(sockaddr_in) << ' ' << myAddr.addressSize << endl; + if ( ::bind(sock, (sockaddr *) &myAddr.sa, myAddr.addressSize) != 0 ) { + out() << "udp init failed" << endl; + closesocket(sock); + sock = 0; + return false; + } + socklen_t optLen; + int rcvbuf; + if (getsockopt(sock, + SOL_SOCKET, + SO_RCVBUF, + (char*)&rcvbuf, + &optLen) != -1) + out() << "SO_RCVBUF:" << rcvbuf << endl; + return true; + } + + inline SockAddr::SockAddr(int sourcePort) { + memset(sa.sin_zero, 0, sizeof(sa.sin_zero)); + sa.sin_family = AF_INET; + sa.sin_port = htons(sourcePort); + sa.sin_addr.s_addr = htonl(INADDR_ANY); + addressSize = sizeof(sa); + } + + inline SockAddr::SockAddr(const char * iporhost , int port) { + string ip = hostbyname( iporhost ); + memset(sa.sin_zero, 0, sizeof(sa.sin_zero)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = inet_addr(ip.c_str()); + addressSize = sizeof(sa); + } + + inline string getHostName() { + char buf[256]; + int ec = gethostname(buf, 127); + if ( ec || *buf == 0 ) { + log() << "can't get this server's hostname " << OUTPUT_ERRNO << endl; + return ""; + } + return buf; + } + + class ListeningSockets { + public: + ListeningSockets() : _sockets( new set<int>() ){ + } + + void add( int sock ){ + boostlock lk( _mutex ); + _sockets->insert( sock ); + } + void remove( int sock ){ + boostlock lk( _mutex ); + _sockets->erase( sock ); + } + + void closeAll(){ + set<int>* s; + { + boostlock lk( _mutex ); + s = _sockets; + _sockets = new set<int>(); + } + + for ( set<int>::iterator i=s->begin(); i!=s->end(); i++ ){ + int sock = *i; + log() << "going to close listening socket: " << sock << endl; + closesocket( sock ); + } + + } + + static ListeningSockets* get(); + + private: + boost::mutex _mutex; + set<int>* _sockets; + static ListeningSockets* _instance; + }; + +} // namespace mongo diff --git a/util/thread_pool.cpp b/util/thread_pool.cpp new file mode 100644 index 0000000..b95bc1d --- /dev/null +++ b/util/thread_pool.cpp @@ -0,0 +1,139 @@ +/* threadpool.cpp +*/ + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "thread_pool.h" +#include "mvar.h" + + +namespace mongo{ +namespace threadpool{ + +// Worker thread +class Worker : boost::noncopyable { +public: + explicit Worker(ThreadPool& owner) + : _owner(owner) + , _is_done(true) + , _thread(boost::bind(&Worker::loop, this)) + {} + + // destructor will block until current operation is completed + // Acts as a "join" on this thread + ~Worker(){ + _task.put(Task()); + _thread.join(); + } + + void set_task(Task& func){ + assert(!func.empty()); + assert(_is_done); + _is_done = false; + + _task.put(func); + } + + private: + ThreadPool& _owner; + MVar<Task> _task; + bool _is_done; // only used for error detection + boost::thread _thread; + + void loop(){ + while (true) { + Task task = _task.take(); + if (task.empty()) + break; // ends the thread + + try { + task(); + } catch (std::exception e){ + log() << "Unhandled exception in worker thread: " << e.what() << endl;; + } catch (...){ + log() << "Unhandled non-exception in worker thread" << endl; + } + _is_done = true; + _owner.task_done(this); + } + } +}; + +ThreadPool::ThreadPool(int nThreads) + : _tasksRemaining(0) + , _nThreads(nThreads) +{ + boostlock lock(_mutex); + while (nThreads-- > 0){ + Worker* worker = new Worker(*this); + _freeWorkers.push_front(worker); + } +} + +ThreadPool::~ThreadPool(){ + join(); + + assert(_tasks.empty()); + + // O(n) but n should be small + assert(_freeWorkers.size() == (unsigned)_nThreads); + + while(!_freeWorkers.empty()){ + delete _freeWorkers.front(); + _freeWorkers.pop_front(); + } +} + +void ThreadPool::join(){ + boostlock lock(_mutex); + while(_tasksRemaining){ + _condition.wait(lock); + } +} + +void ThreadPool::schedule(Task task){ + boostlock lock(_mutex); + + _tasksRemaining++; + + if (!_freeWorkers.empty()){ + _freeWorkers.front()->set_task(task); + _freeWorkers.pop_front(); + }else{ + _tasks.push_back(task); + } +} + +// should only be called by a worker from the worker thread +void ThreadPool::task_done(Worker* worker){ + boostlock lock(_mutex); + + if (!_tasks.empty()){ + worker->set_task(_tasks.front()); + _tasks.pop_front(); + }else{ + _freeWorkers.push_front(worker); + } + + _tasksRemaining--; + + if(_tasksRemaining == 0) + _condition.notify_all(); +} + +} //namespace threadpool +} //namespace mongo diff --git a/util/thread_pool.h b/util/thread_pool.h new file mode 100644 index 0000000..91c2969 --- /dev/null +++ b/util/thread_pool.h @@ -0,0 +1,82 @@ +// thread_pool.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <boost/function.hpp> +#include <boost/bind.hpp> +#undef assert +#define assert xassert + +namespace mongo { + +namespace threadpool { + class Worker; + + typedef boost::function<void(void)> Task; //nullary function or functor + + // exported to the mongo namespace + class ThreadPool : boost::noncopyable{ + public: + explicit ThreadPool(int nThreads=8); + + // blocks until all tasks are complete (tasks_remaining() == 0) + // You should not call schedule while in the destructor + ~ThreadPool(); + + // blocks until all tasks are complete (tasks_remaining() == 0) + // does not prevent new tasks from being scheduled so could wait forever. + // Also, new tasks could be scheduled after this returns. + void join(); + + + // task will be copied a few times so make sure it's relatively cheap + void schedule(Task task); + + // Helpers that wrap schedule and boost::bind. + // Functor and args will be copied a few times so make sure it's relatively cheap + template<typename F, typename A> + void schedule(F f, A a){ schedule(boost::bind(f,a)); } + template<typename F, typename A, typename B> + void schedule(F f, A a, B b){ schedule(boost::bind(f,a,b)); } + template<typename F, typename A, typename B, typename C> + void schedule(F f, A a, B b, C c){ schedule(boost::bind(f,a,b,c)); } + template<typename F, typename A, typename B, typename C, typename D> + void schedule(F f, A a, B b, C c, D d){ schedule(boost::bind(f,a,b,c,d)); } + template<typename F, typename A, typename B, typename C, typename D, typename E> + void schedule(F f, A a, B b, C c, D d, E e){ schedule(boost::bind(f,a,b,c,d,e)); } + + + int tasks_remaining() { return _tasksRemaining; } + + private: + boost::mutex _mutex; + boost::condition _condition; + + list<Worker*> _freeWorkers; //used as LIFO stack (always front) + list<Task> _tasks; //used as FIFO queue (push_back, pop_front) + int _tasksRemaining; // in queue + currently processing + int _nThreads; // only used for sanity checking. could be removed in the future. + + // should only be called by a worker from the worker's thread + void task_done(Worker* worker); + friend class Worker; + }; + +} //namespace threadpool + +using threadpool::ThreadPool; + +} //namespace mongo diff --git a/util/top.cpp b/util/top.cpp new file mode 100644 index 0000000..98d9598 --- /dev/null +++ b/util/top.cpp @@ -0,0 +1,18 @@ +// top.cpp + +#include "stdafx.h" +#include "top.h" + +namespace mongo { + + Top::T Top::_snapshotStart = Top::currentTime(); + Top::D Top::_snapshotDuration; + Top::UsageMap Top::_totalUsage; + Top::UsageMap Top::_snapshotA; + Top::UsageMap Top::_snapshotB; + Top::UsageMap &Top::_snapshot = Top::_snapshotA; + Top::UsageMap &Top::_nextSnapshot = Top::_snapshotB; + boost::mutex Top::topMutex; + + +} diff --git a/util/top.h b/util/top.h new file mode 100644 index 0000000..aaf7c3f --- /dev/null +++ b/util/top.h @@ -0,0 +1,183 @@ +// top.h : DB usage monitor. + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <boost/date_time/posix_time/posix_time.hpp> +#undef assert +#define assert xassert + +namespace mongo { + + /* Records per namespace utilization of the mongod process. + No two functions of this class may be called concurrently. + */ + class Top { + typedef boost::posix_time::ptime T; + typedef boost::posix_time::time_duration D; + typedef boost::tuple< D, int, int, int > UsageData; + public: + Top() : _read(false), _write(false) { } + + /* these are used to record activity: */ + + void clientStart( const char *client ) { + clientStop(); + _currentStart = currentTime(); + _current = client; + } + + /* indicate current request is a read operation. */ + void setRead() { _read = true; } + + void setWrite() { _write = true; } + + void clientStop() { + if ( _currentStart == T() ) + return; + D d = currentTime() - _currentStart; + + { + boostlock L(topMutex); + recordUsage( _current, d ); + } + + _currentStart = T(); + _read = false; + _write = false; + } + + /* these are used to fetch the stats: */ + + struct Usage { + string ns; + D time; + double pct; + int reads, writes, calls; + }; + + static void usage( vector< Usage > &res ) { + boostlock L(topMutex); + + // Populate parent namespaces + UsageMap snapshot; + UsageMap totalUsage; + fillParentNamespaces( snapshot, _snapshot ); + fillParentNamespaces( totalUsage, _totalUsage ); + + multimap< D, string, more > sorted; + for( UsageMap::iterator i = snapshot.begin(); i != snapshot.end(); ++i ) + sorted.insert( make_pair( i->second.get<0>(), i->first ) ); + for( multimap< D, string, more >::iterator i = sorted.begin(); i != sorted.end(); ++i ) { + if ( trivialNs( i->second.c_str() ) ) + continue; + Usage u; + u.ns = i->second; + u.time = totalUsage[ u.ns ].get<0>(); + u.pct = _snapshotDuration != D() ? 100.0 * i->first.ticks() / _snapshotDuration.ticks() : 0; + u.reads = snapshot[ u.ns ].get<1>(); + u.writes = snapshot[ u.ns ].get<2>(); + u.calls = snapshot[ u.ns ].get<3>(); + res.push_back( u ); + } + for( UsageMap::iterator i = totalUsage.begin(); i != totalUsage.end(); ++i ) { + if ( snapshot.count( i->first ) != 0 || trivialNs( i->first.c_str() ) ) + continue; + Usage u; + u.ns = i->first; + u.time = i->second.get<0>(); + u.pct = 0; + u.reads = 0; + u.writes = 0; + u.calls = 0; + res.push_back( u ); + } + } + + static void completeSnapshot() { + boostlock L(topMutex); + + if ( &_snapshot == &_snapshotA ) { + _snapshot = _snapshotB; + _nextSnapshot = _snapshotA; + } else { + _snapshot = _snapshotA; + _nextSnapshot = _snapshotB; + } + _snapshotDuration = currentTime() - _snapshotStart; + _snapshotStart = currentTime(); + _nextSnapshot.clear(); + } + + private: + static boost::mutex topMutex; + static bool trivialNs( const char *ns ) { + const char *ret = strrchr( ns, '.' ); + return ret && ret[ 1 ] == '\0'; + } + typedef map<string,UsageData> UsageMap; // duration, # reads, # writes, # total calls + static T currentTime() { + return boost::posix_time::microsec_clock::universal_time(); + } + void recordUsage( const string &client, D duration ) { + recordUsageForMap( _totalUsage, client, duration ); + recordUsageForMap( _nextSnapshot, client, duration ); + } + void recordUsageForMap( UsageMap &map, const string &client, D duration ) { + UsageData& g = map[client]; + g.get< 0 >() += duration; + if ( _read && !_write ) + g.get< 1 >()++; + else if ( !_read && _write ) + g.get< 2 >()++; + g.get< 3 >()++; + } + static void fillParentNamespaces( UsageMap &to, const UsageMap &from ) { + for( UsageMap::const_iterator i = from.begin(); i != from.end(); ++i ) { + string current = i->first; + size_t dot = current.rfind( "." ); + if ( dot == string::npos || dot != current.length() - 1 ) { + inc( to[ current ], i->second ); + } + while( dot != string::npos ) { + current = current.substr( 0, dot ); + inc( to[ current ], i->second ); + dot = current.rfind( "." ); + } + } + } + static void inc( UsageData &to, const UsageData &from ) { + to.get<0>() += from.get<0>(); + to.get<1>() += from.get<1>(); + to.get<2>() += from.get<2>(); + to.get<3>() += from.get<3>(); + } + struct more { bool operator()( const D &a, const D &b ) { return a > b; } }; + string _current; + T _currentStart; + static T _snapshotStart; + static D _snapshotDuration; + static UsageMap _totalUsage; + static UsageMap _snapshotA; + static UsageMap _snapshotB; + static UsageMap &_snapshot; + static UsageMap &_nextSnapshot; + bool _read; + bool _write; + }; + +} // namespace mongo diff --git a/util/unittest.h b/util/unittest.h new file mode 100644 index 0000000..caf8cb3 --- /dev/null +++ b/util/unittest.h @@ -0,0 +1,59 @@ +// unittest.h + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace mongo { + + /* The idea here is to let all initialization of global variables (classes inheriting from UnitTest) + complete before we run the tests -- otherwise order of initilization being arbitrary may mess + us up. The app's main() function should call runTests(). + + To define a unit test, inherit from this and implement run. instantiate one object for the new class + as a global. + */ + struct UnitTest { + UnitTest() { + registerTest(this); + } + virtual ~UnitTest() {} + + // assert if fails + virtual void run() = 0; + + static bool testsInProgress() { return running; } + private: + static vector<UnitTest*> *tests; + static bool running; + public: + static void registerTest(UnitTest *t) { + if ( tests == 0 ) + tests = new vector<UnitTest*>(); + tests->push_back(t); + } + + static void runTests() { + running = true; + for ( vector<UnitTest*>::iterator i = tests->begin(); i != tests->end(); i++ ) { + (*i)->run(); + } + running = false; + } + }; + + +} // namespace mongo diff --git a/util/util.cpp b/util/util.cpp new file mode 100644 index 0000000..78d8d52 --- /dev/null +++ b/util/util.cpp @@ -0,0 +1,137 @@ +// util.cpp + +/* Copyright 2009 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdafx.h" +#include "goodies.h" +#include "unittest.h" +#include "top.h" +#include "file_allocator.h" +#include "optime.h" + +namespace mongo { + + vector<UnitTest*> *UnitTest::tests = 0; + bool UnitTest::running = false; + + Nullstream nullstream; + + thread_specific_ptr<Logstream> Logstream::tsp; + + const char *default_getcurns() { return ""; } + const char * (*getcurns)() = default_getcurns; + + int logLevel = 0; + boost::mutex &Logstream::mutex = *( new boost::mutex ); + int Logstream::doneSetup = Logstream::magicNumber(); + + bool goingAway = false; + + bool isPrime(int n) { + int z = 2; + while ( 1 ) { + if ( z*z > n ) + break; + if ( n % z == 0 ) + return false; + z++; + } + return true; + } + + int nextPrime(int n) { + n |= 1; // 2 goes to 3...don't care... + while ( !isPrime(n) ) + n += 2; + return n; + } + + struct UtilTest : public UnitTest { + void run() { + assert( WrappingInt(0) <= WrappingInt(0) ); + assert( WrappingInt(0) <= WrappingInt(1) ); + assert( !(WrappingInt(1) <= WrappingInt(0)) ); + assert( (WrappingInt(0xf0000000) <= WrappingInt(0)) ); + assert( (WrappingInt(0xf0000000) <= WrappingInt(9000)) ); + assert( !(WrappingInt(300) <= WrappingInt(0xe0000000)) ); + + assert( tdiff(3, 4) == 1 ); + assert( tdiff(4, 3) == -1 ); + assert( tdiff(0xffffffff, 0) == 1 ); + + assert( isPrime(3) ); + assert( isPrime(2) ); + assert( isPrime(13) ); + assert( isPrime(17) ); + assert( !isPrime(9) ); + assert( !isPrime(6) ); + assert( nextPrime(4) == 5 ); + assert( nextPrime(8) == 11 ); + + assert( endsWith("abcde", "de") ); + assert( !endsWith("abcde", "dasdfasdfashkfde") ); + + assert( swapEndian(0x01020304) == 0x04030201 ); + + } + } utilTest; + + // The mutex contained in this object may be held on shutdown. + FileAllocator &theFileAllocator_ = *(new FileAllocator()); + FileAllocator &theFileAllocator() { return theFileAllocator_; } + + OpTime OpTime::last(0, 0); + + /* this is a good place to set a breakpoint when debugging, as lots of warning things + (assert, wassert) call it. + */ + void sayDbContext(const char *errmsg) { + if ( errmsg ) { + problem() << errmsg << endl; + } + printStackTrace(); + } + + void rawOut( const string &s ) { + if( s.empty() ) return; + char now[64]; + time_t_to_String(time(0), now); + now[20] = 0; +#if defined(_WIN32) + (std::cout << now << " " << s).flush(); +#else + assert( write( STDOUT_FILENO, now, 20 ) > 0 ); + assert( write( STDOUT_FILENO, " ", 1 ) > 0 ); + assert( write( STDOUT_FILENO, s.c_str(), s.length() ) > 0 ); + fsync( STDOUT_FILENO ); +#endif + } + +#ifndef _SCONS + // only works in scons + const char * gitVersion(){ return ""; } + const char * sysInfo(){ return ""; } +#endif + + void printGitVersion() { log() << "git version: " << gitVersion() << endl; } + void printSysInfo() { log() << "sys info: " << sysInfo() << endl; } + string mongodVersion() { + stringstream ss; + ss << "db version v" << versionString << ", pdfile version " << VERSION << "." << VERSION_MINOR; + return ss.str(); + } + +} // namespace mongo |