From eae6b90a2acaef42e8c72350c408de0f4401849a Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 4 Sep 2022 13:14:42 +0300 Subject: [PATCH 001/125] Initial import --- CMakeLists.txt | 7 +++++++ main.cpp | 6 ++++++ storage.kdev4 | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 storage.kdev4 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..705e980 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0) + +project(storage) + +add_executable(storage main.cpp) + +install(TARGETS storage RUNTIME DESTINATION bin) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8bb47f1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + std::cout << "Hello, world!" << std::endl; + return 0; +} diff --git a/storage.kdev4 b/storage.kdev4 new file mode 100644 index 0000000..57f15c2 --- /dev/null +++ b/storage.kdev4 @@ -0,0 +1,3 @@ +[Project] +Name=storage +Manager=KDevCMakeManager From fe0ce01f325cbc81ccb113fa0778807e3f9e9f05 Mon Sep 17 00:00:00 2001 From: Blue Date: Sun, 4 Sep 2022 11:08:51 +0000 Subject: [PATCH 002/125] Initial commit --- LICENSE | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 + 2 files changed, 235 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d41c0bd --- /dev/null +++ b/LICENSE @@ -0,0 +1,232 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is 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. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for 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. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +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 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. Use with the GNU Affero General Public License. +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 Affero 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 special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU 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 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 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 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. + + + Copyright (C) + + 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +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 GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/README.md b/README.md new file mode 100644 index 0000000..bce013e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# storage + +Just a temp experiment project for storing stuff \ No newline at end of file From 1b3feb58d0f3a7aa16ad10da5e7f97d711b027f6 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 4 Sep 2022 14:15:31 +0300 Subject: [PATCH 003/125] Initial stuff --- CMakeLists.txt | 40 +++++++- cmake/FindLMDB.cmake | 52 ++++++++++ exception.cpp | 33 +++++++ exception.h | 40 ++++++++ main.cpp | 2 + storage.h | 158 ++++++++++++++++++++++++++++++ storage.hpp | 226 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 548 insertions(+), 3 deletions(-) create mode 100644 cmake/FindLMDB.cmake create mode 100644 exception.cpp create mode 100644 exception.h create mode 100644 storage.h create mode 100644 storage.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 705e980..f462b3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,41 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.16) -project(storage) +project(storage VERSION 0.0.1 LANGUAGES CXX) -add_executable(storage main.cpp) +cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0079 NEW) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +if (NOT DEFINED QT_VERSION_MAJOR) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +endif() +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +qt_standard_project_setup() + +find_package(LMDB REQUIRED) + +# Build type +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif () + +add_executable(storage + main.cpp + exception.cpp +) + + +target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) + +target_link_libraries(storage + PRIVATE + Qt${QT_VERSION_MAJOR}::Core +) +target_link_libraries(storage PRIVATE lmdb) install(TARGETS storage RUNTIME DESTINATION bin) diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake new file mode 100644 index 0000000..d6f2cd3 --- /dev/null +++ b/cmake/FindLMDB.cmake @@ -0,0 +1,52 @@ +#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license +#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code + +# Try to find LMDB headers and library. +# +# Usage of this module as follows: +# +# find_package(LMDB) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# LMDB_ROOT_DIR Set this variable to the root installation of +# LMDB if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# LMDB_FOUND System has LMDB library/headers. +# LMDB_LIBRARIES The LMDB library. +# LMDB_INCLUDE_DIRS The location of LMDB headers. + +find_path(LMDB_ROOT_DIR + NAMES include/lmdb.h + ) + +find_library(LMDB_LIBRARIES + NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible + HINTS ${LMDB_ROOT_DIR}/lib + ) + +add_library(lmdb UNKNOWN IMPORTED) +set_target_properties(lmdb PROPERTIES + IMPORTED_LOCATION ${LMDB_LIBRARIES} + ) + +find_path(LMDB_INCLUDE_DIRS + NAMES lmdb.h + HINTS ${LMDB_ROOT_DIR}/include + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LMDB DEFAULT_MSG + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS + ) + +mark_as_advanced( + LMDB_ROOT_DIR + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS +) diff --git a/exception.cpp b/exception.cpp new file mode 100644 index 0000000..3dee9b3 --- /dev/null +++ b/exception.cpp @@ -0,0 +1,33 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "exception.h" + +Utils::Exception::Exception() +{ +} + +Utils::Exception::~Exception() +{ +} + +const char* Utils::Exception::what() const noexcept( true ) +{ + std::string* msg = new std::string(getMessage()); + return msg->c_str(); +} diff --git a/exception.h b/exception.h new file mode 100644 index 0000000..4c66c2d --- /dev/null +++ b/exception.h @@ -0,0 +1,40 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EXCEPTION_H +#define EXCEPTION_H + +#include +#include + +namespace Utils +{ + class Exception: + public std::exception + { + public: + Exception(); + virtual ~Exception(); + + virtual std::string getMessage() const = 0; + + const char* what() const noexcept( true ); + }; +} + +#endif // EXCEPTION_H diff --git a/main.cpp b/main.cpp index 8bb47f1..5d0f628 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,7 @@ #include +#include + int main(int argc, char **argv) { std::cout << "Hello, world!" << std::endl; return 0; diff --git a/storage.h b/storage.h new file mode 100644 index 0000000..6670f6a --- /dev/null +++ b/storage.h @@ -0,0 +1,158 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CORE_STORAGE_H +#define CORE_STORAGE_H + +#include +#include + + +#include "exception.h" + +namespace Core { + +/** + * @todo write docs + */ +template +class Storage +{ +public: + Storage(const QString& name); + ~Storage(); + + void open(); + void close(); + + void addRecord(const K& key, const V& value); + void changeRecord(const K& key, const V& value); + void removeRecord(const K& key); + V getRecord(const K& key) const; + QString getName() const; + + +private: + QString name; + bool opened; + MDB_env* environment; + MDB_dbi base; + +public: + class Directory: + public Utils::Exception + { + public: + Directory(const std::string& p_path):Exception(), path(p_path){} + + std::string getMessage() const{return "Can't create directory for database at " + path;} + private: + std::string path; + }; + + class Closed: + public Utils::Exception + { + public: + Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} + + std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} + private: + std::string operation; + std::string account; + }; + + class NotFound: + public Utils::Exception + { + public: + NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} + + std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} + private: + std::string key; + std::string account; + }; + + class Empty: + public Utils::Exception + { + public: + Empty(const std::string& acc):Exception(), account(acc){} + + std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} + private: + std::string account; + }; + + class Exist: + public Utils::Exception + { + public: + Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} + + std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} + private: + std::string account; + std::string key; + }; + + class NoAvatar: + public Utils::Exception + { + public: + NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ + if (resource.size() == 0) { + resource = "for himself"; + } + } + + std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} + private: + std::string element; + std::string resource; + }; + + class Unknown: + public Utils::Exception + { + public: + Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} + + std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} + private: + std::string account; + std::string msg; + }; +}; + +} + +MDB_val& operator << (MDB_val& data, QString& value); +MDB_val& operator >> (MDB_val& data, QString& value); + +MDB_val& operator << (MDB_val& data, uint32_t& value); +MDB_val& operator >> (MDB_val& data, uint32_t& value); + +namespace std { + std::string to_string(const QString& str); +} + +#include "storage.hpp" + +#endif // CORE_STORAGE_H diff --git a/storage.hpp b/storage.hpp new file mode 100644 index 0000000..477d70e --- /dev/null +++ b/storage.hpp @@ -0,0 +1,226 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CORE_STORAGE_HPP +#define CORE_STORAGE_HPP + +#include +#include + +#include "storage.h" +#include + +template +Core::Storage::Storage(const QString& p_name): + name(p_name), + opened(false), + environment(), + base() +{ +} + +template +Core::Storage::~Storage() +{ + close(); +} + +template +void Core::Storage::open() +{ + if (!opened) { + mdb_env_create(&environment); + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + name; + QDir cache(path); + + if (!cache.exists()) { + bool res = cache.mkpath(path); + if (!res) { + throw Directory(path.toStdString()); + } + } + + mdb_env_set_maxdbs(environment, 1); + mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL); + mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); + + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + mdb_dbi_open(txn, "base", MDB_CREATE, &base); + mdb_txn_commit(txn); + opened = true; + } +} + +template +void Core::Storage::close() +{ + if (opened) { + mdb_dbi_close(environment, base); + mdb_env_close(environment); + opened = false; + } +} + +template +void Core::Storage::addRecord(const K& key, const V& value) +{ + if (!opened) { + throw Closed("addRecord", name.toStdString()); + } + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << value; + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + lmdbData.mv_size = ba.size(); + lmdbData.mv_data = (uint8_t*)ba.data(); + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc == MDB_KEYEXIST) { + throw Exist(name.toStdString(), std::to_string(key)); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +void Core::Storage::changeRecord(const K& key, const V& value) +{ + if (!opened) { + throw Closed("changeRecord", name.toStdString()); + } + + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << value; + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + lmdbData.mv_size = ba.size(); + lmdbData.mv_data = (uint8_t*)ba.data(); + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc) { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +V Core::Storage::getRecord(const K& key) const +{ + if (!opened) { + throw Closed("addElement", name.toStdString()); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + rc = mdb_get(txn, base, &lmdbKey, &lmdbData); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), name.toStdString()); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); + QDataStream ds(&ba, QIODevice::ReadOnly); + V value; + ds >> value; + mdb_txn_abort(txn); + + return value; + } +} + +template +void Core::Storage::removeRecord(const K& key) +{ + if (!opened) { + throw Closed("addElement", name.toStdString()); + } + + MDB_val lmdbKey; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(environment, NULL, 0, &txn); + rc = mdb_del(txn, base, &lmdbKey, NULL); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), name.toStdString()); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +QString Core::Storage::getName() const { + return name;} + +MDB_val& operator << (MDB_val& data, const QString& value) { + QByteArray ba = value.toUtf8(); + data.mv_size = ba.size(); + data.mv_data = ba.data(); + return data; +} +MDB_val& operator >> (MDB_val& data, QString& value) { + value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); + return data; +} + +MDB_val& operator << (MDB_val& data, uint32_t& value) { + data.mv_size = 4; + data.mv_data = &value; + return data; +} +MDB_val& operator >> (MDB_val& data, uint32_t& value) { + std::memcpy(&value, data.mv_data, data.mv_size); + return data; +} + +std::string std::to_string(const QString& str) { + return str.toStdString(); +} +#endif //CORE_STORAGE_HPP From a3f58bd3c3c27b31052618c5cb5218109c466971 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 4 Sep 2022 14:21:12 +0300 Subject: [PATCH 004/125] removed kdevelop trash --- storage.kdev4 | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 storage.kdev4 diff --git a/storage.kdev4 b/storage.kdev4 deleted file mode 100644 index 57f15c2..0000000 --- a/storage.kdev4 +++ /dev/null @@ -1,3 +0,0 @@ -[Project] -Name=storage -Manager=KDevCMakeManager From c3139974f1b40a9caf8d5e6aabcf9b19e259e13b Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 5 Sep 2022 23:25:39 +0300 Subject: [PATCH 005/125] Temp ideas --- CMakeLists.txt | 2 + database.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++ database.h | 68 +++++++++++++++++++++++++++++++ exceptions.h | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ table.cpp | 25 ++++++++++++ table.h | 52 ++++++++++++++++++++++++ table.hpp | 53 +++++++++++++++++++++++++ 7 files changed, 404 insertions(+) create mode 100644 database.cpp create mode 100644 database.h create mode 100644 exceptions.h create mode 100644 table.cpp create mode 100644 table.h create mode 100644 table.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f462b3b..2c18426 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ endif () add_executable(storage main.cpp exception.cpp + table.cpp + database.cpp ) diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..0e9344c --- /dev/null +++ b/database.cpp @@ -0,0 +1,98 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "database.h" +#include "exceptions.h" +#include "table.h" + +Core::DataBase::DataBase(const QString& p_name, uint16_t mapSize): + name(p_name.toStdString()), + opened(false), + size(mapSize), + environment(), + tables() +{ +} + +Core::DataBase::~DataBase() +{ + close(); + for (const std::pair& pair : tables) { + delete pair.second; + } +} + +void Core::DataBase::close() +{ + if (opened) { + for (const std::pair& pair : tables) { + _Table* table = pair.second; + mdb_dbi_close(environment, table->dbi); + } + mdb_env_close(environment); + opened = false; + } +} + +void Core::DataBase::open() +{ + if (!opened) { + mdb_env_create(&environment); + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + getName(); + QDir cache(path); + + if (!cache.exists()) { + bool res = cache.mkpath(path); + if (!res) { + throw Directory(path.toStdString()); + } + } + + mdb_env_set_maxdbs(environment, tables.size()); + mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); + mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); + + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + + for (const std::pair& pair : tables) { + _Table* table = pair.second; + int rc = mdb_dbi_open(txn, pair.first.c_str(), MDB_CREATE, &table->dbi); + if (rc) { + throw Unknown(name, mdb_strerror(rc)); + } + + } + mdb_txn_commit(txn); + opened = true; + } +} + +QString Core::DataBase::getName() const +{ + return QString::fromStdString(name); +} + +template +Core::DataBase::Table* Core::DataBase::addTable(const QString& p_name) { + if (opened) { + throw Core::DataBase::Opened(name); + } + Core::DataBase::Table* table = new Core::DataBase::Table(); + tables.insert(std::make_pair(p_name.toStdString(), table)); + return table; +} diff --git a/database.h b/database.h new file mode 100644 index 0000000..a407531 --- /dev/null +++ b/database.h @@ -0,0 +1,68 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_DATABASE_H +#define CORE_DATABASE_H + +#include +#include + +#include +#include +#include +#include + +namespace Core { + +class DataBase +{ + class _Table; +public: + template + class Table; + + DataBase(const QString& name, uint16_t mapSize = 10); + ~DataBase(); + + void open(); + void close(); + QString getName() const; + + template + Table* addTable(const QString& name); + +public: + //exceptions + class Directory; + class Closed; + class Opened; + class NotFound; + class Empty; + class Exist; + class NoAvatar; + class Unknown; + +private: + std::string name; + bool opened; + uint16_t size; + MDB_env* environment; + std::map tables; +}; + +} + +#endif // CORE_DATABASE_H diff --git a/exceptions.h b/exceptions.h new file mode 100644 index 0000000..82a0fce --- /dev/null +++ b/exceptions.h @@ -0,0 +1,106 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_DATABASE_EXCEPTIONS_H +#define CORE_DATABASE_EXCEPTIONS_H + +#include "exception.h" +#include "database.h" + +namespace Core { + +class DataBase::Directory: public Utils::Exception { +public: + Directory(const std::string& p_path):Exception(), path(p_path){} + + std::string getMessage() const{return "Can't create directory for database at " + path;} +private: + std::string path; +}; + +class DataBase::Closed : public Utils::Exception { +public: + Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} + + std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} +private: + std::string operation; + std::string account; +}; + +class DataBase::Opened : public Utils::Exception { +public: + Opened(const std::string& tn):Exception(), tableName(tn){} + + std::string getMessage() const{return "An attempt to add table " + tableName + " but it's impossible to do if the DataBase is already opened";} +private: + std::string tableName; +}; + +class DataBase::NotFound : public Utils::Exception { +public: + NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} + + std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} +private: + std::string key; + std::string account; +}; + +class DataBase::Empty : public Utils::Exception { +public: + Empty(const std::string& acc):Exception(), account(acc){} + + std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} +private: + std::string account; +}; + +class DataBase::Exist : public Utils::Exception { +public: + Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} + + std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} +private: + std::string account; + std::string key; +}; + +class DataBase::NoAvatar : public Utils::Exception { +public: + NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ + if (resource.size() == 0) { + resource = "for himself"; + } + } + + std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} +private: + std::string element; + std::string resource; +}; + +class DataBase::Unknown : public Utils::Exception { +public: + Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} + + std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} +private: + std::string account; + std::string msg; +}; +} +#endif //CORE_DATABASE_EXCEPTIONS_H diff --git a/table.cpp b/table.cpp new file mode 100644 index 0000000..adc2265 --- /dev/null +++ b/table.cpp @@ -0,0 +1,25 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "table.h" + +Core::DataBase::_Table::_Table(DataBase* parent): + dbi(), + db(parent) +{ +} + +Core::DataBase::_Table::~_Table() {} diff --git a/table.h b/table.h new file mode 100644 index 0000000..8a39d05 --- /dev/null +++ b/table.h @@ -0,0 +1,52 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_TABLE_H +#define CORE_TABLE_H + +#include "database.h" + +namespace Core { + +class DataBase::_Table { +public: + _Table(DataBase* parent); + virtual ~_Table(); + +public: + MDB_dbi dbi; + DataBase* db; +}; + +template +class DataBase::Table : private DataBase::_Table { + friend class DataBase; +private: + Table(DataBase* parent); + ~Table() override; + +public: + void addRecord(const K& key, const V& value); + void changeRecord(const K& key, const V& value); + void removeRecord(const K& key); + V getRecord(const K& key) const; +}; + +} + +#include "table.hpp" + +#endif // CORE_TABLE_H diff --git a/table.hpp b/table.hpp new file mode 100644 index 0000000..d900940 --- /dev/null +++ b/table.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_TABLE_HPP +#define CORE_TABLE_HPP + +#include "table.h" + + +template +void Core::DataBase::Table::addRecord(const K& key, const V& value) { + if (!db->opened) { + throw Closed("addRecord", db->name); + } + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << value; + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + lmdbData.mv_size = ba.size(); + lmdbData.mv_data = (uint8_t*)ba.data(); + MDB_txn *txn; + mdb_txn_begin(db->environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc == MDB_KEYEXIST) { + throw Exist(db->name, std::to_string(key)); + } else { + throw Unknown(db->name, mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +#endif //CORE_TABLE_HPP From c4917933870994c7738abd3e2420ed249e80f05e Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 9 Sep 2022 20:15:40 +0300 Subject: [PATCH 006/125] some thoughts about exceptions --- CMakeLists.txt | 1 + database.cpp | 7 +-- database.h | 2 - exceptions.cpp | 105 ++++++++++++++++++++++++++++++++++++++++ exceptions.h | 60 ++++++++--------------- main.cpp | 13 ++++- table.cpp | 5 +- table.h | 15 +++++- table.hpp | 127 +++++++++++++++++++++++++++++++++++++++++++++---- 9 files changed, 276 insertions(+), 59 deletions(-) create mode 100644 exceptions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c18426..ea1dcaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ endif () add_executable(storage main.cpp exception.cpp + exceptions.cpp table.cpp database.cpp ) diff --git a/database.cpp b/database.cpp index 0e9344c..0b10e11 100644 --- a/database.cpp +++ b/database.cpp @@ -89,10 +89,11 @@ QString Core::DataBase::getName() const template Core::DataBase::Table* Core::DataBase::addTable(const QString& p_name) { + std::string nm = p_name.toStdString(); if (opened) { - throw Core::DataBase::Opened(name); + throw Core::DataBase::Opened(name, nm); } - Core::DataBase::Table* table = new Core::DataBase::Table(); - tables.insert(std::make_pair(p_name.toStdString(), table)); + Core::DataBase::Table* table = new Core::DataBase::Table(nm, this); + tables.insert(std::make_pair(nm, table)); return table; } diff --git a/database.h b/database.h index a407531..6bb25fd 100644 --- a/database.h +++ b/database.h @@ -50,9 +50,7 @@ public: class Closed; class Opened; class NotFound; - class Empty; class Exist; - class NoAvatar; class Unknown; private: diff --git a/exceptions.cpp b/exceptions.cpp new file mode 100644 index 0000000..0c0c7d5 --- /dev/null +++ b/exceptions.cpp @@ -0,0 +1,105 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "exceptions.h" + +Core::DataBase::Directory::Directory(const std::string& p_path): + Exception(), + path(p_path) {} + +std::string Core::DataBase::Directory::getMessage() const { + return "Can't create directory for database at " + path;} + +Core::DataBase::Closed::Closed( + const std::string& p_operation, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + operation(p_operation), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::Closed::getMessage() const { + return "An attempt to perform operation " + operation + + " on closed database " + dbName + + " on table " + tableName; +} + +Core::DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_tableName): + Exception(), + dbName(p_dbName), + tableName(p_tableName) {} + + +std::string Core::DataBase::Opened::getMessage() const { + return "An attempt to add table " + tableName + + " to the database " + dbName + + " but it's can't be done because the DataBase is already opened." + + " Please add all tables before opening DataBase."; +} + +Core::DataBase::NotFound::NotFound( + const std::string& p_key, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + key(p_key), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::NotFound::getMessage() const { + return "Element for id " + key + " wasn't found " + + " in database " + dbName + + " in table " + tableName;} + +Core::DataBase::Exist::Exist( + const std::string& p_key, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + key(p_key), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string Core::DataBase::Exist::getMessage() const { + return "An attempt to insert element with key " + key + + " to database " + dbName + + " to table " + tableName + + " but it already has an element with given id"; +} + +Core::DataBase::Unknown::Unknown( + const std::string& p_dbName, + const std::string& message, + const std::optional& p_tableName +): + Exception(), + dbName(p_dbName), + tableName(p_tableName), + msg(message) {} + +std::string Core::DataBase::Unknown::getMessage() const +{ + std::string result = "Unknown error in database " + dbName; + if (tableName.has_value()) { + result += " in table " + tableName.value(); + } + result += ": " + msg; + return result; +} diff --git a/exceptions.h b/exceptions.h index 82a0fce..7e2bd34 100644 --- a/exceptions.h +++ b/exceptions.h @@ -24,82 +24,64 @@ namespace Core { class DataBase::Directory: public Utils::Exception { public: - Directory(const std::string& p_path):Exception(), path(p_path){} + Directory(const std::string& path); - std::string getMessage() const{return "Can't create directory for database at " + path;} + std::string getMessage() const; private: std::string path; }; class DataBase::Closed : public Utils::Exception { public: - Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} + Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} + std::string getMessage() const; private: std::string operation; - std::string account; + std::string dbName; + std::string tableName; }; class DataBase::Opened : public Utils::Exception { public: - Opened(const std::string& tn):Exception(), tableName(tn){} + Opened(const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to add table " + tableName + " but it's impossible to do if the DataBase is already opened";} + std::string getMessage() const; private: + std::string dbName; std::string tableName; }; class DataBase::NotFound : public Utils::Exception { public: - NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} + NotFound(const std::string& key, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} + std::string getMessage() const; private: std::string key; - std::string account; -}; - -class DataBase::Empty : public Utils::Exception { -public: - Empty(const std::string& acc):Exception(), account(acc){} - - std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} -private: - std::string account; + std::string dbName; + std::string tableName; }; class DataBase::Exist : public Utils::Exception { public: - Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} + Exist(const std::string& key, const std::string& dbName, const std::string& tableName); - std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} + std::string getMessage() const; private: - std::string account; std::string key; -}; - -class DataBase::NoAvatar : public Utils::Exception { -public: - NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ - if (resource.size() == 0) { - resource = "for himself"; - } - } - - std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} -private: - std::string element; - std::string resource; + std::string dbName; + std::string tableName; }; class DataBase::Unknown : public Utils::Exception { public: - Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} + Unknown(const std::string& dbName, const std::string& message, const std::optional& tableName = std::nullopt); - std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} + std::string getMessage() const; private: - std::string account; + std::string dbName; + std::optional tableName; std::string msg; }; } diff --git a/main.cpp b/main.cpp index 5d0f628..3375807 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,17 @@ #include -#include +#include "database.h" +#include "table.h" int main(int argc, char **argv) { - std::cout << "Hello, world!" << std::endl; + + Core::DataBase base("test1"); + Core::DataBase::Table* table1 = base.addTable("table1"); + Core::DataBase::Table* table2 = base.addTable("table2"); + + base.open(); + + table1->addRecord(1, 2); + return 0; } diff --git a/table.cpp b/table.cpp index adc2265..17b6e03 100644 --- a/table.cpp +++ b/table.cpp @@ -16,9 +16,10 @@ #include "table.h" -Core::DataBase::_Table::_Table(DataBase* parent): +Core::DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): dbi(), - db(parent) + db(parent), + name(p_name) { } diff --git a/table.h b/table.h index 8a39d05..cdde697 100644 --- a/table.h +++ b/table.h @@ -23,19 +23,20 @@ namespace Core { class DataBase::_Table { public: - _Table(DataBase* parent); + _Table(const std::string& name, DataBase* parent); virtual ~_Table(); public: MDB_dbi dbi; DataBase* db; + const std::string name; }; template class DataBase::Table : private DataBase::_Table { friend class DataBase; private: - Table(DataBase* parent); + Table(const std::string& name, DataBase* parent); ~Table() override; public: @@ -47,6 +48,16 @@ public: } +MDB_val& operator << (MDB_val& data, const QString& value); +MDB_val& operator >> (MDB_val& data, QString& value); + +MDB_val& operator << (MDB_val& data, const uint32_t& value); +MDB_val& operator >> (MDB_val& data, uint32_t& value); + +namespace std { + std::string to_string(const QString& str); +} + #include "table.hpp" #endif // CORE_TABLE_H diff --git a/table.hpp b/table.hpp index d900940..8ccbca0 100644 --- a/table.hpp +++ b/table.hpp @@ -18,22 +18,27 @@ #define CORE_TABLE_HPP #include "table.h" +#include "exceptions.h" +template +Core::DataBase::Table::Table(const std::string& p_name, Core::DataBase* parent): + _Table(p_name, parent) +{ +} + +template +Core::DataBase::Table::~Table() { +} template void Core::DataBase::Table::addRecord(const K& key, const V& value) { if (!db->opened) { - throw Closed("addRecord", db->name); + throw Closed("addRecord", db->name, name); } - QByteArray ba; - QDataStream ds(&ba, QIODevice::WriteOnly); - ds << value; MDB_val lmdbKey, lmdbData; lmdbKey << key; - - lmdbData.mv_size = ba.size(); - lmdbData.mv_data = (uint8_t*)ba.data(); + lmdbData << value; MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -41,13 +46,117 @@ void Core::DataBase::Table::addRecord(const K& key, const V& value) { if (rc != 0) { mdb_txn_abort(txn); if (rc == MDB_KEYEXIST) { - throw Exist(db->name, std::to_string(key)); + throw Exist(std::to_string(key), db->name, name); } else { - throw Unknown(db->name, mdb_strerror(rc)); + throw Unknown(db->name, mdb_strerror(rc), name); } } else { mdb_txn_commit(txn); } } + + +template +void Core::DataBase::Table::changeRecord(const K& key, const V& value) { + if (!db->opened) { + throw Closed("changeRecord", db->name, name); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + lmdbData << value; + MDB_txn *txn; + mdb_txn_begin(db->environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + mdb_txn_commit(txn); + } +} + +template +V Core::DataBase::Table::getRecord(const K& key) const { + if (!db->opened) { + throw Closed("getRecord", db->name, name); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), db->name, name); + } else { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + V value; + lmdbData >> value; + mdb_txn_abort(txn); + + return value; + } +} + +template +void Core::DataBase::Table::removeRecord(const K& key) { + if (!db->opened) { + throw Closed("removeRecord", db->name, name); + } + + MDB_val lmdbKey; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(db->environment, NULL, 0, &txn); + rc = mdb_del(txn, dbi, &lmdbKey, NULL); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), db->name, name); + } else { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + mdb_txn_commit(txn); + } +} + +MDB_val& operator << (MDB_val& data, const QString& value) { + QByteArray ba = value.toUtf8(); + data.mv_size = ba.size(); + data.mv_data = ba.data(); + return data; +} +MDB_val& operator >> (MDB_val& data, QString& value) { + value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); + return data; +} + +MDB_val& operator << (MDB_val& data, const uint32_t& value) { + data.mv_size = 4; + data.mv_data = &value; + return data; +} +MDB_val& operator >> (MDB_val& data, uint32_t& value) { + std::memcpy(&value, data.mv_data, data.mv_size); + return data; +} + +std::string std::to_string(const QString& str) { + return str.toStdString(); +} + #endif //CORE_TABLE_HPP From d67a3c32ebac351c71791ae167f45da3f03948e6 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 12 Sep 2022 18:16:18 +0300 Subject: [PATCH 007/125] just some thoughts about serializer --- database.h | 2 ++ main.cpp | 24 +++++++++++++++++ serializer.h | 52 +++++++++++++++++++++++++++++++++++++ serializer.hpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ table.h | 11 ++++---- table.hpp | 42 ++++++++---------------------- 6 files changed, 163 insertions(+), 38 deletions(-) create mode 100644 serializer.h create mode 100644 serializer.hpp diff --git a/database.h b/database.h index 6bb25fd..685e3fe 100644 --- a/database.h +++ b/database.h @@ -30,6 +30,8 @@ namespace Core { class DataBase { class _Table; + template + class Serializer; public: template class Table; diff --git a/main.cpp b/main.cpp index 3375807..505c051 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,28 @@ #include "database.h" #include "table.h" + +template +class Serializer { +public: + Serializer(const T& value):val(value) {} + + void print() { + std::cout << val << std::endl; + } + + T val; +}; + + +template<> +class Serializer { +public: + Serializer(int v): value(v) {} + + int value; +}; + int main(int argc, char **argv) { Core::DataBase base("test1"); @@ -13,5 +35,7 @@ int main(int argc, char **argv) { table1->addRecord(1, 2); + + return 0; } diff --git a/serializer.h b/serializer.h new file mode 100644 index 0000000..db29197 --- /dev/null +++ b/serializer.h @@ -0,0 +1,52 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_DATABASE_SERIALIZER_H +#define CORE_DATABASE_SERIALIZER_H + +#include +#include +#include + +#include "database.h" + +namespace Core { + +template +class DataBase::Serializer +{ +public: + Serializer(); + Serializer(const T& value); + ~Serializer(); + + MDB_val setData(const T& value); + MDB_val getData(); + void clear(); + +private: + void _setData(const T& value); + + +private: + QByteArray bytes; + QBuffer buffer; + QDataStream stream; +}; + +} + +#endif // CORE_DATABASE_SERIALIZER_H diff --git a/serializer.hpp b/serializer.hpp new file mode 100644 index 0000000..4a43c66 --- /dev/null +++ b/serializer.hpp @@ -0,0 +1,70 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "serializer.h" + +template +Core::DataBase::Serializer::Serializer() : + bytes(), + buffer(&bytes), + stream(&buffer) +{ + buffer.open(QIODevice::WriteOnly); +} + +template +Core::DataBase::Serializer::Serializer(const T& value) : + bytes(), + buffer(&bytes), + stream(&buffer) +{ + buffer.open(QIODevice::WriteOnly); + _setValue(value); +} + +template +Core::DataBase::Serializer::~Serializer() { + buffer.close(); +} + +template +MDB_val Core::DataBase::Serializer::setData(const T& value) { + clear(); + _setValue(value); + return getValue(); +} + +template +void Core::DataBase::Serializer::_setData(const T& value) { + stream << value; +} + +template +void Core::DataBase::Serializer::clear() { + if (buffer.pos() > 0) { + buffer.seek(0); + } +} + +template +MDB_val Core::DataBase::Serializer::getData() { + MDB_val val; + + val.mv_size = buffer.pos(); + val.mv_data = (uint8_t*)bytes.data(); + + return val; +} diff --git a/table.h b/table.h index cdde697..1fadee5 100644 --- a/table.h +++ b/table.h @@ -18,6 +18,7 @@ #define CORE_TABLE_H #include "database.h" +#include "serializer.h" namespace Core { @@ -44,16 +45,14 @@ public: void changeRecord(const K& key, const V& value); void removeRecord(const K& key); V getRecord(const K& key) const; + +private: + Serializer keySerializer; + Serializer valueSerializer; }; } -MDB_val& operator << (MDB_val& data, const QString& value); -MDB_val& operator >> (MDB_val& data, QString& value); - -MDB_val& operator << (MDB_val& data, const uint32_t& value); -MDB_val& operator >> (MDB_val& data, uint32_t& value); - namespace std { std::string to_string(const QString& str); } diff --git a/table.hpp b/table.hpp index 8ccbca0..ee6766c 100644 --- a/table.hpp +++ b/table.hpp @@ -22,7 +22,9 @@ template Core::DataBase::Table::Table(const std::string& p_name, Core::DataBase* parent): - _Table(p_name, parent) + _Table(p_name, parent), + keySerializer(), + valueSerializer() { } @@ -36,9 +38,8 @@ void Core::DataBase::Table::addRecord(const K& key, const V& value) { throw Closed("addRecord", db->name, name); } - MDB_val lmdbKey, lmdbData; - lmdbKey << key; - lmdbData << value; + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData = valueSerializer.setData(value); MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -63,9 +64,8 @@ void Core::DataBase::Table::changeRecord(const K& key, const V& value) { throw Closed("changeRecord", db->name, name); } - MDB_val lmdbKey, lmdbData; - lmdbKey << key; - lmdbData << value; + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData = valueSerializer.setData(value); MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -86,8 +86,8 @@ V Core::DataBase::Table::getRecord(const K& key) const { throw Closed("getRecord", db->name, name); } - MDB_val lmdbKey, lmdbData; - lmdbKey << key; + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData; MDB_txn *txn; int rc; @@ -115,8 +115,7 @@ void Core::DataBase::Table::removeRecord(const K& key) { throw Closed("removeRecord", db->name, name); } - MDB_val lmdbKey; - lmdbKey << key; + MDB_val lmdbKey = keySerializer.setData(key); MDB_txn *txn; int rc; @@ -134,27 +133,6 @@ void Core::DataBase::Table::removeRecord(const K& key) { } } -MDB_val& operator << (MDB_val& data, const QString& value) { - QByteArray ba = value.toUtf8(); - data.mv_size = ba.size(); - data.mv_data = ba.data(); - return data; -} -MDB_val& operator >> (MDB_val& data, QString& value) { - value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); - return data; -} - -MDB_val& operator << (MDB_val& data, const uint32_t& value) { - data.mv_size = 4; - data.mv_data = &value; - return data; -} -MDB_val& operator >> (MDB_val& data, uint32_t& value) { - std::memcpy(&value, data.mv_data, data.mv_size); - return data; -} - std::string std::to_string(const QString& str) { return str.toStdString(); } From 5f90a21fe6d3d834e0aea59c4919ca53314ced5b Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 15 Sep 2022 01:18:31 +0300 Subject: [PATCH 008/125] finally, first working prototype --- database.cpp | 23 ++++--------- database.h | 13 ++++++-- exceptions.cpp | 24 +++++++------- exceptions.h | 4 +-- main.cpp | 43 ++++++++++-------------- serializer.h | 6 ++-- serializer.hpp | 40 ++++++++++++++++------- serializer_uint32.hpp | 50 ++++++++++++++++++++++++++++ table.cpp | 4 +-- table.h | 22 +++++++------ table.hpp | 76 ++++++++++++++++++++++++++++--------------- 11 files changed, 193 insertions(+), 112 deletions(-) create mode 100644 serializer_uint32.hpp diff --git a/database.cpp b/database.cpp index 0b10e11..2c2791a 100644 --- a/database.cpp +++ b/database.cpp @@ -18,7 +18,7 @@ #include "exceptions.h" #include "table.h" -Core::DataBase::DataBase(const QString& p_name, uint16_t mapSize): +DataBase::DataBase(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), size(mapSize), @@ -27,7 +27,7 @@ Core::DataBase::DataBase(const QString& p_name, uint16_t mapSize): { } -Core::DataBase::~DataBase() +DataBase::~DataBase() { close(); for (const std::pair& pair : tables) { @@ -35,7 +35,7 @@ Core::DataBase::~DataBase() } } -void Core::DataBase::close() +void DataBase::close() { if (opened) { for (const std::pair& pair : tables) { @@ -47,7 +47,7 @@ void Core::DataBase::close() } } -void Core::DataBase::open() +void DataBase::open() { if (!opened) { mdb_env_create(&environment); @@ -71,7 +71,7 @@ void Core::DataBase::open() for (const std::pair& pair : tables) { _Table* table = pair.second; - int rc = mdb_dbi_open(txn, pair.first.c_str(), MDB_CREATE, &table->dbi); + int rc = table->createTable(txn); if (rc) { throw Unknown(name, mdb_strerror(rc)); } @@ -82,18 +82,9 @@ void Core::DataBase::open() } } -QString Core::DataBase::getName() const +QString DataBase::getName() const { return QString::fromStdString(name); } -template -Core::DataBase::Table* Core::DataBase::addTable(const QString& p_name) { - std::string nm = p_name.toStdString(); - if (opened) { - throw Core::DataBase::Opened(name, nm); - } - Core::DataBase::Table* table = new Core::DataBase::Table(nm, this); - tables.insert(std::make_pair(nm, table)); - return table; -} + diff --git a/database.h b/database.h index 685e3fe..815ae61 100644 --- a/database.h +++ b/database.h @@ -25,8 +25,6 @@ #include #include -namespace Core { - class DataBase { class _Table; @@ -63,6 +61,17 @@ private: std::map tables; }; +#include "exceptions.h" + +template +DataBase::Table* DataBase::addTable(const QString& p_name) { + std::string nm = p_name.toStdString(); + if (opened) { + throw Opened(name, nm); + } + DataBase::Table* table = new DataBase::Table(nm, this); + tables.insert(std::make_pair(nm, (_Table*)table)); + return table; } #endif // CORE_DATABASE_H diff --git a/exceptions.cpp b/exceptions.cpp index 0c0c7d5..ac607aa 100644 --- a/exceptions.cpp +++ b/exceptions.cpp @@ -16,14 +16,14 @@ #include "exceptions.h" -Core::DataBase::Directory::Directory(const std::string& p_path): +DataBase::Directory::Directory(const std::string& p_path): Exception(), path(p_path) {} -std::string Core::DataBase::Directory::getMessage() const { +std::string DataBase::Directory::getMessage() const { return "Can't create directory for database at " + path;} -Core::DataBase::Closed::Closed( +DataBase::Closed::Closed( const std::string& p_operation, const std::string& p_dbName, const std::string& p_tableName @@ -33,26 +33,26 @@ Core::DataBase::Closed::Closed( dbName(p_dbName), tableName(p_tableName) {} -std::string Core::DataBase::Closed::getMessage() const { +std::string DataBase::Closed::getMessage() const { return "An attempt to perform operation " + operation + " on closed database " + dbName + " on table " + tableName; } -Core::DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_tableName): +DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_tableName): Exception(), dbName(p_dbName), tableName(p_tableName) {} -std::string Core::DataBase::Opened::getMessage() const { +std::string DataBase::Opened::getMessage() const { return "An attempt to add table " + tableName + " to the database " + dbName + " but it's can't be done because the DataBase is already opened." + " Please add all tables before opening DataBase."; } -Core::DataBase::NotFound::NotFound( +DataBase::NotFound::NotFound( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -62,12 +62,12 @@ Core::DataBase::NotFound::NotFound( dbName(p_dbName), tableName(p_tableName) {} -std::string Core::DataBase::NotFound::getMessage() const { +std::string DataBase::NotFound::getMessage() const { return "Element for id " + key + " wasn't found " + " in database " + dbName + " in table " + tableName;} -Core::DataBase::Exist::Exist( +DataBase::Exist::Exist( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -77,14 +77,14 @@ Core::DataBase::Exist::Exist( dbName(p_dbName), tableName(p_tableName) {} -std::string Core::DataBase::Exist::getMessage() const { +std::string DataBase::Exist::getMessage() const { return "An attempt to insert element with key " + key + " to database " + dbName + " to table " + tableName + " but it already has an element with given id"; } -Core::DataBase::Unknown::Unknown( +DataBase::Unknown::Unknown( const std::string& p_dbName, const std::string& message, const std::optional& p_tableName @@ -94,7 +94,7 @@ Core::DataBase::Unknown::Unknown( tableName(p_tableName), msg(message) {} -std::string Core::DataBase::Unknown::getMessage() const +std::string DataBase::Unknown::getMessage() const { std::string result = "Unknown error in database " + dbName; if (tableName.has_value()) { diff --git a/exceptions.h b/exceptions.h index 7e2bd34..e77f681 100644 --- a/exceptions.h +++ b/exceptions.h @@ -20,8 +20,6 @@ #include "exception.h" #include "database.h" -namespace Core { - class DataBase::Directory: public Utils::Exception { public: Directory(const std::string& path); @@ -84,5 +82,5 @@ private: std::optional tableName; std::string msg; }; -} + #endif //CORE_DATABASE_EXCEPTIONS_H diff --git a/main.cpp b/main.cpp index 505c051..3b2104a 100644 --- a/main.cpp +++ b/main.cpp @@ -4,38 +4,31 @@ #include "table.h" -template -class Serializer { -public: - Serializer(const T& value):val(value) {} - - void print() { - std::cout << val << std::endl; - } - - T val; -}; - - -template<> -class Serializer { -public: - Serializer(int v): value(v) {} - - int value; -}; - int main(int argc, char **argv) { - Core::DataBase base("test1"); - Core::DataBase::Table* table1 = base.addTable("table1"); - Core::DataBase::Table* table2 = base.addTable("table2"); + DataBase base("test1"); + DataBase::Table* table1 = base.addTable("table1"); + DataBase::Table* table2 = base.addTable("table2"); base.open(); - table1->addRecord(1, 2); + try { + table1->addRecord(1, 2); + } catch (const DataBase::Exist& error) { + std::cout << error.getMessage() << std::endl; + } + uint32_t rec1 = table1->getRecord(1); + std::cout << "table1 record under 1 is " << rec1 << std::endl; + try { + table2->addRecord("hello", "world"); + } catch (const DataBase::Exist& error) { + std::cout << error.getMessage() << std::endl; + } + + QString rec2 = table2->getRecord("hello"); + std::cout << "table2 record under hello is " << rec2.toStdString() << std::endl; return 0; } diff --git a/serializer.h b/serializer.h index db29197..992f3f4 100644 --- a/serializer.h +++ b/serializer.h @@ -23,8 +23,6 @@ #include "database.h" -namespace Core { - template class DataBase::Serializer { @@ -33,6 +31,7 @@ public: Serializer(const T& value); ~Serializer(); + T deserialize(const MDB_val& value); MDB_val setData(const T& value); MDB_val getData(); void clear(); @@ -47,6 +46,7 @@ private: QDataStream stream; }; -} +#include "serializer.hpp" +#include "serializer_uint32.hpp" #endif // CORE_DATABASE_SERIALIZER_H diff --git a/serializer.hpp b/serializer.hpp index 4a43c66..59305c0 100644 --- a/serializer.hpp +++ b/serializer.hpp @@ -14,57 +14,73 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . + +#ifndef CORE_DATABASE_SERIALIZER_HPP +#define CORE_DATABASE_SERIALIZER_HPP + #include "serializer.h" template -Core::DataBase::Serializer::Serializer() : +DataBase::Serializer::Serializer() : bytes(), buffer(&bytes), stream(&buffer) { - buffer.open(QIODevice::WriteOnly); + buffer.open(QIODevice::ReadWrite); } template -Core::DataBase::Serializer::Serializer(const T& value) : +DataBase::Serializer::Serializer(const T& value) : bytes(), buffer(&bytes), stream(&buffer) { - buffer.open(QIODevice::WriteOnly); + buffer.open(QIODevice::ReadWrite); _setValue(value); } template -Core::DataBase::Serializer::~Serializer() { +DataBase::Serializer::~Serializer() { buffer.close(); } template -MDB_val Core::DataBase::Serializer::setData(const T& value) { +MDB_val DataBase::Serializer::setData(const T& value) { clear(); - _setValue(value); - return getValue(); + _setData(value); + return getData(); } template -void Core::DataBase::Serializer::_setData(const T& value) { +T DataBase::Serializer::deserialize(const MDB_val& value) { + clear(); + bytes.setRawData((char*)value.mv_data, value.mv_size); + T result; + stream >> result; + + return result; +} + +template +void DataBase::Serializer::_setData(const T& value) { stream << value; } template -void Core::DataBase::Serializer::clear() { +void DataBase::Serializer::clear() { if (buffer.pos() > 0) { buffer.seek(0); } } template -MDB_val Core::DataBase::Serializer::getData() { +MDB_val DataBase::Serializer::getData() { MDB_val val; val.mv_size = buffer.pos(); - val.mv_data = (uint8_t*)bytes.data(); + val.mv_data = (char*)bytes.data(); return val; } + +#endif //CORE_DATABASE_SERIALIZER_HPP diff --git a/serializer_uint32.hpp b/serializer_uint32.hpp new file mode 100644 index 0000000..7c1832e --- /dev/null +++ b/serializer_uint32.hpp @@ -0,0 +1,50 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_UINT32_HPP +#define CORE_DATABASE_SERIALIZER_UINT32_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const uint32_t& p_value):value(p_value) {}; + ~Serializer() {}; + + uint32_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 4); + return value; + }; + MDB_val setData(const uint32_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value, + result.mv_size = 4; + return result; + }; + void clear() {}; //not possible; + +private: + uint32_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_UINT32_HPP diff --git a/table.cpp b/table.cpp index 17b6e03..ea26110 100644 --- a/table.cpp +++ b/table.cpp @@ -16,11 +16,11 @@ #include "table.h" -Core::DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): +DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): dbi(), db(parent), name(p_name) { } -Core::DataBase::_Table::~_Table() {} +DataBase::_Table::~_Table() {} diff --git a/table.h b/table.h index 1fadee5..0c25231 100644 --- a/table.h +++ b/table.h @@ -20,17 +20,23 @@ #include "database.h" #include "serializer.h" -namespace Core { - class DataBase::_Table { public: _Table(const std::string& name, DataBase* parent); virtual ~_Table(); + virtual int createTable(MDB_txn * transaction) = 0; public: MDB_dbi dbi; DataBase* db; const std::string name; + +protected: + template + int makeTable(MDB_txn* transaction); + + template + static std::string toString(const T& value); }; template @@ -47,16 +53,12 @@ public: V getRecord(const K& key) const; private: - Serializer keySerializer; - Serializer valueSerializer; + Serializer* keySerializer; + Serializer* valueSerializer; + + int createTable(MDB_txn* transaction) override; }; -} - -namespace std { - std::string to_string(const QString& str); -} - #include "table.hpp" #endif // CORE_TABLE_H diff --git a/table.hpp b/table.hpp index ee6766c..9365ec5 100644 --- a/table.hpp +++ b/table.hpp @@ -20,26 +20,28 @@ #include "table.h" #include "exceptions.h" -template -Core::DataBase::Table::Table(const std::string& p_name, Core::DataBase* parent): +template +DataBase::Table::Table(const std::string& p_name, DataBase* parent): _Table(p_name, parent), - keySerializer(), - valueSerializer() + keySerializer(new Serializer()), + valueSerializer(new Serializer()) { } -template -Core::DataBase::Table::~Table() { +template +DataBase::Table::~Table() { + delete valueSerializer; + delete keySerializer; } -template -void Core::DataBase::Table::addRecord(const K& key, const V& value) { +template +void DataBase::Table::addRecord(const K& key, const V& value) { if (!db->opened) { throw Closed("addRecord", db->name, name); } - MDB_val lmdbKey = keySerializer.setData(key); - MDB_val lmdbData = valueSerializer.setData(value); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData = valueSerializer->setData(value); MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -47,7 +49,7 @@ void Core::DataBase::Table::addRecord(const K& key, const V& value) { if (rc != 0) { mdb_txn_abort(txn); if (rc == MDB_KEYEXIST) { - throw Exist(std::to_string(key), db->name, name); + throw Exist(toString(key), db->name, name); } else { throw Unknown(db->name, mdb_strerror(rc), name); } @@ -58,14 +60,14 @@ void Core::DataBase::Table::addRecord(const K& key, const V& value) { -template -void Core::DataBase::Table::changeRecord(const K& key, const V& value) { +template +void DataBase::Table::changeRecord(const K& key, const V& value) { if (!db->opened) { throw Closed("changeRecord", db->name, name); } - MDB_val lmdbKey = keySerializer.setData(key); - MDB_val lmdbData = valueSerializer.setData(value); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData = valueSerializer->setData(value); MDB_txn *txn; mdb_txn_begin(db->environment, NULL, 0, &txn); int rc; @@ -80,13 +82,13 @@ void Core::DataBase::Table::changeRecord(const K& key, const V& value) { } } -template -V Core::DataBase::Table::getRecord(const K& key) const { +template +V DataBase::Table::getRecord(const K& key) const { if (!db->opened) { throw Closed("getRecord", db->name, name); } - MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; MDB_txn *txn; @@ -96,26 +98,25 @@ V Core::DataBase::Table::getRecord(const K& key) const { if (rc) { mdb_txn_abort(txn); if (rc == MDB_NOTFOUND) { - throw NotFound(std::to_string(key), db->name, name); + throw NotFound(toString(key), db->name, name); } else { throw Unknown(db->name, mdb_strerror(rc), name); } } else { - V value; - lmdbData >> value; + V value = valueSerializer->deserialize(lmdbData); mdb_txn_abort(txn); return value; } } -template -void Core::DataBase::Table::removeRecord(const K& key) { +template +void DataBase::Table::removeRecord(const K& key) { if (!db->opened) { throw Closed("removeRecord", db->name, name); } - MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbKey = keySerializer->setData(key); MDB_txn *txn; int rc; @@ -124,7 +125,7 @@ void Core::DataBase::Table::removeRecord(const K& key) { if (rc) { mdb_txn_abort(txn); if (rc == MDB_NOTFOUND) { - throw NotFound(std::to_string(key), db->name, name); + throw NotFound(toString(key), db->name, name); } else { throw Unknown(db->name, mdb_strerror(rc), name); } @@ -133,8 +134,29 @@ void Core::DataBase::Table::removeRecord(const K& key) { } } -std::string std::to_string(const QString& str) { - return str.toStdString(); +template +int DataBase::Table::createTable(MDB_txn* transaction) { + return makeTable(transaction); +} + +template +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); +} + +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template +inline std::string DataBase::_Table::toString(const T& value) { + return std::to_string(value); +} + +template<> +inline std::string DataBase::_Table::toString(const QString& value) { + return value.toStdString(); } #endif //CORE_TABLE_HPP From 047f96b54a56159ed9628803cfae0cb0cd257869 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 16 Sep 2022 00:34:39 +0300 Subject: [PATCH 009/125] some tuning, specializations, basic testing --- CMakeLists.txt | 40 +++++++- database.cpp | 20 ++++ database.h | 22 ++-- exception.cpp | 33 ------ exception.h | 40 -------- exceptions.cpp | 12 +++ exceptions.h | 26 +++-- main.cpp | 34 ------- serializer.h | 3 + serializer_uint16.hpp | 52 ++++++++++ serializer_uint64.hpp | 51 ++++++++++ serializer_uint8.hpp | 53 ++++++++++ storage.h | 158 ----------------------------- storage.hpp | 226 ------------------------------------------ table.hpp | 20 ++++ test/CMakeLists.txt | 22 ++++ test/basic.cpp | 66 ++++++++++++ 17 files changed, 370 insertions(+), 508 deletions(-) delete mode 100644 exception.cpp delete mode 100644 exception.h delete mode 100644 main.cpp create mode 100644 serializer_uint16.hpp create mode 100644 serializer_uint64.hpp create mode 100644 serializer_uint8.hpp delete mode 100644 storage.h delete mode 100644 storage.hpp create mode 100644 test/CMakeLists.txt create mode 100644 test/basic.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ea1dcaa..d9702c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,11 @@ project(storage VERSION 0.0.1 LANGUAGES CXX) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) + +option(BUILD_STATIC "Builds library as static library" ON) +option(BUILD_TESTS "Builds tests" ON) +include(GNUInstallDirs) + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") @@ -22,14 +27,36 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif () -add_executable(storage - main.cpp - exception.cpp +set(SOURCES exceptions.cpp table.cpp database.cpp ) +set(HEADERS + database.h + exceptions.h + table.h + table.hpp + serializer.h + serializer.hpp + serializer_uint8.hpp + serializer_uint16.hpp + serializer_uint32.hpp + serializer_uint64.hpp +) + +if (BUILD_STATIC) + add_library(storage STATIC ${SOURCES}) +else () + add_library(storage SHARED ${SOURCES}) +endif() + +if (BUILD_TESTS) + add_subdirectory(test) +endif () + +set_target_properties(storage PROPERTIES PUBLIC_HEADER "${HEADERS}") target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR}) target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) @@ -41,4 +68,9 @@ target_link_libraries(storage ) target_link_libraries(storage PRIVATE lmdb) -install(TARGETS storage RUNTIME DESTINATION bin) +install(TARGETS storage + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/storage + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/storage +) + diff --git a/database.cpp b/database.cpp index 2c2791a..78351a2 100644 --- a/database.cpp +++ b/database.cpp @@ -82,9 +82,29 @@ void DataBase::open() } } +bool DataBase::removeDirectory() +{ + if (opened) { + throw Opened(name, ""); + } + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + getName(); + QDir cache(path); + + if (cache.exists()) { + return cache.removeRecursively(); + } else { + return true; + } +} + QString DataBase::getName() const { return QString::fromStdString(name); } +bool DataBase::ready() const +{ + return opened; +} diff --git a/database.h b/database.h index 815ae61..9872ecb 100644 --- a/database.h +++ b/database.h @@ -39,13 +39,19 @@ public: void open(); void close(); + bool ready() const; + bool removeDirectory(); QString getName() const; template - Table* addTable(const QString& name); + Table* addTable(const std::string& name); + + template + Table* getTable(const std::string& name); public: //exceptions + class Exception; class Directory; class Closed; class Opened; @@ -64,14 +70,18 @@ private: #include "exceptions.h" template -DataBase::Table* DataBase::addTable(const QString& p_name) { - std::string nm = p_name.toStdString(); +DataBase::Table* DataBase::addTable(const std::string& p_name) { if (opened) { - throw Opened(name, nm); + throw Opened(name, p_name); } - DataBase::Table* table = new DataBase::Table(nm, this); - tables.insert(std::make_pair(nm, (_Table*)table)); + DataBase::Table* table = new DataBase::Table(p_name, this); + tables.insert(std::make_pair(p_name, (_Table*)table)); return table; } +template +DataBase::Table* DataBase::getTable(const std::string& p_name) { + return static_cast*>(tables.at(p_name)); +} + #endif // CORE_DATABASE_H diff --git a/exception.cpp b/exception.cpp deleted file mode 100644 index 3dee9b3..0000000 --- a/exception.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * 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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "exception.h" - -Utils::Exception::Exception() -{ -} - -Utils::Exception::~Exception() -{ -} - -const char* Utils::Exception::what() const noexcept( true ) -{ - std::string* msg = new std::string(getMessage()); - return msg->c_str(); -} diff --git a/exception.h b/exception.h deleted file mode 100644 index 4c66c2d..0000000 --- a/exception.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * 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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef EXCEPTION_H -#define EXCEPTION_H - -#include -#include - -namespace Utils -{ - class Exception: - public std::exception - { - public: - Exception(); - virtual ~Exception(); - - virtual std::string getMessage() const = 0; - - const char* what() const noexcept( true ); - }; -} - -#endif // EXCEPTION_H diff --git a/exceptions.cpp b/exceptions.cpp index ac607aa..a210372 100644 --- a/exceptions.cpp +++ b/exceptions.cpp @@ -16,6 +16,18 @@ #include "exceptions.h" +DataBase::Exception::Exception(): +std::exception() +{} + +DataBase::Exception::~Exception() {} + +const char* DataBase::Exception::what() const noexcept( true ) +{ + std::string* msg = new std::string(getMessage()); + return msg->c_str(); +} + DataBase::Directory::Directory(const std::string& p_path): Exception(), path(p_path) {} diff --git a/exceptions.h b/exceptions.h index e77f681..bdfd62a 100644 --- a/exceptions.h +++ b/exceptions.h @@ -17,10 +17,22 @@ #ifndef CORE_DATABASE_EXCEPTIONS_H #define CORE_DATABASE_EXCEPTIONS_H -#include "exception.h" +#include +#include #include "database.h" -class DataBase::Directory: public Utils::Exception { +class DataBase::Exception : public std::exception +{ +public: + Exception(); + virtual ~Exception(); + + virtual std::string getMessage() const = 0; + + const char* what() const noexcept( true ); +}; + +class DataBase::Directory: public DataBase::Exception { public: Directory(const std::string& path); @@ -29,7 +41,7 @@ private: std::string path; }; -class DataBase::Closed : public Utils::Exception { +class DataBase::Closed : public DataBase::Exception { public: Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName); @@ -40,7 +52,7 @@ private: std::string tableName; }; -class DataBase::Opened : public Utils::Exception { +class DataBase::Opened : public DataBase::Exception { public: Opened(const std::string& dbName, const std::string& tableName); @@ -50,7 +62,7 @@ private: std::string tableName; }; -class DataBase::NotFound : public Utils::Exception { +class DataBase::NotFound : public DataBase::Exception { public: NotFound(const std::string& key, const std::string& dbName, const std::string& tableName); @@ -61,7 +73,7 @@ private: std::string tableName; }; -class DataBase::Exist : public Utils::Exception { +class DataBase::Exist : public DataBase::Exception { public: Exist(const std::string& key, const std::string& dbName, const std::string& tableName); @@ -72,7 +84,7 @@ private: std::string tableName; }; -class DataBase::Unknown : public Utils::Exception { +class DataBase::Unknown : public DataBase::Exception { public: Unknown(const std::string& dbName, const std::string& message, const std::optional& tableName = std::nullopt); diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 3b2104a..0000000 --- a/main.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "database.h" -#include "table.h" - - -int main(int argc, char **argv) { - - DataBase base("test1"); - DataBase::Table* table1 = base.addTable("table1"); - DataBase::Table* table2 = base.addTable("table2"); - - base.open(); - - try { - table1->addRecord(1, 2); - } catch (const DataBase::Exist& error) { - std::cout << error.getMessage() << std::endl; - } - - uint32_t rec1 = table1->getRecord(1); - std::cout << "table1 record under 1 is " << rec1 << std::endl; - - try { - table2->addRecord("hello", "world"); - } catch (const DataBase::Exist& error) { - std::cout << error.getMessage() << std::endl; - } - - QString rec2 = table2->getRecord("hello"); - std::cout << "table2 record under hello is " << rec2.toStdString() << std::endl; - - return 0; -} diff --git a/serializer.h b/serializer.h index 992f3f4..1adbabc 100644 --- a/serializer.h +++ b/serializer.h @@ -47,6 +47,9 @@ private: }; #include "serializer.hpp" +#include "serializer_uint64.hpp" #include "serializer_uint32.hpp" +#include "serializer_uint16.hpp" +#include "serializer_uint8.hpp" #endif // CORE_DATABASE_SERIALIZER_H diff --git a/serializer_uint16.hpp b/serializer_uint16.hpp new file mode 100644 index 0000000..3551de2 --- /dev/null +++ b/serializer_uint16.hpp @@ -0,0 +1,52 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_UINT16_HPP +#define CORE_DATABASE_SERIALIZER_UINT16_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const uint16_t& p_value):value(p_value) {}; + ~Serializer() {}; + + uint16_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 2); + return value; + }; + MDB_val setData(const uint16_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value, + result.mv_size = 2; + return result; + }; + void clear() {}; //not possible; + +private: + uint16_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_UINT16_HPP + + diff --git a/serializer_uint64.hpp b/serializer_uint64.hpp new file mode 100644 index 0000000..2b19b9d --- /dev/null +++ b/serializer_uint64.hpp @@ -0,0 +1,51 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_UINT64_HPP +#define CORE_DATABASE_SERIALIZER_UINT64_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const uint64_t& p_value):value(p_value) {}; + ~Serializer() {}; + + uint64_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 8); + return value; + }; + MDB_val setData(const uint64_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value, + result.mv_size = 8; + return result; + }; + void clear() {}; //not possible; + +private: + uint64_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_UINT64_HPP + diff --git a/serializer_uint8.hpp b/serializer_uint8.hpp new file mode 100644 index 0000000..97f76b0 --- /dev/null +++ b/serializer_uint8.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_UINT8_HPP +#define CORE_DATABASE_SERIALIZER_UINT8_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const uint8_t& p_value):value(p_value) {}; + ~Serializer() {}; + + uint8_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 1); + return value; + }; + MDB_val setData(const uint8_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value, + result.mv_size = 1; + return result; + }; + void clear() {}; //not possible; + +private: + uint8_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_UINT8_HPP + + + diff --git a/storage.h b/storage.h deleted file mode 100644 index 6670f6a..0000000 --- a/storage.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * 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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef CORE_STORAGE_H -#define CORE_STORAGE_H - -#include -#include - - -#include "exception.h" - -namespace Core { - -/** - * @todo write docs - */ -template -class Storage -{ -public: - Storage(const QString& name); - ~Storage(); - - void open(); - void close(); - - void addRecord(const K& key, const V& value); - void changeRecord(const K& key, const V& value); - void removeRecord(const K& key); - V getRecord(const K& key) const; - QString getName() const; - - -private: - QString name; - bool opened; - MDB_env* environment; - MDB_dbi base; - -public: - class Directory: - public Utils::Exception - { - public: - Directory(const std::string& p_path):Exception(), path(p_path){} - - std::string getMessage() const{return "Can't create directory for database at " + path;} - private: - std::string path; - }; - - class Closed: - public Utils::Exception - { - public: - Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} - - std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} - private: - std::string operation; - std::string account; - }; - - class NotFound: - public Utils::Exception - { - public: - NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} - - std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} - private: - std::string key; - std::string account; - }; - - class Empty: - public Utils::Exception - { - public: - Empty(const std::string& acc):Exception(), account(acc){} - - std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} - private: - std::string account; - }; - - class Exist: - public Utils::Exception - { - public: - Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} - - std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} - private: - std::string account; - std::string key; - }; - - class NoAvatar: - public Utils::Exception - { - public: - NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ - if (resource.size() == 0) { - resource = "for himself"; - } - } - - std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} - private: - std::string element; - std::string resource; - }; - - class Unknown: - public Utils::Exception - { - public: - Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} - - std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} - private: - std::string account; - std::string msg; - }; -}; - -} - -MDB_val& operator << (MDB_val& data, QString& value); -MDB_val& operator >> (MDB_val& data, QString& value); - -MDB_val& operator << (MDB_val& data, uint32_t& value); -MDB_val& operator >> (MDB_val& data, uint32_t& value); - -namespace std { - std::string to_string(const QString& str); -} - -#include "storage.hpp" - -#endif // CORE_STORAGE_H diff --git a/storage.hpp b/storage.hpp deleted file mode 100644 index 477d70e..0000000 --- a/storage.hpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * 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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef CORE_STORAGE_HPP -#define CORE_STORAGE_HPP - -#include -#include - -#include "storage.h" -#include - -template -Core::Storage::Storage(const QString& p_name): - name(p_name), - opened(false), - environment(), - base() -{ -} - -template -Core::Storage::~Storage() -{ - close(); -} - -template -void Core::Storage::open() -{ - if (!opened) { - mdb_env_create(&environment); - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + name; - QDir cache(path); - - if (!cache.exists()) { - bool res = cache.mkpath(path); - if (!res) { - throw Directory(path.toStdString()); - } - } - - mdb_env_set_maxdbs(environment, 1); - mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL); - mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - mdb_dbi_open(txn, "base", MDB_CREATE, &base); - mdb_txn_commit(txn); - opened = true; - } -} - -template -void Core::Storage::close() -{ - if (opened) { - mdb_dbi_close(environment, base); - mdb_env_close(environment); - opened = false; - } -} - -template -void Core::Storage::addRecord(const K& key, const V& value) -{ - if (!opened) { - throw Closed("addRecord", name.toStdString()); - } - QByteArray ba; - QDataStream ds(&ba, QIODevice::WriteOnly); - ds << value; - - MDB_val lmdbKey, lmdbData; - lmdbKey << key; - - lmdbData.mv_size = ba.size(); - lmdbData.mv_data = (uint8_t*)ba.data(); - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - int rc; - rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); - if (rc != 0) { - mdb_txn_abort(txn); - if (rc == MDB_KEYEXIST) { - throw Exist(name.toStdString(), std::to_string(key)); - } else { - throw Unknown(name.toStdString(), mdb_strerror(rc)); - } - } else { - mdb_txn_commit(txn); - } -} - -template -void Core::Storage::changeRecord(const K& key, const V& value) -{ - if (!opened) { - throw Closed("changeRecord", name.toStdString()); - } - - QByteArray ba; - QDataStream ds(&ba, QIODevice::WriteOnly); - ds << value; - - MDB_val lmdbKey, lmdbData; - lmdbKey << key; - lmdbData.mv_size = ba.size(); - lmdbData.mv_data = (uint8_t*)ba.data(); - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - int rc; - rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0); - if (rc != 0) { - mdb_txn_abort(txn); - if (rc) { - throw Unknown(name.toStdString(), mdb_strerror(rc)); - } - } else { - mdb_txn_commit(txn); - } -} - -template -V Core::Storage::getRecord(const K& key) const -{ - if (!opened) { - throw Closed("addElement", name.toStdString()); - } - - MDB_val lmdbKey, lmdbData; - lmdbKey << key; - - MDB_txn *txn; - int rc; - mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - rc = mdb_get(txn, base, &lmdbKey, &lmdbData); - if (rc) { - mdb_txn_abort(txn); - if (rc == MDB_NOTFOUND) { - throw NotFound(std::to_string(key), name.toStdString()); - } else { - throw Unknown(name.toStdString(), mdb_strerror(rc)); - } - } else { - QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); - QDataStream ds(&ba, QIODevice::ReadOnly); - V value; - ds >> value; - mdb_txn_abort(txn); - - return value; - } -} - -template -void Core::Storage::removeRecord(const K& key) -{ - if (!opened) { - throw Closed("addElement", name.toStdString()); - } - - MDB_val lmdbKey; - lmdbKey << key; - - MDB_txn *txn; - int rc; - mdb_txn_begin(environment, NULL, 0, &txn); - rc = mdb_del(txn, base, &lmdbKey, NULL); - if (rc) { - mdb_txn_abort(txn); - if (rc == MDB_NOTFOUND) { - throw NotFound(std::to_string(key), name.toStdString()); - } else { - throw Unknown(name.toStdString(), mdb_strerror(rc)); - } - } else { - mdb_txn_commit(txn); - } -} - -template -QString Core::Storage::getName() const { - return name;} - -MDB_val& operator << (MDB_val& data, const QString& value) { - QByteArray ba = value.toUtf8(); - data.mv_size = ba.size(); - data.mv_data = ba.data(); - return data; -} -MDB_val& operator >> (MDB_val& data, QString& value) { - value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); - return data; -} - -MDB_val& operator << (MDB_val& data, uint32_t& value) { - data.mv_size = 4; - data.mv_data = &value; - return data; -} -MDB_val& operator >> (MDB_val& data, uint32_t& value) { - std::memcpy(&value, data.mv_data, data.mv_size); - return data; -} - -std::string std::to_string(const QString& str) { - return str.toStdString(); -} -#endif //CORE_STORAGE_HPP diff --git a/table.hpp b/table.hpp index 9365ec5..47b135c 100644 --- a/table.hpp +++ b/table.hpp @@ -144,11 +144,26 @@ inline int DataBase::_Table::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); } +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + template<> inline int DataBase::_Table::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + template inline std::string DataBase::_Table::toString(const T& value) { return std::to_string(value); @@ -159,4 +174,9 @@ inline std::string DataBase::_Table::toString(const QString& value) { return value.toStdString(); } +template<> +inline std::string DataBase::_Table::toString(const std::string& value) { + return value; +} + #endif //CORE_TABLE_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..d35802f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,22 @@ +enable_testing() +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIR}) + +add_executable(runUnitTests + basic.cpp +) + +target_compile_options(runUnitTests PRIVATE -fPIC) + +target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) + +target_link_libraries( + runUnitTests + GTest::gtest_main + storage +) + +include(GoogleTest) +gtest_discover_tests(runUnitTests) diff --git a/test/basic.cpp b/test/basic.cpp new file mode 100644 index 0000000..affbd4e --- /dev/null +++ b/test/basic.cpp @@ -0,0 +1,66 @@ +#include + +#include "database.h" +#include "table.h" + +#include + +class DataBaseTest : public ::testing::Test { +protected: + DataBaseTest(): + ::testing::Test(), + t1(db->getTable("table1")), + t2(db->getTable("table2")) {} + + ~DataBaseTest() { + } + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new DataBase("testBase"); + db->addTable("table1"); + db->addTable("table2"); + } + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static DataBase* db; + + DataBase::Table* t1; + DataBase::Table* t2; +}; + + +DataBase* DataBaseTest::db = nullptr; + +TEST_F(DataBaseTest, RemovingDirectory) { + EXPECT_EQ(db->removeDirectory(), true); +} + +TEST_F(DataBaseTest, OpeningDatabase) { + db->open(); + EXPECT_EQ(db->ready(), true); +} + +TEST_F(DataBaseTest, AddingIntegerKey) { + EXPECT_EQ(db->ready(), true); + t1->addRecord(1, 2); + EXPECT_EQ(t1->getRecord(1), 2); +} + +TEST_F(DataBaseTest, AddingQStringKey) { + EXPECT_EQ(db->ready(), true); + t2->addRecord("hello", "world"); + EXPECT_EQ(t2->getRecord("hello"), "world"); +} + +TEST_F(DataBaseTest, ClosingDatabase) { + db->close(); + EXPECT_EQ(db->ready(), false); +} From 2f34fa69e8714f5df07a88f5169e8d93dc2110be Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 17 Sep 2022 15:31:58 +0300 Subject: [PATCH 010/125] some more primitive specializations, counting rows, dropping tables, tests --- CMakeLists.txt | 8 +++ database.cpp | 24 ++++++++- database.h | 3 +- exceptions.cpp | 23 ++++---- exceptions.h | 8 +-- serializer.h | 8 +++ serializer_double.hpp | 53 ++++++++++++++++++ serializer_float.hpp | 53 ++++++++++++++++++ serializer_int16.hpp | 50 +++++++++++++++++ serializer_int32.hpp | 53 ++++++++++++++++++ serializer_int64.hpp | 53 ++++++++++++++++++ serializer_int8.hpp | 53 ++++++++++++++++++ serializer_qstring.hpp | 58 ++++++++++++++++++++ serializer_stdstring.hpp | 54 +++++++++++++++++++ serializer_uint16.hpp | 2 +- serializer_uint32.hpp | 2 +- serializer_uint64.hpp | 2 +- serializer_uint8.hpp | 2 +- table.cpp | 43 +++++++++++++++ table.h | 11 +++- table.hpp | 20 +++++++ test/basic.cpp | 112 ++++++++++++++++++++++++++++++++++++--- 22 files changed, 667 insertions(+), 28 deletions(-) create mode 100644 serializer_double.hpp create mode 100644 serializer_float.hpp create mode 100644 serializer_int16.hpp create mode 100644 serializer_int32.hpp create mode 100644 serializer_int64.hpp create mode 100644 serializer_int8.hpp create mode 100644 serializer_qstring.hpp create mode 100644 serializer_stdstring.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d9702c4..1cd7f21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,14 @@ set(HEADERS serializer_uint16.hpp serializer_uint32.hpp serializer_uint64.hpp + serializer_int8.hpp + serializer_int16.hpp + serializer_int32.hpp + serializer_int64.hpp + serializer_float.hpp + serializer_double.hpp + serializer_stdstring.hpp + serializer_qstring.hpp ) if (BUILD_STATIC) diff --git a/database.cpp b/database.cpp index 78351a2..8904bf7 100644 --- a/database.cpp +++ b/database.cpp @@ -85,7 +85,7 @@ void DataBase::open() bool DataBase::removeDirectory() { if (opened) { - throw Opened(name, ""); + throw Opened(name, "remove database directory"); } QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + getName(); @@ -108,3 +108,25 @@ bool DataBase::ready() const return opened; } +void DataBase::drop() +{ + if (!opened) { + throw Closed("drop", name); + } + + MDB_txn *txn; + int rc = mdb_txn_begin(environment, NULL, 0, &txn); + if (rc) { + throw Unknown(name, mdb_strerror(rc)); + } + for (const std::pair& pair : tables) { + rc = pair.second->drop(txn); + if (rc) { + throw Unknown(name, mdb_strerror(rc), pair.first); + } + } + + mdb_txn_commit(txn); +} + + diff --git a/database.h b/database.h index 9872ecb..167fc95 100644 --- a/database.h +++ b/database.h @@ -42,6 +42,7 @@ public: bool ready() const; bool removeDirectory(); QString getName() const; + void drop(); template Table* addTable(const std::string& name); @@ -72,7 +73,7 @@ private: template DataBase::Table* DataBase::addTable(const std::string& p_name) { if (opened) { - throw Opened(name, p_name); + throw Opened(name, "add table " + p_name); } DataBase::Table* table = new DataBase::Table(p_name, this); tables.insert(std::make_pair(p_name, (_Table*)table)); diff --git a/exceptions.cpp b/exceptions.cpp index a210372..086649e 100644 --- a/exceptions.cpp +++ b/exceptions.cpp @@ -38,7 +38,7 @@ std::string DataBase::Directory::getMessage() const { DataBase::Closed::Closed( const std::string& p_operation, const std::string& p_dbName, - const std::string& p_tableName + const std::optional& p_tableName ): Exception(), operation(p_operation), @@ -46,22 +46,25 @@ DataBase::Closed::Closed( tableName(p_tableName) {} std::string DataBase::Closed::getMessage() const { - return "An attempt to perform operation " + operation - + " on closed database " + dbName - + " on table " + tableName; + std::string msg = "An attempt to perform operation " + operation + + " on closed database " + dbName; + if (tableName.has_value()) { + msg += + " on table " + tableName.value(); + } + return msg; + } -DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_tableName): +DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), - tableName(p_tableName) {} + action(p_action) {} std::string DataBase::Opened::getMessage() const { - return "An attempt to add table " + tableName - + " to the database " + dbName - + " but it's can't be done because the DataBase is already opened." - + " Please add all tables before opening DataBase."; + return "An attempt to " + action + + " (the database " + dbName + + ") but it's can't be done because the DataBase is already opened"; } DataBase::NotFound::NotFound( diff --git a/exceptions.h b/exceptions.h index bdfd62a..2d06535 100644 --- a/exceptions.h +++ b/exceptions.h @@ -43,23 +43,23 @@ private: class DataBase::Closed : public DataBase::Exception { public: - Closed(const std::string& p_operation, const std::string& dbName, const std::string& tableName); + Closed(const std::string& p_operation, const std::string& dbName, const std::optional& tableName = std::nullopt); std::string getMessage() const; private: std::string operation; std::string dbName; - std::string tableName; + std::optional tableName; }; class DataBase::Opened : public DataBase::Exception { public: - Opened(const std::string& dbName, const std::string& tableName); + Opened(const std::string& dbName, const std::string& action); std::string getMessage() const; private: std::string dbName; - std::string tableName; + std::string action; }; class DataBase::NotFound : public DataBase::Exception { diff --git a/serializer.h b/serializer.h index 1adbabc..82f9880 100644 --- a/serializer.h +++ b/serializer.h @@ -51,5 +51,13 @@ private: #include "serializer_uint32.hpp" #include "serializer_uint16.hpp" #include "serializer_uint8.hpp" +#include "serializer_int64.hpp" +#include "serializer_int32.hpp" +#include "serializer_int16.hpp" +#include "serializer_int8.hpp" +#include "serializer_float.hpp" +#include "serializer_double.hpp" +#include "serializer_stdstring.hpp" +#include "serializer_qstring.hpp" #endif // CORE_DATABASE_SERIALIZER_H diff --git a/serializer_double.hpp b/serializer_double.hpp new file mode 100644 index 0000000..aaab3b1 --- /dev/null +++ b/serializer_double.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_DOUBLE_HPP +#define CORE_DATABASE_SERIALIZER_DOUBLE_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const double& p_value):value(p_value) {}; + ~Serializer() {}; + + double deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 8); + return value; + }; + MDB_val setData(const double& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 8; + return result; + }; + void clear() {}; //not possible; + +private: + double value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_DOUBLE_HPP + + + diff --git a/serializer_float.hpp b/serializer_float.hpp new file mode 100644 index 0000000..6a79946 --- /dev/null +++ b/serializer_float.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_FLOAT_HPP +#define CORE_DATABASE_SERIALIZER_FLOAT_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const float& p_value):value(p_value) {}; + ~Serializer() {}; + + float deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 4); + return value; + }; + MDB_val setData(const float& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 4; + return result; + }; + void clear() {}; //not possible; + +private: + float value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_FLOAT_HPP + + + diff --git a/serializer_int16.hpp b/serializer_int16.hpp new file mode 100644 index 0000000..380b184 --- /dev/null +++ b/serializer_int16.hpp @@ -0,0 +1,50 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_INT16_HPP +#define CORE_DATABASE_SERIALIZER_INT16_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const int16_t& p_value):value(p_value) {}; + ~Serializer() {}; + + int16_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 2); + return value; + }; + MDB_val setData(const int16_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 2; + return result; + }; + void clear() {}; //not possible; + +private: + int16_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_INT16_HPP diff --git a/serializer_int32.hpp b/serializer_int32.hpp new file mode 100644 index 0000000..da89324 --- /dev/null +++ b/serializer_int32.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_INT32_HPP +#define CORE_DATABASE_SERIALIZER_INT32_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const int32_t& p_value):value(p_value) {}; + ~Serializer() {}; + + int32_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 4); + return value; + }; + MDB_val setData(const int32_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 4; + return result; + }; + void clear() {}; //not possible; + +private: + int32_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_INT32_HPP + + + diff --git a/serializer_int64.hpp b/serializer_int64.hpp new file mode 100644 index 0000000..d39a368 --- /dev/null +++ b/serializer_int64.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_INT64_HPP +#define CORE_DATABASE_SERIALIZER_INT64_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const int64_t& p_value):value(p_value) {}; + ~Serializer() {}; + + int64_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 8); + return value; + }; + MDB_val setData(const int64_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 8; + return result; + }; + void clear() {}; //not possible; + +private: + int64_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_INT64_HPP + + + diff --git a/serializer_int8.hpp b/serializer_int8.hpp new file mode 100644 index 0000000..a1e0404 --- /dev/null +++ b/serializer_int8.hpp @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_INT8_HPP +#define CORE_DATABASE_SERIALIZER_INT8_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value(0) {}; + Serializer(const int8_t& p_value):value(p_value) {}; + ~Serializer() {}; + + int8_t deserialize(const MDB_val& data) { + std::memcpy(&value, data.mv_data, 1); + return value; + }; + MDB_val setData(const int8_t& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = &value; + result.mv_size = 1; + return result; + }; + void clear() {}; //not possible; + +private: + int8_t value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_INT8_HPP + + + diff --git a/serializer_qstring.hpp b/serializer_qstring.hpp new file mode 100644 index 0000000..72c33dd --- /dev/null +++ b/serializer_qstring.hpp @@ -0,0 +1,58 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_QSTRING_HPP +#define CORE_DATABASE_SERIALIZER_QSTRING_HPP + +#include +#include + +template<> +class DataBase::Serializer +{ +public: + Serializer():value() {}; + Serializer(const QString& p_value):value(p_value.toUtf8()) {}; + ~Serializer() {}; + + QString deserialize(const MDB_val& data) { + value = QByteArray((char*)data.mv_data, data.mv_size); + return QString::fromUtf8(value); + }; + MDB_val setData(const QString& data) { + value = data.toUtf8(); + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = value.data(); + result.mv_size = value.size(); + return result; + }; + void clear() {}; //not possible; + +private: + QByteArray value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_QSTRING_HPP + + + + + diff --git a/serializer_stdstring.hpp b/serializer_stdstring.hpp new file mode 100644 index 0000000..a17a6f1 --- /dev/null +++ b/serializer_stdstring.hpp @@ -0,0 +1,54 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_STDSTRING_HPP +#define CORE_DATABASE_SERIALIZER_STDSTRING_HPP + +template<> +class DataBase::Serializer +{ +public: + Serializer():value() {}; + Serializer(const std::string& p_value):value(p_value) {}; + ~Serializer() {}; + + std::string deserialize(const MDB_val& data) { + value = std::string((char*)data.mv_data, data.mv_size); + return value; + }; + MDB_val setData(const std::string& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = (char*)value.c_str(); + result.mv_size = value.size(); + return result; + }; + void clear() {}; //not possible; + +private: + std::string value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_STDSTRING_HPP + + + + diff --git a/serializer_uint16.hpp b/serializer_uint16.hpp index 3551de2..b5df796 100644 --- a/serializer_uint16.hpp +++ b/serializer_uint16.hpp @@ -36,7 +36,7 @@ public: }; MDB_val getData() { MDB_val result; - result.mv_data = &value, + result.mv_data = &value; result.mv_size = 2; return result; }; diff --git a/serializer_uint32.hpp b/serializer_uint32.hpp index 7c1832e..590ecb2 100644 --- a/serializer_uint32.hpp +++ b/serializer_uint32.hpp @@ -36,7 +36,7 @@ public: }; MDB_val getData() { MDB_val result; - result.mv_data = &value, + result.mv_data = &value; result.mv_size = 4; return result; }; diff --git a/serializer_uint64.hpp b/serializer_uint64.hpp index 2b19b9d..6edbbb5 100644 --- a/serializer_uint64.hpp +++ b/serializer_uint64.hpp @@ -36,7 +36,7 @@ public: }; MDB_val getData() { MDB_val result; - result.mv_data = &value, + result.mv_data = &value; result.mv_size = 8; return result; }; diff --git a/serializer_uint8.hpp b/serializer_uint8.hpp index 97f76b0..bb1fc7e 100644 --- a/serializer_uint8.hpp +++ b/serializer_uint8.hpp @@ -36,7 +36,7 @@ public: }; MDB_val getData() { MDB_val result; - result.mv_data = &value, + result.mv_data = &value; result.mv_size = 1; return result; }; diff --git a/table.cpp b/table.cpp index ea26110..141df13 100644 --- a/table.cpp +++ b/table.cpp @@ -24,3 +24,46 @@ DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): } DataBase::_Table::~_Table() {} + + +void DataBase::_Table::drop() +{ + if (!db->opened) { + throw Closed("drop", db->name, name); + } + + MDB_txn *txn; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = drop(txn); + if (rc) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + + mdb_txn_commit(txn); +} + +int DataBase::_Table::drop(MDB_txn* transaction) +{ + return mdb_drop(transaction, dbi, 0); +} + +uint32_t DataBase::_Table::count() const +{ + if (!db->opened) { + throw Closed("count", db->name, name); + } + + MDB_txn *txn; + MDB_stat stat; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + rc = mdb_stat(txn, dbi, &stat); + if (rc) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + uint32_t amount = stat.ms_entries; + mdb_txn_abort(txn); + return amount; +} diff --git a/table.h b/table.h index 0c25231..082518c 100644 --- a/table.h +++ b/table.h @@ -21,12 +21,19 @@ #include "serializer.h" class DataBase::_Table { -public: + friend class DataBase; +protected: _Table(const std::string& name, DataBase* parent); virtual ~_Table(); virtual int createTable(MDB_txn * transaction) = 0; + virtual int drop(MDB_txn * transaction); + public: + virtual void drop(); + virtual uint32_t count() const; + +protected: MDB_dbi dbi; DataBase* db; const std::string name; @@ -40,7 +47,7 @@ protected: }; template -class DataBase::Table : private DataBase::_Table { +class DataBase::Table : public DataBase::_Table { friend class DataBase; private: Table(const std::string& name, DataBase* parent); diff --git a/table.hpp b/table.hpp index 47b135c..8fa315b 100644 --- a/table.hpp +++ b/table.hpp @@ -164,6 +164,26 @@ inline int DataBase::_Table::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int DataBase::_Table::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + template inline std::string DataBase::_Table::toString(const T& value) { return std::to_string(value); diff --git a/test/basic.cpp b/test/basic.cpp index affbd4e..e68f521 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -12,8 +12,7 @@ protected: t1(db->getTable("table1")), t2(db->getTable("table2")) {} - ~DataBaseTest() { - } + ~DataBaseTest() {} static void SetUpTestSuite() { if (db == nullptr) { @@ -43,7 +42,12 @@ TEST_F(DataBaseTest, RemovingDirectory) { EXPECT_EQ(db->removeDirectory(), true); } -TEST_F(DataBaseTest, OpeningDatabase) { +TEST_F(DataBaseTest, OpeningClosingDatabase) { + EXPECT_EQ(db->ready(), false); + db->open(); + EXPECT_EQ(db->ready(), true); + db->close(); + EXPECT_EQ(db->ready(), false); db->open(); EXPECT_EQ(db->ready(), true); } @@ -51,16 +55,112 @@ TEST_F(DataBaseTest, OpeningDatabase) { TEST_F(DataBaseTest, AddingIntegerKey) { EXPECT_EQ(db->ready(), true); t1->addRecord(1, 2); + t1->addRecord(2, 2); + t1->addRecord(3, 15); EXPECT_EQ(t1->getRecord(1), 2); } TEST_F(DataBaseTest, AddingQStringKey) { EXPECT_EQ(db->ready(), true); t2->addRecord("hello", "world"); + t2->addRecord("aaa", "gagdfsdf"); + t2->addRecord("sdfhga", "DSFFDG"); + t2->addRecord("sdfsda", "shgsdgfa"); EXPECT_EQ(t2->getRecord("hello"), "world"); } -TEST_F(DataBaseTest, ClosingDatabase) { - db->close(); - EXPECT_EQ(db->ready(), false); +TEST_F(DataBaseTest, AddingRepeatingIntegerKey) { + EXPECT_EQ(db->ready(), true); + bool thrown = false; + try { + t1->addRecord(3, 24); + } catch (const DataBase::Exist e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_EQ(t1->getRecord(3), 15); } + +TEST_F(DataBaseTest, AddingRepeatingStringKey) { + EXPECT_EQ(db->ready(), true); + bool thrown = false; + try { + t2->addRecord("sdfhga", "world"); + } catch (const DataBase::Exist e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); +} + +TEST_F(DataBaseTest, GettingNotExistingKeys) { + EXPECT_EQ(db->ready(), true); + bool thrown = false; + try { + QString wrong = t2->getRecord("almonds"); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + thrown = false; + try { + uint32_t wrong = t1->getRecord(64); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; +} + +TEST_F(DataBaseTest, Persistence) { + EXPECT_EQ(db->ready(), true); + db->close(); + delete db; + + db = new DataBase("testBase"); + t1 = db->addTable("table1"); + t2 = db->addTable("table2"); + db->open(); + + EXPECT_EQ(t1->getRecord(3), 15); + EXPECT_EQ(t1->getRecord(1), 2); + EXPECT_EQ(t1->getRecord(2), 2); + EXPECT_EQ(t2->getRecord("hello"), "world"); + EXPECT_EQ(t2->getRecord("aaa"), "gagdfsdf"); + EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); + EXPECT_EQ(t2->getRecord("sdfsda"), "shgsdgfa"); + + bool thrown = false; + try { + QString wrong = t2->getRecord("cats"); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + thrown = false; + try { + uint32_t wrong = t1->getRecord(7893); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; +} + +TEST_F(DataBaseTest, CountAndDrop) { + EXPECT_EQ(db->ready(), true); + EXPECT_EQ(t1->count(), 3); + EXPECT_EQ(t2->count(), 4); + + db->drop(); + + EXPECT_EQ(t1->count(), 0); + EXPECT_EQ(t2->count(), 0); + + t1->addRecord(2, 2); + t2->addRecord("sdfhga", "world"); + + EXPECT_EQ(t1->count(), 1); + EXPECT_EQ(t2->count(), 1); +} + From a613eaed2731a58e1b78f3faff7d539ad002c11d Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 20 Sep 2022 20:16:48 +0300 Subject: [PATCH 011/125] first thoughts about cache --- CMakeLists.txt | 2 + cache.h | 53 +++++++++++++++++++++++ cache.hpp | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ database.h | 24 +++++++++++ table.h | 12 +++--- test/basic.cpp | 7 ++- 6 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 cache.h create mode 100644 cache.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cd7f21..26e1b4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,8 @@ set(HEADERS exceptions.h table.h table.hpp + cache.h + cache.hpp serializer.h serializer.hpp serializer_uint8.hpp diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..6432347 --- /dev/null +++ b/cache.h @@ -0,0 +1,53 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DATABASE_CACHE_H +#define DATABASE_CACHE_H + +#include "database.h" +#include "table.h" +#include +#include + +template +class DataBase::Cache : public DataBase::Table { + friend class DataBase; + enum class Mode { + nothing, + size, + full + }; +protected: + Cache(const std::string& name, DataBase* parent); + ~Cache() override; + +public: + virtual void addRecord(const K& key, const V& value) override; + virtual void changeRecord(const K& key, const V& value) override; + virtual void removeRecord(const K& key) override; + virtual V getRecord(const K& key) const override; + //virtual uint32_t count() const; + //virtual void drop(); + +protected: + Mode mode; + std::map* cache; + std::set* abscent; +}; + +#include "cache.hpp" + +#endif // DATABASE_CACHE_H diff --git a/cache.hpp b/cache.hpp new file mode 100644 index 0000000..47a678a --- /dev/null +++ b/cache.hpp @@ -0,0 +1,114 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DATABASE_CACHE_HPP +#define DATABASE_CACHE_HPP + +#include "cache.h" +#include "exceptions.h" + +template +DataBase::Cache::Cache(const std::string& p_name, DataBase* parent): + DataBase::Table(p_name, parent), + mode(Mode::nothing), + cache(new std::map()), + abscent(new std::set()) +{ +} + +template +DataBase::Cache::~Cache() { + delete cache; + delete abscent; +} + +template +void DataBase::Cache::addRecord(const K& key, const V& value) { + if (!DataBase::Table::db->opened) { + throw Closed("addRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + if (cache->count(key) > 0) { + throw Exist(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } + + Table::addRecord(key, value); + cache->insert(std::make_pair(key, value)); +} + + + +template +void DataBase::Cache::changeRecord(const K& key, const V& value) { + if (!DataBase::Table::db->opened) { + throw Closed("changeRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + typename std::map::iterator itr = cache->find(key); + if (itr == cache->end() || abscent->count(key) > 0) { + throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } + + try { + Table::changeRecord(key, value); + itr->second = value; + } catch (const NotFound& error) { + abscent->insert(key); + throw error; + } +} + +template +V DataBase::Cache::getRecord(const K& key) const { + if (!DataBase::Table::db->opened) { + throw Closed("getRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + typename std::map::const_iterator itr = cache->find(key); + if (itr != cache->end()) { + return itr->second; + } + + if (abscent->count(key) == 0) { + throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } + + try { + V value = Table::getRecord(key); + cache->insert(std::make_pair(key, value)); + return value; + } catch (const NotFound& error) { + abscent->insert(key); + throw error; + } +} + +template +void DataBase::Cache::removeRecord(const K& key) { + if (!DataBase::Table::db->opened) { + throw Closed("removeRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + typename std::pair::const_iterator, bool> pair = abscent->insert(key); + if (!pair.second) { + throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } + + cache->erase(key); + Table::removeRecord(key); +} + +#endif //DATABASE_CACHE_HPP diff --git a/database.h b/database.h index 167fc95..98ce4cf 100644 --- a/database.h +++ b/database.h @@ -33,6 +33,8 @@ class DataBase public: template class Table; + template + class Cache; DataBase(const QString& name, uint16_t mapSize = 10); ~DataBase(); @@ -47,9 +49,15 @@ public: template Table* addTable(const std::string& name); + template + Cache* addCache(const std::string& name); + template Table* getTable(const std::string& name); + template + Cache* getCache(const std::string& name); + public: //exceptions class Exception; @@ -80,9 +88,25 @@ DataBase::Table* DataBase::addTable(const std::string& p_name) { return table; } +template +DataBase::Cache * DataBase::addCache(const std::string& p_name) { + if (opened) { + throw Opened(name, "add cache " + p_name); + } + DataBase::Cache* cache = new DataBase::Cache(p_name, this); + tables.insert(std::make_pair(p_name, (_Table*)cache)); + return cache; +} + template DataBase::Table* DataBase::getTable(const std::string& p_name) { return static_cast*>(tables.at(p_name)); } +template +DataBase::Cache* DataBase::getCache(const std::string& p_name) { + return static_cast*>(tables.at(p_name)); +} + + #endif // CORE_DATABASE_H diff --git a/table.h b/table.h index 082518c..51d2335 100644 --- a/table.h +++ b/table.h @@ -49,17 +49,17 @@ protected: template class DataBase::Table : public DataBase::_Table { friend class DataBase; -private: +protected: Table(const std::string& name, DataBase* parent); ~Table() override; public: - void addRecord(const K& key, const V& value); - void changeRecord(const K& key, const V& value); - void removeRecord(const K& key); - V getRecord(const K& key) const; + virtual void addRecord(const K& key, const V& value); + virtual void changeRecord(const K& key, const V& value); + virtual void removeRecord(const K& key); + virtual V getRecord(const K& key) const; -private: +protected: Serializer* keySerializer; Serializer* valueSerializer; diff --git a/test/basic.cpp b/test/basic.cpp index e68f521..6b39acf 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -2,6 +2,7 @@ #include "database.h" #include "table.h" +#include "cache.h" #include @@ -10,7 +11,8 @@ protected: DataBaseTest(): ::testing::Test(), t1(db->getTable("table1")), - t2(db->getTable("table2")) {} + t2(db->getTable("table2")), + c1(db->getCache("cache1")) {} ~DataBaseTest() {} @@ -19,6 +21,7 @@ protected: db = new DataBase("testBase"); db->addTable("table1"); db->addTable("table2"); + db->addCache("cache1"); } } @@ -33,6 +36,7 @@ protected: DataBase::Table* t1; DataBase::Table* t2; + DataBase::Cache* c1; }; @@ -120,6 +124,7 @@ TEST_F(DataBaseTest, Persistence) { db = new DataBase("testBase"); t1 = db->addTable("table1"); t2 = db->addTable("table2"); + c1 = db->addCache("cache1"); db->open(); EXPECT_EQ(t1->getRecord(3), 15); From c23dae2a253d91a2f4080e9584588f36c4cd147f Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 8 Oct 2022 19:45:07 +0300 Subject: [PATCH 012/125] some debug, some idea how to utilize home hints of data by cache --- cache.h | 18 ++++++----- cache.hpp | 94 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/cache.h b/cache.h index 6432347..9ba0a3d 100644 --- a/cache.h +++ b/cache.h @@ -25,27 +25,31 @@ template class DataBase::Cache : public DataBase::Table { friend class DataBase; - enum class Mode { - nothing, - size, - full + enum class Mode { //it's a cache state when we: + nothing, // - know nothing about records in database on disk + size, // - know just an amount of records + full // - shure that our cache is equal to the database on disk }; protected: Cache(const std::string& name, DataBase* parent); ~Cache() override; +private: + void handleMode() const; + public: virtual void addRecord(const K& key, const V& value) override; virtual void changeRecord(const K& key, const V& value) override; virtual void removeRecord(const K& key) override; virtual V getRecord(const K& key) const override; - //virtual uint32_t count() const; - //virtual void drop(); + virtual uint32_t count() const override; + virtual void drop() override; protected: - Mode mode; + Mode* mode; std::map* cache; std::set* abscent; + uint32_t* sizeDifference; }; #include "cache.hpp" diff --git a/cache.hpp b/cache.hpp index 47a678a..fc05ab6 100644 --- a/cache.hpp +++ b/cache.hpp @@ -23,14 +23,19 @@ template DataBase::Cache::Cache(const std::string& p_name, DataBase* parent): DataBase::Table(p_name, parent), - mode(Mode::nothing), + mode(new Mode), cache(new std::map()), - abscent(new std::set()) + abscent(new std::set()), + sizeDifference(new uint32_t) { + *mode = Mode::nothing; + *sizeDifference = 0; } template DataBase::Cache::~Cache() { + delete sizeDifference; + delete mode; delete cache; delete abscent; } @@ -50,24 +55,37 @@ void DataBase::Cache::addRecord(const K& key, const V& value) { } - template void DataBase::Cache::changeRecord(const K& key, const V& value) { if (!DataBase::Table::db->opened) { throw Closed("changeRecord", DataBase::Table::db->name, DataBase::Table::name); } - typename std::map::iterator itr = cache->find(key); - if (itr == cache->end() || abscent->count(key) > 0) { - throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } - - try { + if (*mode == Mode::full) { + typename std::map::iterator itr = cache->find(key); + if (itr != cache->end()) { + throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } Table::changeRecord(key, value); itr->second = value; - } catch (const NotFound& error) { - abscent->insert(key); - throw error; + } else { + if (abscent->count(key) > 0) { + throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); + } + + try { + Table::changeRecord(key, value); + typename std::map::iterator itr = cache->find(key); + if (itr != cache->end()) { + itr->second = value; + } else { + cache->insert(std::make_pair(key, value)); + handleMode(); + } + } catch (const NotFound& error) { + abscent->insert(key); + throw error; + } } } @@ -82,13 +100,14 @@ V DataBase::Cache::getRecord(const K& key) const { return itr->second; } - if (abscent->count(key) == 0) { + if (*mode == Mode::full || abscent->count(key) == 0) { throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); } try { V value = Table::getRecord(key); cache->insert(std::make_pair(key, value)); + handleMode(); return value; } catch (const NotFound& error) { abscent->insert(key); @@ -107,8 +126,55 @@ void DataBase::Cache::removeRecord(const K& key) { throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); } - cache->erase(key); Table::removeRecord(key); + if (cache->erase(key) == 0) { //if it was not cached and we are now in size mode then the sizeDifference would decrease + handleMode(); + } + if (*mode != Mode::full) { + abscent->insert(key); + } +} + +template +uint32_t DataBase::Cache::count() const { + switch (*mode) { + case Mode::nothing: + { + uint32_t sz = DataBase::Table::count(); + *sizeDifference = sz - cache->size(); + if (sz == 0) { + *mode = Mode::full; + abscent->clear(); + } else { + *mode = Mode::size; + } + return sz; + } + case Mode::size: + return cache->size() + *sizeDifference; + case Mode::full: + return cache->size(); + } +} + +template +void DataBase::Cache::handleMode() const { + if (*mode == Mode::size) { + --(*sizeDifference); + if (*sizeDifference == 0) { + *mode = Mode::full; + abscent->clear(); + } + } +} + +template +void DataBase::Cache::drop() { + DataBase::Table::drop(); + cache->clear(); + abscent->clear(); + *mode = Mode::full; + *sizeDifference = 0; } #endif //DATABASE_CACHE_HPP From ceb6df6ecab8e7dd3c0eb1b0b00432bdfeda1439 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 9 Oct 2022 17:03:30 +0300 Subject: [PATCH 013/125] some test cases for cache, found some bugs, fixed --- cache.h | 2 +- cache.hpp | 7 ++++--- test/basic.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 9ba0a3d..8bd3c4b 100644 --- a/cache.h +++ b/cache.h @@ -43,7 +43,7 @@ public: virtual void removeRecord(const K& key) override; virtual V getRecord(const K& key) const override; virtual uint32_t count() const override; - virtual void drop() override; + virtual int drop(MDB_txn * transaction) override; protected: Mode* mode; diff --git a/cache.hpp b/cache.hpp index fc05ab6..6a36df9 100644 --- a/cache.hpp +++ b/cache.hpp @@ -100,7 +100,7 @@ V DataBase::Cache::getRecord(const K& key) const { return itr->second; } - if (*mode == Mode::full || abscent->count(key) == 0) { + if (*mode == Mode::full || abscent->count(key) != 0) { throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); } @@ -169,12 +169,13 @@ void DataBase::Cache::handleMode() const { } template -void DataBase::Cache::drop() { - DataBase::Table::drop(); +int DataBase::Cache::drop(MDB_txn * transaction) { + int res = DataBase::Table::drop(transaction); cache->clear(); abscent->clear(); *mode = Mode::full; *sizeDifference = 0; + return res; } #endif //DATABASE_CACHE_HPP diff --git a/test/basic.cpp b/test/basic.cpp index 6b39acf..499c0ee 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -73,6 +73,16 @@ TEST_F(DataBaseTest, AddingQStringKey) { EXPECT_EQ(t2->getRecord("hello"), "world"); } +TEST_F(DataBaseTest, AddingKeysToCache) { + EXPECT_EQ(db->ready(), true); + c1->addRecord(2, "blah balah"); + c1->addRecord(-4, "testing goes brrr"); + c1->addRecord(140, "whatever"); + c1->addRecord(-37, "aaaaa tss tsss tsss tsss aaaaaaa"); + EXPECT_EQ(c1->getRecord(140), "whatever"); + EXPECT_EQ(c1->getRecord(-116), "whatever"); +} + TEST_F(DataBaseTest, AddingRepeatingIntegerKey) { EXPECT_EQ(db->ready(), true); bool thrown = false; @@ -97,6 +107,18 @@ TEST_F(DataBaseTest, AddingRepeatingStringKey) { EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); } +TEST_F(DataBaseTest, AddingRepeatingCacheKey) { + EXPECT_EQ(db->ready(), true); + bool thrown = false; + try { + c1->addRecord(-4, "world"); + } catch (const DataBase::Exist e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); +} + TEST_F(DataBaseTest, GettingNotExistingKeys) { EXPECT_EQ(db->ready(), true); bool thrown = false; @@ -114,6 +136,14 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + thrown = false; + try { + std::string wrong = c1->getRecord(21); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; } TEST_F(DataBaseTest, Persistence) { @@ -130,11 +160,18 @@ TEST_F(DataBaseTest, Persistence) { EXPECT_EQ(t1->getRecord(3), 15); EXPECT_EQ(t1->getRecord(1), 2); EXPECT_EQ(t1->getRecord(2), 2); + EXPECT_EQ(t2->getRecord("hello"), "world"); EXPECT_EQ(t2->getRecord("aaa"), "gagdfsdf"); EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); EXPECT_EQ(t2->getRecord("sdfsda"), "shgsdgfa"); + EXPECT_EQ(c1->getRecord(-116), "whatever"); + EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); + EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); + EXPECT_EQ(c1->getRecord(-37), "aaaaa tss tsss tsss tsss aaaaaaa"); + EXPECT_EQ(c1->getRecord(2), "blah balah"); + bool thrown = false; try { QString wrong = t2->getRecord("cats"); @@ -150,22 +187,35 @@ TEST_F(DataBaseTest, Persistence) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + thrown = false; + try { + std::string wrong = c1->getRecord(89); + } catch (const DataBase::NotFound e) { + thrown = true; + } + ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; } TEST_F(DataBaseTest, CountAndDrop) { EXPECT_EQ(db->ready(), true); EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t2->count(), 4); + EXPECT_EQ(c1->count(), 4); db->drop(); EXPECT_EQ(t1->count(), 0); EXPECT_EQ(t2->count(), 0); + EXPECT_EQ(c1->count(), 0); t1->addRecord(2, 2); t2->addRecord("sdfhga", "world"); + c1->addRecord(15, "world"); + c1->addRecord(12, "grr grr"); EXPECT_EQ(t1->count(), 1); EXPECT_EQ(t2->count(), 1); + EXPECT_EQ(c1->count(), 2); } From e7f1d48db622188ce5512a31fdf961be625b9fa2 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 10 Oct 2022 23:51:48 +0300 Subject: [PATCH 014/125] some fixes to help build in nested project --- CMakeLists.txt | 6 +++++- database.h | 1 + exceptions.h | 2 ++ serializer.h | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26e1b4a..7d774e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,9 @@ option(BUILD_STATIC "Builds library as static library" ON) option(BUILD_TESTS "Builds tests" ON) include(GNUInstallDirs) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") @@ -68,7 +71,8 @@ endif () set_target_properties(storage PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(storage PUBLIC ${CMAKE_SOURCE_DIR}) +target_include_directories(storage PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) diff --git a/database.h b/database.h index 98ce4cf..a56a5a4 100644 --- a/database.h +++ b/database.h @@ -19,6 +19,7 @@ #include #include +#include #include #include diff --git a/exceptions.h b/exceptions.h index 2d06535..88c2e6a 100644 --- a/exceptions.h +++ b/exceptions.h @@ -19,6 +19,8 @@ #include #include +#include + #include "database.h" class DataBase::Exception : public std::exception diff --git a/serializer.h b/serializer.h index 82f9880..3350e8b 100644 --- a/serializer.h +++ b/serializer.h @@ -17,6 +17,8 @@ #ifndef CORE_DATABASE_SERIALIZER_H #define CORE_DATABASE_SERIALIZER_H +#include + #include #include #include From 24e1dca5eaf44f740d4edd665e13d42a493877c3 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 10 Oct 2022 23:53:58 +0300 Subject: [PATCH 015/125] one more fix --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d774e6..eab713e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,6 @@ if (NOT DEFINED QT_VERSION_MAJOR) endif() find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) -qt_standard_project_setup() - find_package(LMDB REQUIRED) # Build type From 928558539c2be2181c2e2019f617ec573b9974a5 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 15 Oct 2022 13:54:34 +0300 Subject: [PATCH 016/125] serializer for QByteArray, method to read all records --- CMakeLists.txt | 1 + cache.h | 1 + cache.hpp | 16 +++++++++++ serializer.h | 1 + serializer_qbytearray.hpp | 58 +++++++++++++++++++++++++++++++++++++++ table.h | 1 + table.hpp | 33 ++++++++++++++++++++++ 7 files changed, 111 insertions(+) create mode 100644 serializer_qbytearray.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eab713e..9eb708d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ set(HEADERS serializer_double.hpp serializer_stdstring.hpp serializer_qstring.hpp + serializer_qbytearray.hpp ) if (BUILD_STATIC) diff --git a/cache.h b/cache.h index 8bd3c4b..4c30fac 100644 --- a/cache.h +++ b/cache.h @@ -44,6 +44,7 @@ public: virtual V getRecord(const K& key) const override; virtual uint32_t count() const override; virtual int drop(MDB_txn * transaction) override; + virtual std::map readAll() const override; protected: Mode* mode; diff --git a/cache.hpp b/cache.hpp index 6a36df9..9a509b3 100644 --- a/cache.hpp +++ b/cache.hpp @@ -115,6 +115,22 @@ V DataBase::Cache::getRecord(const K& key) const { } } +template +std::map DataBase::Cache::readAll() const { + if (!DataBase::Table::db->opened) { + throw Closed("readAll", DataBase::Table::db->name, DataBase::Table::name); + } + + if (*mode != Mode::full) { //there is a room for optimization + *mode = Mode::full; //I can read and deserialize only those values + *cache = DataBase::Table::readAll(); //that are missing in the cache + abscent->clear(); + *sizeDifference = 0; + } + + return *cache; +} + template void DataBase::Cache::removeRecord(const K& key) { if (!DataBase::Table::db->opened) { diff --git a/serializer.h b/serializer.h index 3350e8b..9ff3c18 100644 --- a/serializer.h +++ b/serializer.h @@ -61,5 +61,6 @@ private: #include "serializer_double.hpp" #include "serializer_stdstring.hpp" #include "serializer_qstring.hpp" +#include "serializer_qbytearray.hpp" #endif // CORE_DATABASE_SERIALIZER_H diff --git a/serializer_qbytearray.hpp b/serializer_qbytearray.hpp new file mode 100644 index 0000000..741bf34 --- /dev/null +++ b/serializer_qbytearray.hpp @@ -0,0 +1,58 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP +#define CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP + +#include + +template<> +class DataBase::Serializer +{ +public: + Serializer():value() {}; + Serializer(const QByteArray& p_value):value(p_value) {}; + ~Serializer() {}; + + QByteArray deserialize(const MDB_val& data) { + value.setRawData((char*)data.mv_data, data.mv_size); + return value; + }; + MDB_val setData(const QByteArray& data) { + value = data; + return getData(); + }; + MDB_val getData() { + MDB_val result; + result.mv_data = value.data(); + result.mv_size = value.size(); + return result; + }; + void clear() { + value.clear(); + }; + +private: + QByteArray value; +}; + + +#endif //CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP + + + + diff --git a/table.h b/table.h index 51d2335..a2abe81 100644 --- a/table.h +++ b/table.h @@ -58,6 +58,7 @@ public: virtual void changeRecord(const K& key, const V& value); virtual void removeRecord(const K& key); virtual V getRecord(const K& key) const; + virtual std::map readAll() const; protected: Serializer* keySerializer; diff --git a/table.hpp b/table.hpp index 8fa315b..db4f428 100644 --- a/table.hpp +++ b/table.hpp @@ -110,6 +110,39 @@ V DataBase::Table::getRecord(const K& key) const { } } +template +std::map DataBase::Table::readAll() const { + if (!db->opened) { + throw Closed("readAll", db->name, name); + } + + std::map result; + MDB_txn *txn; + MDB_cursor* cursor; + MDB_val lmdbKey, lmdbData; + int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + if (rc != MDB_SUCCESS) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_cursor_open(txn, dbi, &cursor); + if (rc != MDB_SUCCESS) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + while (rc == MDB_SUCCESS) { + K key = keySerializer->deserialize(lmdbKey); + V value = valueSerializer->deserialize(lmdbData); + result.insert(std::make_pair(key, value)); + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); + } + + if (rc != MDB_NOTFOUND) { + throw Unknown(db->name, mdb_strerror(rc), name); + } + + return result; +} + template void DataBase::Table::removeRecord(const K& key) { if (!db->opened) { From 08daad48ad36d9f12dc6485a085190e31e4cf49e Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 14 Dec 2022 01:57:49 +0300 Subject: [PATCH 017/125] methods for addition with overwrite --- cache.h | 1 + cache.hpp | 27 +++++++++++++++++++++++++++ table.h | 1 + table.hpp | 31 +++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/cache.h b/cache.h index 4c30fac..4f9ccbf 100644 --- a/cache.h +++ b/cache.h @@ -39,6 +39,7 @@ private: public: virtual void addRecord(const K& key, const V& value) override; + virtual bool forceRecord(const K& key, const V& value) override; virtual void changeRecord(const K& key, const V& value) override; virtual void removeRecord(const K& key) override; virtual V getRecord(const K& key) const override; diff --git a/cache.hpp b/cache.hpp index 9a509b3..98485c5 100644 --- a/cache.hpp +++ b/cache.hpp @@ -52,8 +52,35 @@ void DataBase::Cache::addRecord(const K& key, const V& value) { Table::addRecord(key, value); cache->insert(std::make_pair(key, value)); + + if (*mode != Mode::full) { + abscent->erase(key); + } } +template +bool DataBase::Cache::forceRecord(const K& key, const V& value) { + if (!DataBase::Table::db->opened) { + throw Closed("forceRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + bool added = Table::forceRecord(key, value); + if (*mode == Mode::full) { + (*cache)[key] = value; + } else { + if (added) { + abscent->erase(key); + } + std::pair::iterator, bool> result = cache->insert(std::make_pair(key, value)); + if (!result.second) { + result.first->second = value; + } else if (!added) { //this way database had value but cache didn't, so, need to decrease sizeDifference + handleMode(); + } + } + + return added; +} template void DataBase::Cache::changeRecord(const K& key, const V& value) { diff --git a/table.h b/table.h index a2abe81..fa647f7 100644 --- a/table.h +++ b/table.h @@ -55,6 +55,7 @@ protected: public: virtual void addRecord(const K& key, const V& value); + virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change virtual void changeRecord(const K& key, const V& value); virtual void removeRecord(const K& key); virtual V getRecord(const K& key) const; diff --git a/table.hpp b/table.hpp index db4f428..c4d0bf8 100644 --- a/table.hpp +++ b/table.hpp @@ -58,7 +58,38 @@ void DataBase::Table::addRecord(const K& key, const V& value) { } } +template +bool DataBase::Table::forceRecord(const K& key, const V& value) { + if (!db->opened) { + throw Closed("forceRecord", db->name, name); + } + bool added; + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData; + MDB_txn *txn; + mdb_txn_begin(db->environment, NULL, 0, &txn); + int rc; + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + if (rc == 0) { + added = false; + } else if (rc == MDB_NOTFOUND) { + added = true; + } else { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + lmdbData = valueSerializer->setData(value); + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } else { + mdb_txn_commit(txn); + } + + return added; +} template void DataBase::Table::changeRecord(const K& key, const V& value) { From 6ff22c937720e5ebc7406fcccaa5cc77ba13da63 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 15 Dec 2022 20:29:49 +0300 Subject: [PATCH 018/125] methods to check elements without deserialization --- cache.h | 1 + cache.hpp | 32 +++++++++++++++++++++++++++++++- table.h | 1 + table.hpp | 26 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/cache.h b/cache.h index 4f9ccbf..00e3042 100644 --- a/cache.h +++ b/cache.h @@ -42,6 +42,7 @@ public: virtual bool forceRecord(const K& key, const V& value) override; virtual void changeRecord(const K& key, const V& value) override; virtual void removeRecord(const K& key) override; + virtual bool checkRecord(const K& key) const override; virtual V getRecord(const K& key) const override; virtual uint32_t count() const override; virtual int drop(MDB_txn * transaction) override; diff --git a/cache.hpp b/cache.hpp index 98485c5..89011ae 100644 --- a/cache.hpp +++ b/cache.hpp @@ -137,11 +137,41 @@ V DataBase::Cache::getRecord(const K& key) const { handleMode(); return value; } catch (const NotFound& error) { - abscent->insert(key); + if (*mode != Mode::full) { + abscent->insert(key); + } throw error; } } +template +bool DataBase::Cache::checkRecord(const K& key) const { + if (!DataBase::Table::db->opened) { + throw Closed("checkRecord", DataBase::Table::db->name, DataBase::Table::name); + } + + typename std::map::const_iterator itr = cache->find(key); + if (itr != cache->end()) { + return true; + } + + if (*mode == Mode::full || abscent->count(key) != 0) { + return false; + } + + try { + V value = Table::getRecord(key); + cache->insert(std::make_pair(key, value)); + handleMode(); + return true; + } catch (const NotFound& error) { + if (*mode != Mode::full) { + abscent->insert(key); + } + return false; + } +} + template std::map DataBase::Cache::readAll() const { if (!DataBase::Table::db->opened) { diff --git a/table.h b/table.h index fa647f7..8803e2e 100644 --- a/table.h +++ b/table.h @@ -58,6 +58,7 @@ public: virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change virtual void changeRecord(const K& key, const V& value); virtual void removeRecord(const K& key); + virtual bool checkRecord(const K& key) const; //checks if there is a record with given key virtual V getRecord(const K& key) const; virtual std::map readAll() const; diff --git a/table.hpp b/table.hpp index c4d0bf8..573c828 100644 --- a/table.hpp +++ b/table.hpp @@ -141,6 +141,32 @@ V DataBase::Table::getRecord(const K& key) const { } } +template +bool DataBase::Table::checkRecord(const K& key) const { + if (!db->opened) { + throw Closed("checkRecord", db->name, name); + } + + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData; + + MDB_txn *txn; + int rc; + mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + mdb_txn_abort(txn); + + if (rc) { + if (rc == MDB_NOTFOUND) { + return false; + } else { + throw Unknown(db->name, mdb_strerror(rc), name); + } + } else { + return true; + } +} + template std::map DataBase::Table::readAll() const { if (!db->opened) { From 6ae32e38b6fa7f92d459c66bfd3a6d19d8b44b06 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 17 Dec 2022 13:06:37 +0300 Subject: [PATCH 019/125] creation of the directory is no a separate method --- database.cpp | 32 ++++++++++++++++++++++---------- database.h | 2 ++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/database.cpp b/database.cpp index 8904bf7..fc114dc 100644 --- a/database.cpp +++ b/database.cpp @@ -51,16 +51,7 @@ void DataBase::open() { if (!opened) { mdb_env_create(&environment); - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + getName(); - QDir cache(path); - - if (!cache.exists()) { - bool res = cache.mkpath(path); - if (!res) { - throw Directory(path.toStdString()); - } - } + QString path = createDirectory(); mdb_env_set_maxdbs(environment, tables.size()); mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); @@ -98,6 +89,27 @@ bool DataBase::removeDirectory() } } +QString DataBase::createDirectory() +{ + if (opened) { + throw Opened(name, "create database directory"); + } + + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + getName(); + QDir cache(path); + + if (!cache.exists()) { + bool res = cache.mkpath(path); + if (!res) { + throw Directory(path.toStdString()); + } + } + + return path; +} + + QString DataBase::getName() const { return QString::fromStdString(name); diff --git a/database.h b/database.h index a56a5a4..b1903b5 100644 --- a/database.h +++ b/database.h @@ -44,9 +44,11 @@ public: void close(); bool ready() const; bool removeDirectory(); + QString createDirectory(); QString getName() const; void drop(); + template Table* addTable(const std::string& name); From a79dae8fd07b36446041f6f85e2ad42cf5ae5264 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 18 Dec 2022 17:45:12 +0300 Subject: [PATCH 020/125] methods to bulk modify the storage, some transaction code hardening, operators to serialize std containers --- CMakeLists.txt | 1 + cache.h | 4 + cache.hpp | 45 ++++++++++- database.h | 1 + operators.hpp | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ table.cpp | 3 + table.h | 3 + table.hpp | 113 +++++++++++++++++++++++--- 8 files changed, 365 insertions(+), 14 deletions(-) create mode 100644 operators.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eb708d..9a400b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ set(HEADERS serializer_stdstring.hpp serializer_qstring.hpp serializer_qbytearray.hpp + operators.hpp ) if (BUILD_STATIC) diff --git a/cache.h b/cache.h index 00e3042..6f7042c 100644 --- a/cache.h +++ b/cache.h @@ -45,8 +45,12 @@ public: virtual bool checkRecord(const K& key) const override; virtual V getRecord(const K& key) const override; virtual uint32_t count() const override; + + using DataBase::Table::drop; virtual int drop(MDB_txn * transaction) override; virtual std::map readAll() const override; + virtual void replaceAll(const std::map& data) override; + virtual uint32_t addRecords(const std::map& data, bool overwrite = false) override; protected: Mode* mode; diff --git a/cache.hpp b/cache.hpp index 89011ae..503b2f4 100644 --- a/cache.hpp +++ b/cache.hpp @@ -102,11 +102,10 @@ void DataBase::Cache::changeRecord(const K& key, const V& value) { try { Table::changeRecord(key, value); - typename std::map::iterator itr = cache->find(key); - if (itr != cache->end()) { - itr->second = value; + typename std::pair::iterator, bool> res = cache->insert(std::make_pair(key, value)); + if (!res.second) { + res.first->second = value; } else { - cache->insert(std::make_pair(key, value)); handleMode(); } } catch (const NotFound& error) { @@ -188,6 +187,44 @@ std::map DataBase::Cache::readAll() const { return *cache; } +template +void DataBase::Cache::replaceAll(const std::map& data) { + DataBase::Table::replaceAll(data); + *cache = data; +} + +template +uint32_t DataBase::Cache::addRecords(const std::map& data, bool overwrite) { + uint32_t newSize = DataBase::Table::addRecords(data, overwrite); + + Mode& m = *mode; + if (m == Mode::nothing) { + m = Mode::size; + } + std::map& c = *cache; + std::set& a = *abscent; + for (const std::pair& pair : data) { + std::pair::iterator, bool> res = c.insert(pair); + if (!res.second) { + if (overwrite) { + res.first->second = pair.second; + } + } else if (m != Mode::full) { + a.erase(pair.first); + } + } + + if (m != Mode::full) { + *sizeDifference = newSize - c.size(); + if (*sizeDifference == 0) { + *mode = Mode::full; + abscent->clear(); + } + } + + return newSize; +} + template void DataBase::Cache::removeRecord(const K& key) { if (!DataBase::Table::db->opened) { diff --git a/database.h b/database.h index b1903b5..005c1d9 100644 --- a/database.h +++ b/database.h @@ -80,6 +80,7 @@ private: }; #include "exceptions.h" +#include "operators.hpp" template DataBase::Table* DataBase::addTable(const std::string& p_name) { diff --git a/operators.hpp b/operators.hpp new file mode 100644 index 0000000..c4010ff --- /dev/null +++ b/operators.hpp @@ -0,0 +1,209 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef CORE_OPERATORS_HPP +#define CORE_OPERATORS_HPP + +#include +#include +#include +#include +#include + +#include + +template +QDataStream& operator << (QDataStream &out, const std::map& container) { + uint32_t size = container.size(); + out << size; + for (const std::pair& pair : container) { + out << pair; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::map& container) { + uint32_t size; + in >> size; + for (uint32_t i = 0; i < size; ++i) { + std::pair pair; + in >> pair; + container.insert(pair); + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::multimap& container) { + uint32_t size = container.size(); + out << size; + for (const std::pair& pair : container) { + out << pair; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::multimap& container) { + uint32_t size; + in >> size; + for (uint32_t i = 0; i < size; ++i) { + std::pair pair; + in >> pair; + container.insert(pair); + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::pair& pair) { + out << pair.first; + out << pair.second; + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::pair& container) { + in >> container.first; + in >> container.second; + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::set& container) { + uint32_t size = container.size(); + out << size; + for (const K& value : container) { + out << value; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::set& container) { + uint32_t size; + in >> size; + for (uint32_t i = 0; i < size; ++i) { + K value; + in >> value; + container.insert(value); + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::multiset& container) { + uint32_t size = container.size(); + out << size; + for (const K& value : container) { + out << value; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::multiset& container) { + uint32_t size; + in >> size; + for (uint32_t i = 0; i < size; ++i) { + K value; + in >> value; + container.insert(value); + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::vector& container) { + uint32_t size = container.size(); + out << size; + for (const K& value : container) { + out << value; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::vector& container) { + uint32_t size; + in >> size; + container.resize(size); + for (uint32_t i = 0; i < size; ++i) { + in >> container[i]; + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::deque& container) { + uint32_t size = container.size(); + out << size; + for (const K& value : container) { + out << value; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::deque& container) { + uint32_t size; + in >> size; + container.resize(size); + for (uint32_t i = 0; i < size; ++i) { + in >> container[i]; + } + + return in; +} + +template +QDataStream& operator << (QDataStream &out, const std::list& container) { + uint32_t size = container.size(); + out << size; + for (const K& value : container) { + out << value; + } + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::list& container) { + uint32_t size; + in >> size; + for (uint32_t i = 0; i < size; ++i) { + typename std::list::iterator itr = container.emplace(container.end()); + in >> *itr; + } + + return in; +} + +#endif //CORE_OPERATORS_HPP diff --git a/table.cpp b/table.cpp index 141df13..80ac7bc 100644 --- a/table.cpp +++ b/table.cpp @@ -35,10 +35,12 @@ void DataBase::_Table::drop() MDB_txn *txn; int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); if (rc) { + mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } rc = drop(txn); if (rc) { + mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } @@ -61,6 +63,7 @@ uint32_t DataBase::_Table::count() const int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); rc = mdb_stat(txn, dbi, &stat); if (rc) { + mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } uint32_t amount = stat.ms_entries; diff --git a/table.h b/table.h index 8803e2e..8279237 100644 --- a/table.h +++ b/table.h @@ -54,6 +54,7 @@ protected: ~Table() override; public: + using DataBase::_Table::drop; virtual void addRecord(const K& key, const V& value); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change virtual void changeRecord(const K& key, const V& value); @@ -61,6 +62,8 @@ public: virtual bool checkRecord(const K& key) const; //checks if there is a record with given key virtual V getRecord(const K& key) const; virtual std::map readAll() const; + virtual void replaceAll(const std::map& data); + virtual uint32_t addRecords(const std::map& data, bool overwrite = false); protected: Serializer* keySerializer; diff --git a/table.hpp b/table.hpp index 573c828..49cb38e 100644 --- a/table.hpp +++ b/table.hpp @@ -43,8 +43,12 @@ void DataBase::Table::addRecord(const K& key, const V& value) { MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData = valueSerializer->setData(value); MDB_txn *txn; - mdb_txn_begin(db->environment, NULL, 0, &txn); - int rc; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != 0) { mdb_txn_abort(txn); @@ -68,8 +72,12 @@ bool DataBase::Table::forceRecord(const K& key, const V& value) { MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; MDB_txn *txn; - mdb_txn_begin(db->environment, NULL, 0, &txn); - int rc; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc == 0) { added = false; @@ -100,8 +108,13 @@ void DataBase::Table::changeRecord(const K& key, const V& value) { MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData = valueSerializer->setData(value); MDB_txn *txn; - mdb_txn_begin(db->environment, NULL, 0, &txn); - int rc; + + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != 0) { mdb_txn_abort(txn); @@ -123,8 +136,12 @@ V DataBase::Table::getRecord(const K& key) const { MDB_val lmdbData; MDB_txn *txn; - int rc; - mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc) { mdb_txn_abort(txn); @@ -151,8 +168,12 @@ bool DataBase::Table::checkRecord(const K& key) const { MDB_val lmdbData; MDB_txn *txn; - int rc; - mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); mdb_txn_abort(txn); @@ -179,10 +200,12 @@ std::map DataBase::Table::readAll() const { MDB_val lmdbKey, lmdbData; int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } rc = mdb_cursor_open(txn, dbi, &cursor); if (rc != MDB_SUCCESS) { + mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); @@ -193,6 +216,7 @@ std::map DataBase::Table::readAll() const { rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } + mdb_txn_abort(txn); if (rc != MDB_NOTFOUND) { throw Unknown(db->name, mdb_strerror(rc), name); } @@ -200,6 +224,75 @@ std::map DataBase::Table::readAll() const { return result; } +template +void DataBase::Table::replaceAll(const std::map& data) { + if (!db->opened) { + throw Closed("replaceAll", db->name, name); + } + + MDB_txn *txn; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = drop(txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + + MDB_val lmdbKey, lmdbData; + for (const std::pair& pair : data) { + lmdbKey = keySerializer->setData(pair.first); + lmdbData = valueSerializer->setData(pair.second); + + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != 0) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + } + mdb_txn_commit(txn); +} + +template +uint32_t DataBase::Table::addRecords(const std::map& data, bool overwrite) { + if (!db->opened) { + throw Closed("addRecords", db->name, name); + } + + MDB_txn *txn; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + + MDB_val lmdbKey, lmdbData; + for (const std::pair& pair : data) { + lmdbKey = keySerializer->setData(pair.first); + lmdbData = valueSerializer->setData(pair.second); + + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); + if (rc != 0) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + } + + MDB_stat stat; + rc = mdb_stat(txn, dbi, &stat); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + uint32_t amount = stat.ms_entries; + + mdb_txn_commit(txn); + return amount; +} + template void DataBase::Table::removeRecord(const K& key) { if (!db->opened) { From 6a8f67ac34de286588cd89e3218f55b54da47f42 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 9 Mar 2023 22:50:53 +0300 Subject: [PATCH 021/125] cache change bugfix, covered that case with unit tests, unittests for forcing records --- cache.hpp | 2 +- test/basic.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/cache.hpp b/cache.hpp index 503b2f4..811b161 100644 --- a/cache.hpp +++ b/cache.hpp @@ -90,7 +90,7 @@ void DataBase::Cache::changeRecord(const K& key, const V& value) { if (*mode == Mode::full) { typename std::map::iterator itr = cache->find(key); - if (itr != cache->end()) { + if (itr == cache->end()) { throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); } Table::changeRecord(key, value); diff --git a/test/basic.cpp b/test/basic.cpp index 499c0ee..2ee0262 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -219,3 +219,62 @@ TEST_F(DataBaseTest, CountAndDrop) { EXPECT_EQ(c1->count(), 2); } +TEST_F(DataBaseTest, Change) { + EXPECT_EQ(db->ready(), true); + EXPECT_EQ(t1->count(), 1); + EXPECT_EQ(t2->count(), 1); + EXPECT_EQ(c1->count(), 2); + + EXPECT_EQ(t1->getRecord(2), 2); + EXPECT_EQ(t2->getRecord("sdfhga"), "world"); + EXPECT_EQ(c1->getRecord(15), "world"); + EXPECT_EQ(c1->getRecord(12), "grr grr"); + + t1->addRecord(58, 39); + t2->addRecord("lawfirm", "stumble"); + c1->addRecord(89, "answer"); + + t1->changeRecord(2, 49); + t2->changeRecord("sdfhga", "void"); + c1->changeRecord(15, "recording"); + c1->changeRecord(12, "thermal"); + + EXPECT_EQ(t1->getRecord(2), 49); + EXPECT_EQ(t2->getRecord("sdfhga"), "void"); + EXPECT_EQ(c1->getRecord(15), "recording"); + EXPECT_EQ(c1->getRecord(12), "thermal"); + + EXPECT_EQ(t1->getRecord(58), 39); + EXPECT_EQ(t2->getRecord("lawfirm"), "stumble"); + EXPECT_EQ(c1->getRecord(89), "answer"); + + EXPECT_EQ(t1->count(), 2); + EXPECT_EQ(t2->count(), 2); + EXPECT_EQ(c1->count(), 3); +} + +TEST_F(DataBaseTest, Force) { + EXPECT_EQ(db->ready(), true); + + t1->forceRecord(58, 35); //changing + t1->forceRecord(68, 36); //adding + t2->forceRecord("prophecy", "dumpling"); //adding + t2->forceRecord("lawfirm", "paracetamol"); //changing + c1->forceRecord(89, "canine"); //changing + c1->forceRecord(98, "duration"); //adding + + EXPECT_EQ(t1->getRecord(2), 49); + EXPECT_EQ(t1->getRecord(58), 35); + EXPECT_EQ(t1->getRecord(68), 36); + EXPECT_EQ(t2->getRecord("sdfhga"), "void"); + EXPECT_EQ(t2->getRecord("prophecy"), "dumpling"); + EXPECT_EQ(t2->getRecord("lawfirm"), "paracetamol"); + EXPECT_EQ(c1->getRecord(15), "recording"); + EXPECT_EQ(c1->getRecord(12), "thermal"); + EXPECT_EQ(c1->getRecord(89), "canine"); + EXPECT_EQ(c1->getRecord(98), "duration"); + + EXPECT_EQ(t1->count(), 3); + EXPECT_EQ(t2->count(), 3); + EXPECT_EQ(c1->count(), 4); +} From 19229f6c2666ab65d2bb43a8ed25c82b1e8c978f Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 20 Mar 2023 18:37:13 +0300 Subject: [PATCH 022/125] big refactoring part 1 --- CMakeLists.txt | 6 +- cache.h | 18 +- cache.hpp | 174 ++++++++--------- database.cpp | 151 ++++++++++----- database.h | 90 +++++---- exceptions.cpp | 43 ++--- exceptions.h | 25 +-- serializer.h | 13 +- serializer.hpp | 23 ++- serializer_double.hpp | 11 +- serializer_float.hpp | 11 +- serializer_int16.hpp | 13 +- serializer_int32.hpp | 11 +- serializer_int64.hpp | 11 +- serializer_int8.hpp | 13 +- serializer_qbytearray.hpp | 13 +- serializer_qstring.hpp | 13 +- serializer_stdstring.hpp | 13 +- serializer_uint16.hpp | 13 +- serializer_uint32.hpp | 13 +- serializer_uint64.hpp | 13 +- serializer_uint8.hpp | 13 +- storage.cpp | 121 ++++++++++++ table.h => storage.h | 55 ++++-- storage.hpp | 291 ++++++++++++++++++++++++++++ table.cpp | 72 ------- table.hpp | 385 -------------------------------------- test/basic.cpp | 34 ++-- 28 files changed, 867 insertions(+), 795 deletions(-) create mode 100644 storage.cpp rename table.h => storage.h (51%) create mode 100644 storage.hpp delete mode 100644 table.cpp delete mode 100644 table.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a400b8..1eb24f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,15 +30,15 @@ endif () set(SOURCES exceptions.cpp - table.cpp + storage.cpp database.cpp ) set(HEADERS database.h exceptions.h - table.h - table.hpp + storage.h + storage.hpp cache.h cache.hpp serializer.h diff --git a/cache.h b/cache.h index 6f7042c..ba72f46 100644 --- a/cache.h +++ b/cache.h @@ -14,16 +14,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef DATABASE_CACHE_H -#define DATABASE_CACHE_H +#ifndef LMDBDATABASE_CACHE_H +#define LMDBDATABASE_CACHE_H -#include "database.h" -#include "table.h" #include #include +#include "storage.h" + +namespace LMDBDataBase { + template -class DataBase::Cache : public DataBase::Table { +class Cache : public Storage { friend class DataBase; enum class Mode { //it's a cache state when we: nothing, // - know nothing about records in database on disk @@ -46,7 +48,7 @@ public: virtual V getRecord(const K& key) const override; virtual uint32_t count() const override; - using DataBase::Table::drop; + using Storage::drop; virtual int drop(MDB_txn * transaction) override; virtual std::map readAll() const override; virtual void replaceAll(const std::map& data) override; @@ -59,6 +61,8 @@ protected: uint32_t* sizeDifference; }; +} + #include "cache.hpp" -#endif // DATABASE_CACHE_H +#endif // LMDBDATABASE_CACHE_H diff --git a/cache.hpp b/cache.hpp index 811b161..1212f4e 100644 --- a/cache.hpp +++ b/cache.hpp @@ -14,15 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef DATABASE_CACHE_HPP -#define DATABASE_CACHE_HPP +#ifndef LMDBDATABASE_CACHE_HPP +#define LMDBDATABASE_CACHE_HPP #include "cache.h" #include "exceptions.h" template -DataBase::Cache::Cache(const std::string& p_name, DataBase* parent): - DataBase::Table(p_name, parent), +LMDBDataBase::Cache::Cache(const std::string& p_name, DataBase* parent): + Storage(p_name, parent), mode(new Mode), cache(new std::map()), abscent(new std::set()), @@ -33,7 +33,7 @@ DataBase::Cache::Cache(const std::string& p_name, DataBase* parent): } template -DataBase::Cache::~Cache() { +LMDBDataBase::Cache::~Cache() { delete sizeDifference; delete mode; delete cache; @@ -41,73 +41,62 @@ DataBase::Cache::~Cache() { } template -void DataBase::Cache::addRecord(const K& key, const V& value) { - if (!DataBase::Table::db->opened) { - throw Closed("addRecord", DataBase::Table::db->name, DataBase::Table::name); - } +void LMDBDataBase::Cache::addRecord(const K& key, const V& value) { + StorageBase::ensureOpened(StorageBase::addRecordMethodName); - if (cache->count(key) > 0) { - throw Exist(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } + if (cache->count(key) > 0) + StorageBase::throwDuplicate(StorageBase::toString(key)); - Table::addRecord(key, value); + Storage::addRecord(key, value); cache->insert(std::make_pair(key, value)); - if (*mode != Mode::full) { + if (*mode != Mode::full) abscent->erase(key); - } } template -bool DataBase::Cache::forceRecord(const K& key, const V& value) { - if (!DataBase::Table::db->opened) { - throw Closed("forceRecord", DataBase::Table::db->name, DataBase::Table::name); - } +bool LMDBDataBase::Cache::forceRecord(const K& key, const V& value) { + StorageBase::ensureOpened(StorageBase::forceRecordMethodName); - bool added = Table::forceRecord(key, value); + bool added = Storage::forceRecord(key, value); if (*mode == Mode::full) { (*cache)[key] = value; } else { - if (added) { + if (added) abscent->erase(key); - } + std::pair::iterator, bool> result = cache->insert(std::make_pair(key, value)); - if (!result.second) { + if (!result.second) result.first->second = value; - } else if (!added) { //this way database had value but cache didn't, so, need to decrease sizeDifference + else if (!added) //this way database had value but cache didn't, so, need to decrease sizeDifference handleMode(); - } } return added; } template -void DataBase::Cache::changeRecord(const K& key, const V& value) { - if (!DataBase::Table::db->opened) { - throw Closed("changeRecord", DataBase::Table::db->name, DataBase::Table::name); - } +void LMDBDataBase::Cache::changeRecord(const K& key, const V& value) { + StorageBase::ensureOpened(StorageBase::changeRecordMethodName); if (*mode == Mode::full) { typename std::map::iterator itr = cache->find(key); - if (itr == cache->end()) { - throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } - Table::changeRecord(key, value); + if (itr == cache->end()) + StorageBase::throwNotFound(StorageBase::toString(key)); + + Storage::changeRecord(key, value); itr->second = value; } else { - if (abscent->count(key) > 0) { - throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } + if (abscent->count(key) > 0) + StorageBase::throwNotFound(StorageBase::toString(key)); try { - Table::changeRecord(key, value); + Storage::changeRecord(key, value); typename std::pair::iterator, bool> res = cache->insert(std::make_pair(key, value)); - if (!res.second) { + if (!res.second) res.first->second = value; - } else { + else handleMode(); - } } catch (const NotFound& error) { abscent->insert(key); throw error; @@ -116,70 +105,60 @@ void DataBase::Cache::changeRecord(const K& key, const V& value) { } template -V DataBase::Cache::getRecord(const K& key) const { - if (!DataBase::Table::db->opened) { - throw Closed("getRecord", DataBase::Table::db->name, DataBase::Table::name); - } +V LMDBDataBase::Cache::getRecord(const K& key) const { + StorageBase::ensureOpened(StorageBase::getRecordMethodName); typename std::map::const_iterator itr = cache->find(key); - if (itr != cache->end()) { + if (itr != cache->end()) return itr->second; - } - if (*mode == Mode::full || abscent->count(key) != 0) { - throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } + if (*mode == Mode::full || abscent->count(key) != 0) + StorageBase::throwNotFound(StorageBase::toString(key)); try { - V value = Table::getRecord(key); + V value = Storage::getRecord(key); cache->insert(std::make_pair(key, value)); handleMode(); return value; } catch (const NotFound& error) { - if (*mode != Mode::full) { + if (*mode != Mode::full) abscent->insert(key); - } + throw error; } } template -bool DataBase::Cache::checkRecord(const K& key) const { - if (!DataBase::Table::db->opened) { - throw Closed("checkRecord", DataBase::Table::db->name, DataBase::Table::name); - } +bool LMDBDataBase::Cache::checkRecord(const K& key) const { + StorageBase::ensureOpened(StorageBase::checkRecordMethodName); typename std::map::const_iterator itr = cache->find(key); - if (itr != cache->end()) { + if (itr != cache->end()) return true; - } - if (*mode == Mode::full || abscent->count(key) != 0) { + if (*mode == Mode::full || abscent->count(key) != 0) return false; - } try { - V value = Table::getRecord(key); + V value = Storage::getRecord(key); cache->insert(std::make_pair(key, value)); handleMode(); return true; } catch (const NotFound& error) { - if (*mode != Mode::full) { + if (*mode != Mode::full) abscent->insert(key); - } + return false; } } template -std::map DataBase::Cache::readAll() const { - if (!DataBase::Table::db->opened) { - throw Closed("readAll", DataBase::Table::db->name, DataBase::Table::name); - } +std::map LMDBDataBase::Cache::readAll() const { + StorageBase::ensureOpened(StorageBase::readAllMethodName); - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values - *cache = DataBase::Table::readAll(); //that are missing in the cache + if (*mode != Mode::full) { //there is a room for optimization + *mode = Mode::full; //I can read and deserialize only those values + *cache = Storage::readAll(); //that are missing in the cache abscent->clear(); *sizeDifference = 0; } @@ -188,27 +167,32 @@ std::map DataBase::Cache::readAll() const { } template -void DataBase::Cache::replaceAll(const std::map& data) { - DataBase::Table::replaceAll(data); +void LMDBDataBase::Cache::replaceAll(const std::map& data) { + Storage::replaceAll(data); *cache = data; + + if (*mode != Mode::full) { + *mode = Mode::full; + abscent->clear(); + *sizeDifference = 0; + } } template -uint32_t DataBase::Cache::addRecords(const std::map& data, bool overwrite) { - uint32_t newSize = DataBase::Table::addRecords(data, overwrite); +uint32_t LMDBDataBase::Cache::addRecords(const std::map& data, bool overwrite) { + uint32_t newSize = Storage::addRecords(data, overwrite); Mode& m = *mode; - if (m == Mode::nothing) { + if (m == Mode::nothing) m = Mode::size; - } + std::map& c = *cache; std::set& a = *abscent; for (const std::pair& pair : data) { std::pair::iterator, bool> res = c.insert(pair); if (!res.second) { - if (overwrite) { + if (overwrite) res.first->second = pair.second; - } } else if (m != Mode::full) { a.erase(pair.first); } @@ -226,31 +210,27 @@ uint32_t DataBase::Cache::addRecords(const std::map& data, bool over } template -void DataBase::Cache::removeRecord(const K& key) { - if (!DataBase::Table::db->opened) { - throw Closed("removeRecord", DataBase::Table::db->name, DataBase::Table::name); - } +void LMDBDataBase::Cache::removeRecord(const K& key) { + StorageBase::ensureOpened(StorageBase::removeRecordMethodName); typename std::pair::const_iterator, bool> pair = abscent->insert(key); - if (!pair.second) { - throw NotFound(DataBase::_Table::toString(key), DataBase::Table::db->name, DataBase::Table::name); - } + if (!pair.second) + StorageBase::throwNotFound(StorageBase::toString(key)); - Table::removeRecord(key); - if (cache->erase(key) == 0) { //if it was not cached and we are now in size mode then the sizeDifference would decrease + Storage::removeRecord(key); + if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease handleMode(); - } - if (*mode != Mode::full) { + + if (*mode != Mode::full) abscent->insert(key); - } } template -uint32_t DataBase::Cache::count() const { +uint32_t LMDBDataBase::Cache::count() const { switch (*mode) { case Mode::nothing: { - uint32_t sz = DataBase::Table::count(); + uint32_t sz = Storage::count(); *sizeDifference = sz - cache->size(); if (sz == 0) { *mode = Mode::full; @@ -264,11 +244,13 @@ uint32_t DataBase::Cache::count() const { return cache->size() + *sizeDifference; case Mode::full: return cache->size(); + default: + return 0; //unreachable, no such state, just to suppress the waring } } template -void DataBase::Cache::handleMode() const { +void LMDBDataBase::Cache::handleMode() const { if (*mode == Mode::size) { --(*sizeDifference); if (*sizeDifference == 0) { @@ -279,8 +261,8 @@ void DataBase::Cache::handleMode() const { } template -int DataBase::Cache::drop(MDB_txn * transaction) { - int res = DataBase::Table::drop(transaction); +int LMDBDataBase::Cache::drop(MDB_txn * transaction) { + int res = Storage::drop(transaction); cache->clear(); abscent->clear(); *mode = Mode::full; @@ -288,4 +270,4 @@ int DataBase::Cache::drop(MDB_txn * transaction) { return res; } -#endif //DATABASE_CACHE_HPP +#endif //LMDBDATABASE_CACHE_HPP diff --git a/database.cpp b/database.cpp index fc114dc..87f4419 100644 --- a/database.cpp +++ b/database.cpp @@ -16,30 +16,30 @@ #include "database.h" #include "exceptions.h" -#include "table.h" +#include "storage.h" -DataBase::DataBase(const QString& p_name, uint16_t mapSize): +LMDBDataBase::DataBase::DataBase(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), size(mapSize), environment(), - tables() -{ -} + tables(), + transactions(new Transactions()) +{} -DataBase::~DataBase() -{ +LMDBDataBase::DataBase::~DataBase() { close(); - for (const std::pair& pair : tables) { + + delete transactions; + + for (const std::pair& pair : tables) delete pair.second; - } } -void DataBase::close() -{ +void LMDBDataBase::DataBase::close() { if (opened) { - for (const std::pair& pair : tables) { - _Table* table = pair.second; + for (const std::pair& pair : tables) { + StorageBase* table = pair.second; mdb_dbi_close(environment, table->dbi); } mdb_env_close(environment); @@ -47,8 +47,7 @@ void DataBase::close() } } -void DataBase::open() -{ +void LMDBDataBase::DataBase::open() { if (!opened) { mdb_env_create(&environment); QString path = createDirectory(); @@ -60,40 +59,34 @@ void DataBase::open() MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); - for (const std::pair& pair : tables) { - _Table* table = pair.second; + for (const std::pair& pair : tables) { + StorageBase* table = pair.second; int rc = table->createTable(txn); - if (rc) { + if (rc) throw Unknown(name, mdb_strerror(rc)); - } - } mdb_txn_commit(txn); opened = true; } } -bool DataBase::removeDirectory() -{ - if (opened) { +bool LMDBDataBase::DataBase::removeDirectory() { + if (opened) throw Opened(name, "remove database directory"); - } + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + getName(); QDir cache(path); - if (cache.exists()) { + if (cache.exists()) return cache.removeRecursively(); - } else { + else return true; - } } -QString DataBase::createDirectory() -{ - if (opened) { +QString LMDBDataBase::DataBase::createDirectory() { + if (opened) throw Opened(name, "create database directory"); - } QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + getName(); @@ -101,44 +94,100 @@ QString DataBase::createDirectory() if (!cache.exists()) { bool res = cache.mkpath(path); - if (!res) { + if (!res) throw Directory(path.toStdString()); - } } return path; } +QString LMDBDataBase::DataBase::getName() const { + return QString::fromStdString(name);} -QString DataBase::getName() const -{ - return QString::fromStdString(name); -} +bool LMDBDataBase::DataBase::ready() const { + return opened;} -bool DataBase::ready() const -{ - return opened; -} - -void DataBase::drop() -{ - if (!opened) { +void LMDBDataBase::DataBase::drop() { + if (!opened) throw Closed("drop", name); - } MDB_txn *txn; int rc = mdb_txn_begin(environment, NULL, 0, &txn); - if (rc) { + if (rc) throw Unknown(name, mdb_strerror(rc)); - } - for (const std::pair& pair : tables) { + + for (const std::pair& pair : tables) { rc = pair.second->drop(txn); - if (rc) { + if (rc) throw Unknown(name, mdb_strerror(rc), pair.first); - } } mdb_txn_commit(txn); } +LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginReadOnlyTransaction() const { + return beginReadOnlyTransaction(emptyName);} +LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginTransaction() const { + return beginTransaction(emptyName);} + +void LMDBDataBase::DataBase::abortTransaction(LMDBDataBase::TransactionID id) const { + return abortTransaction(id, emptyName);} + +void LMDBDataBase::DataBase::commitTransaction(LMDBDataBase::TransactionID id) const { + return commitTransaction(id, emptyName);} + + +LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginReadOnlyTransaction(const std::string& storageName) const { + if (!opened) + throw Closed("beginReadOnlyTransaction", name, storageName); + + MDB_txn* txn; + int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(name, mdb_strerror(rc), storageName); + } + transactions->emplace(txn); + return txn; +} + +LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginTransaction(const std::string& storageName) const { + if (!opened) + throw Closed("beginTransaction", name, storageName); + + MDB_txn* txn; + int rc = mdb_txn_begin(environment, NULL, 0, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(name, mdb_strerror(rc), storageName); + } + transactions->emplace(txn); + return txn; +} + +void LMDBDataBase::DataBase::abortTransaction(LMDBDataBase::TransactionID id, const std::string& storageName) const { + if (!opened) + throw Closed("abortTransaction", name, storageName); + + Transactions::iterator itr = transactions->find(id); + if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this + throw Unknown(name, "unable to abort transaction: transaction was not found", storageName); + + mdb_txn_abort(id); + transactions->erase(itr); +} + +void LMDBDataBase::DataBase::commitTransaction(LMDBDataBase::TransactionID id, const std::string& storageName) const { + if (!opened) + throw Closed("abortTransaction", name, storageName); + + Transactions::iterator itr = transactions->find(id); + if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this + throw Unknown(name, "unable to commit transaction: transaction was not found", storageName); + + int rc = mdb_txn_commit(id); + transactions->erase(itr); + if (rc != MDB_SUCCESS) + throw Unknown(name, mdb_strerror(rc), storageName); +} diff --git a/database.h b/database.h index 005c1d9..27f318e 100644 --- a/database.h +++ b/database.h @@ -14,28 +14,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef CORE_DATABASE_H -#define CORE_DATABASE_H +#ifndef LMDBDATABASE_DATABASE_H +#define LMDBDATABASE_DATABASE_H #include +#include #include #include +#include #include #include #include + #include -class DataBase -{ - class _Table; - template - class Serializer; +#include "exceptions.h" + +namespace LMDBDataBase { + +class StorageBase; + +template +class Serializer; + +template +class Storage; + +template +class Cache; + +typedef MDB_txn* TransactionID; + +class DataBase { + friend class StorageBase; public: - template - class Table; - template - class Cache; DataBase(const QString& name, uint16_t mapSize = 10); ~DataBase(); @@ -48,69 +61,74 @@ public: QString getName() const; void drop(); + TransactionID beginReadOnlyTransaction() const; + TransactionID beginTransaction() const; + void commitTransaction(TransactionID id) const; + void abortTransaction(TransactionID id) const; template - Table* addTable(const std::string& name); + Storage* addTable(const std::string& name); template Cache* addCache(const std::string& name); template - Table* getTable(const std::string& name); + Storage* getTable(const std::string& name); template Cache* getCache(const std::string& name); -public: - //exceptions - class Exception; - class Directory; - class Closed; - class Opened; - class NotFound; - class Exist; - class Unknown; +private: + typedef std::map Tables; + typedef std::set Transactions; + + TransactionID beginReadOnlyTransaction(const std::string& storageName) const; + TransactionID beginTransaction(const std::string& storageName) const; + void commitTransaction(TransactionID id, const std::string& storageName) const; + void abortTransaction(TransactionID id, const std::string& storageName) const; private: std::string name; bool opened; uint16_t size; MDB_env* environment; - std::map tables; + Tables tables; + Transactions* transactions; + + inline static const std::string emptyName = ""; }; -#include "exceptions.h" +} #include "operators.hpp" template -DataBase::Table* DataBase::addTable(const std::string& p_name) { +LMDBDataBase::Storage* LMDBDataBase::DataBase::addTable(const std::string& p_name) { if (opened) { throw Opened(name, "add table " + p_name); } - DataBase::Table* table = new DataBase::Table(p_name, this); - tables.insert(std::make_pair(p_name, (_Table*)table)); + Storage* table = new Storage(p_name, this); + tables.insert(std::make_pair(p_name, (StorageBase*)table)); return table; } template -DataBase::Cache * DataBase::addCache(const std::string& p_name) { +LMDBDataBase::Cache * LMDBDataBase::DataBase::addCache(const std::string& p_name) { if (opened) { throw Opened(name, "add cache " + p_name); } - DataBase::Cache* cache = new DataBase::Cache(p_name, this); - tables.insert(std::make_pair(p_name, (_Table*)cache)); + Cache* cache = new Cache(p_name, this); + tables.insert(std::make_pair(p_name, (StorageBase*)cache)); return cache; } template -DataBase::Table* DataBase::getTable(const std::string& p_name) { - return static_cast*>(tables.at(p_name)); +LMDBDataBase::Storage* LMDBDataBase::DataBase::getTable(const std::string& p_name) { + return static_cast*>(tables.at(p_name)); } template -DataBase::Cache* DataBase::getCache(const std::string& p_name) { - return static_cast*>(tables.at(p_name)); +LMDBDataBase::Cache* LMDBDataBase::DataBase::getCache(const std::string& p_name) { + return static_cast*>(tables.at(p_name)); } - -#endif // CORE_DATABASE_H +#endif // LMDBDATABASE_DATABASE_H diff --git a/exceptions.cpp b/exceptions.cpp index 086649e..16a8010 100644 --- a/exceptions.cpp +++ b/exceptions.cpp @@ -16,26 +16,25 @@ #include "exceptions.h" -DataBase::Exception::Exception(): -std::exception() +LMDBDataBase::Exception::Exception(): + std::exception() {} -DataBase::Exception::~Exception() {} +LMDBDataBase::Exception::~Exception() {} -const char* DataBase::Exception::what() const noexcept( true ) -{ +const char* LMDBDataBase::Exception::what() const noexcept( true ) { std::string* msg = new std::string(getMessage()); return msg->c_str(); } -DataBase::Directory::Directory(const std::string& p_path): +LMDBDataBase::Directory::Directory(const std::string& p_path): Exception(), path(p_path) {} -std::string DataBase::Directory::getMessage() const { +std::string LMDBDataBase::Directory::getMessage() const { return "Can't create directory for database at " + path;} -DataBase::Closed::Closed( +LMDBDataBase::Closed::Closed( const std::string& p_operation, const std::string& p_dbName, const std::optional& p_tableName @@ -45,29 +44,28 @@ DataBase::Closed::Closed( dbName(p_dbName), tableName(p_tableName) {} -std::string DataBase::Closed::getMessage() const { +std::string LMDBDataBase::Closed::getMessage() const { std::string msg = "An attempt to perform operation " + operation + " on closed database " + dbName; - if (tableName.has_value()) { + if (tableName.has_value()) msg += + " on table " + tableName.value(); - } - return msg; + return msg; } -DataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_action): +LMDBDataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), action(p_action) {} -std::string DataBase::Opened::getMessage() const { +std::string LMDBDataBase::Opened::getMessage() const { return "An attempt to " + action + " (the database " + dbName + ") but it's can't be done because the DataBase is already opened"; } -DataBase::NotFound::NotFound( +LMDBDataBase::NotFound::NotFound( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -77,12 +75,12 @@ DataBase::NotFound::NotFound( dbName(p_dbName), tableName(p_tableName) {} -std::string DataBase::NotFound::getMessage() const { +std::string LMDBDataBase::NotFound::getMessage() const { return "Element for id " + key + " wasn't found " + " in database " + dbName + " in table " + tableName;} -DataBase::Exist::Exist( +LMDBDataBase::Exist::Exist( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -92,14 +90,14 @@ DataBase::Exist::Exist( dbName(p_dbName), tableName(p_tableName) {} -std::string DataBase::Exist::getMessage() const { +std::string LMDBDataBase::Exist::getMessage() const { return "An attempt to insert element with key " + key + " to database " + dbName + " to table " + tableName + " but it already has an element with given id"; } -DataBase::Unknown::Unknown( +LMDBDataBase::Unknown::Unknown( const std::string& p_dbName, const std::string& message, const std::optional& p_tableName @@ -109,12 +107,11 @@ DataBase::Unknown::Unknown( tableName(p_tableName), msg(message) {} -std::string DataBase::Unknown::getMessage() const -{ +std::string LMDBDataBase::Unknown::getMessage() const { std::string result = "Unknown error in database " + dbName; - if (tableName.has_value()) { + if (tableName.has_value()) result += " in table " + tableName.value(); - } + result += ": " + msg; return result; } diff --git a/exceptions.h b/exceptions.h index 88c2e6a..87dc438 100644 --- a/exceptions.h +++ b/exceptions.h @@ -14,17 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef CORE_DATABASE_EXCEPTIONS_H -#define CORE_DATABASE_EXCEPTIONS_H +#ifndef LMDBDATABASE_EXCEPTIONS_H +#define LMDBDATABASE_EXCEPTIONS_H #include #include #include -#include "database.h" +namespace LMDBDataBase { -class DataBase::Exception : public std::exception -{ +class Exception : public std::exception { public: Exception(); virtual ~Exception(); @@ -34,7 +33,7 @@ public: const char* what() const noexcept( true ); }; -class DataBase::Directory: public DataBase::Exception { +class Directory: public Exception { public: Directory(const std::string& path); @@ -43,7 +42,7 @@ private: std::string path; }; -class DataBase::Closed : public DataBase::Exception { +class Closed : public Exception { public: Closed(const std::string& p_operation, const std::string& dbName, const std::optional& tableName = std::nullopt); @@ -54,7 +53,7 @@ private: std::optional tableName; }; -class DataBase::Opened : public DataBase::Exception { +class Opened : Exception { public: Opened(const std::string& dbName, const std::string& action); @@ -64,7 +63,7 @@ private: std::string action; }; -class DataBase::NotFound : public DataBase::Exception { +class NotFound : public Exception { public: NotFound(const std::string& key, const std::string& dbName, const std::string& tableName); @@ -75,7 +74,7 @@ private: std::string tableName; }; -class DataBase::Exist : public DataBase::Exception { +class Exist : public Exception { public: Exist(const std::string& key, const std::string& dbName, const std::string& tableName); @@ -86,7 +85,7 @@ private: std::string tableName; }; -class DataBase::Unknown : public DataBase::Exception { +class Unknown : public Exception { public: Unknown(const std::string& dbName, const std::string& message, const std::optional& tableName = std::nullopt); @@ -97,4 +96,6 @@ private: std::string msg; }; -#endif //CORE_DATABASE_EXCEPTIONS_H +} + +#endif //LMDBDATABASE_EXCEPTIONS_H diff --git a/serializer.h b/serializer.h index 9ff3c18..8f00db9 100644 --- a/serializer.h +++ b/serializer.h @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef CORE_DATABASE_SERIALIZER_H -#define CORE_DATABASE_SERIALIZER_H +#ifndef LMDBDATABASE_SERIALIZER_H +#define LMDBDATABASE_SERIALIZER_H #include @@ -25,9 +25,10 @@ #include "database.h" +namespace LMDBDataBase { + template -class DataBase::Serializer -{ +class Serializer { public: Serializer(); Serializer(const T& value); @@ -48,6 +49,8 @@ private: QDataStream stream; }; +} + #include "serializer.hpp" #include "serializer_uint64.hpp" #include "serializer_uint32.hpp" @@ -63,4 +66,4 @@ private: #include "serializer_qstring.hpp" #include "serializer_qbytearray.hpp" -#endif // CORE_DATABASE_SERIALIZER_H +#endif // LMDBDATABASE_SERIALIZER_H diff --git a/serializer.hpp b/serializer.hpp index 59305c0..a580a88 100644 --- a/serializer.hpp +++ b/serializer.hpp @@ -14,14 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - -#ifndef CORE_DATABASE_SERIALIZER_HPP -#define CORE_DATABASE_SERIALIZER_HPP +#ifndef LMDBDATABASE_SERIALIZER_HPP +#define LMDBDATABASE_SERIALIZER_HPP #include "serializer.h" template -DataBase::Serializer::Serializer() : +LMDBDataBase::Serializer::Serializer() : bytes(), buffer(&bytes), stream(&buffer) @@ -30,7 +29,7 @@ DataBase::Serializer::Serializer() : } template -DataBase::Serializer::Serializer(const T& value) : +LMDBDataBase::Serializer::Serializer(const T& value) : bytes(), buffer(&bytes), stream(&buffer) @@ -40,19 +39,19 @@ DataBase::Serializer::Serializer(const T& value) : } template -DataBase::Serializer::~Serializer() { +LMDBDataBase::Serializer::~Serializer() { buffer.close(); } template -MDB_val DataBase::Serializer::setData(const T& value) { +MDB_val LMDBDataBase::Serializer::setData(const T& value) { clear(); _setData(value); return getData(); } template -T DataBase::Serializer::deserialize(const MDB_val& value) { +T LMDBDataBase::Serializer::deserialize(const MDB_val& value) { clear(); bytes.setRawData((char*)value.mv_data, value.mv_size); T result; @@ -62,19 +61,19 @@ T DataBase::Serializer::deserialize(const MDB_val& value) { } template -void DataBase::Serializer::_setData(const T& value) { +void LMDBDataBase::Serializer::_setData(const T& value) { stream << value; } template -void DataBase::Serializer::clear() { +void LMDBDataBase::Serializer::clear() { if (buffer.pos() > 0) { buffer.seek(0); } } template -MDB_val DataBase::Serializer::getData() { +MDB_val LMDBDataBase::Serializer::getData() { MDB_val val; val.mv_size = buffer.pos(); @@ -83,4 +82,4 @@ MDB_val DataBase::Serializer::getData() { return val; } -#endif //CORE_DATABASE_SERIALIZER_HPP +#endif //LMDBDATABASE_SERIALIZER_HPP diff --git a/serializer_double.hpp b/serializer_double.hpp index aaab3b1..6cdbf01 100644 --- a/serializer_double.hpp +++ b/serializer_double.hpp @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_DOUBLE_HPP +#define LMDBDATABASE_SERIALIZER_DOUBLE_HPP -#ifndef CORE_DATABASE_SERIALIZER_DOUBLE_HPP -#define CORE_DATABASE_SERIALIZER_DOUBLE_HPP +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const double& p_value):value(p_value) {}; @@ -46,8 +46,9 @@ private: double value; }; +} -#endif //CORE_DATABASE_SERIALIZER_DOUBLE_HPP +#endif //LMDBDATABASE_SERIALIZER_DOUBLE_HPP diff --git a/serializer_float.hpp b/serializer_float.hpp index 6a79946..29ce0e0 100644 --- a/serializer_float.hpp +++ b/serializer_float.hpp @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_FLOAT_HPP +#define LMDBDATABASE_SERIALIZER_FLOAT_HPP -#ifndef CORE_DATABASE_SERIALIZER_FLOAT_HPP -#define CORE_DATABASE_SERIALIZER_FLOAT_HPP +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const float& p_value):value(p_value) {}; @@ -46,8 +46,9 @@ private: float value; }; +} -#endif //CORE_DATABASE_SERIALIZER_FLOAT_HPP +#endif //LMDBDATABASE_SERIALIZER_FLOAT_HPP diff --git a/serializer_int16.hpp b/serializer_int16.hpp index 380b184..ff07821 100644 --- a/serializer_int16.hpp +++ b/serializer_int16.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_INT16_HPP +#define LMDBDATABASE_SERIALIZER_INT16_HPP -#ifndef CORE_DATABASE_SERIALIZER_INT16_HPP -#define CORE_DATABASE_SERIALIZER_INT16_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const int16_t& p_value):value(p_value) {}; @@ -46,5 +48,6 @@ private: int16_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_INT16_HPP +#endif //LMDBDATABASE_SERIALIZER_INT16_HPP diff --git a/serializer_int32.hpp b/serializer_int32.hpp index da89324..28da663 100644 --- a/serializer_int32.hpp +++ b/serializer_int32.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_INT32_HPP +#define LMDBDATABASE_SERIALIZER_INT32_HPP -#ifndef CORE_DATABASE_SERIALIZER_INT32_HPP -#define CORE_DATABASE_SERIALIZER_INT32_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const int32_t& p_value):value(p_value) {}; @@ -46,6 +48,7 @@ private: int32_t value; }; +} #endif //CORE_DATABASE_SERIALIZER_INT32_HPP diff --git a/serializer_int64.hpp b/serializer_int64.hpp index d39a368..14c7426 100644 --- a/serializer_int64.hpp +++ b/serializer_int64.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_INT64_HPP +#define LMDBDATABASE_SERIALIZER_INT64_HPP -#ifndef CORE_DATABASE_SERIALIZER_INT64_HPP -#define CORE_DATABASE_SERIALIZER_INT64_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const int64_t& p_value):value(p_value) {}; @@ -46,6 +48,7 @@ private: int64_t value; }; +} #endif //CORE_DATABASE_SERIALIZER_INT64_HPP diff --git a/serializer_int8.hpp b/serializer_int8.hpp index a1e0404..d6df4c1 100644 --- a/serializer_int8.hpp +++ b/serializer_int8.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_INT8_HPP +#define LMDBDATABASE_SERIALIZER_INT8_HPP -#ifndef CORE_DATABASE_SERIALIZER_INT8_HPP -#define CORE_DATABASE_SERIALIZER_INT8_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const int8_t& p_value):value(p_value) {}; @@ -46,8 +48,9 @@ private: int8_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_INT8_HPP +#endif //LMDBDATABASE_SERIALIZER_INT8_HPP diff --git a/serializer_qbytearray.hpp b/serializer_qbytearray.hpp index 741bf34..bbb8050 100644 --- a/serializer_qbytearray.hpp +++ b/serializer_qbytearray.hpp @@ -14,15 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - -#ifndef CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP -#define CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP +#ifndef LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP +#define LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP #include +namespace LMDBDataBase { + template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value() {}; Serializer(const QByteArray& p_value):value(p_value) {}; @@ -50,8 +50,9 @@ private: QByteArray value; }; +} -#endif //CORE_DATABASE_SERIALIZER_QBYTEARRAY_HPP +#endif //LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP diff --git a/serializer_qstring.hpp b/serializer_qstring.hpp index 72c33dd..6073435 100644 --- a/serializer_qstring.hpp +++ b/serializer_qstring.hpp @@ -14,16 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - -#ifndef CORE_DATABASE_SERIALIZER_QSTRING_HPP -#define CORE_DATABASE_SERIALIZER_QSTRING_HPP +#ifndef LMDBDATABASE_SERIALIZER_QSTRING_HPP +#define LMDBDATABASE_SERIALIZER_QSTRING_HPP #include #include +namespace LMDBDataBase { + template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value() {}; Serializer(const QString& p_value):value(p_value.toUtf8()) {}; @@ -49,8 +49,9 @@ private: QByteArray value; }; +} -#endif //CORE_DATABASE_SERIALIZER_QSTRING_HPP +#endif //LMDBDATABASE_SERIALIZER_QSTRING_HPP diff --git a/serializer_stdstring.hpp b/serializer_stdstring.hpp index a17a6f1..9fdc152 100644 --- a/serializer_stdstring.hpp +++ b/serializer_stdstring.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_STDSTRING_HPP +#define LMDBDATABASE_SERIALIZER_STDSTRING_HPP -#ifndef CORE_DATABASE_SERIALIZER_STDSTRING_HPP -#define CORE_DATABASE_SERIALIZER_STDSTRING_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value() {}; Serializer(const std::string& p_value):value(p_value) {}; @@ -46,8 +48,9 @@ private: std::string value; }; +} -#endif //CORE_DATABASE_SERIALIZER_STDSTRING_HPP +#endif //LMDBDATABASE_SERIALIZER_STDSTRING_HPP diff --git a/serializer_uint16.hpp b/serializer_uint16.hpp index b5df796..ea3324c 100644 --- a/serializer_uint16.hpp +++ b/serializer_uint16.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_UINT16_HPP +#define LMDBDATABASE_SERIALIZER_UINT16_HPP -#ifndef CORE_DATABASE_SERIALIZER_UINT16_HPP -#define CORE_DATABASE_SERIALIZER_UINT16_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const uint16_t& p_value):value(p_value) {}; @@ -46,7 +48,8 @@ private: uint16_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_UINT16_HPP +#endif //LMDBDATABASE_SERIALIZER_UINT16_HPP diff --git a/serializer_uint32.hpp b/serializer_uint32.hpp index 590ecb2..bd07534 100644 --- a/serializer_uint32.hpp +++ b/serializer_uint32.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_UINT32_HPP +#define LMDBDATABASE_SERIALIZER_UINT32_HPP -#ifndef CORE_DATABASE_SERIALIZER_UINT32_HPP -#define CORE_DATABASE_SERIALIZER_UINT32_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const uint32_t& p_value):value(p_value) {}; @@ -46,5 +48,6 @@ private: uint32_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_UINT32_HPP +#endif //LMDBDATABASE_SERIALIZER_UINT32_HPP diff --git a/serializer_uint64.hpp b/serializer_uint64.hpp index 6edbbb5..9015378 100644 --- a/serializer_uint64.hpp +++ b/serializer_uint64.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_UINT64_HPP +#define LMDBDATABASE_SERIALIZER_UINT64_HPP -#ifndef CORE_DATABASE_SERIALIZER_UINT64_HPP -#define CORE_DATABASE_SERIALIZER_UINT64_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const uint64_t& p_value):value(p_value) {}; @@ -46,6 +48,7 @@ private: uint64_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_UINT64_HPP +#endif //LMDBDATABASE_SERIALIZER_UINT64_HPP diff --git a/serializer_uint8.hpp b/serializer_uint8.hpp index bb1fc7e..ba2526c 100644 --- a/serializer_uint8.hpp +++ b/serializer_uint8.hpp @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef LMDBDATABASE_SERIALIZER_UINT8_HPP +#define LMDBDATABASE_SERIALIZER_UINT8_HPP -#ifndef CORE_DATABASE_SERIALIZER_UINT8_HPP -#define CORE_DATABASE_SERIALIZER_UINT8_HPP +#include + +namespace LMDBDataBase { template<> -class DataBase::Serializer -{ +class Serializer { public: Serializer():value(0) {}; Serializer(const uint8_t& p_value):value(p_value) {}; @@ -46,8 +48,9 @@ private: uint8_t value; }; +} -#endif //CORE_DATABASE_SERIALIZER_UINT8_HPP +#endif //LMDBDATABASE_SERIALIZER_UINT8_HPP diff --git a/storage.cpp b/storage.cpp new file mode 100644 index 0000000..213a0f2 --- /dev/null +++ b/storage.cpp @@ -0,0 +1,121 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "storage.h" + +LMDBDataBase::StorageBase::StorageBase(const std::string& p_name, DataBase* parent): + dbi(), + db(parent), + name(p_name) +{} + +LMDBDataBase::StorageBase::~StorageBase() {} + +void LMDBDataBase::StorageBase::drop() { + ensureOpened(dropMethodName); + + MDB_txn *txn; + int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + rc = drop(txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + + mdb_txn_commit(txn); +} + +int LMDBDataBase::StorageBase::drop(MDB_txn* transaction) { + return mdb_drop(transaction, dbi, 0); +} + +const std::string & LMDBDataBase::StorageBase::dbName() const { + return db->name;} + +bool LMDBDataBase::StorageBase::isDBOpened() const { + return db->opened;} + +void LMDBDataBase::StorageBase::ensureOpened(const std::string& methodName) const { + if (!db->opened) + throw Closed(methodName, db->name, name); +} + +uint32_t LMDBDataBase::StorageBase::count() const { + ensureOpened(countMethodName); + + MDB_txn *txn; + MDB_stat stat; + int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + + rc = mdb_stat(txn, dbi, &stat); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(db->name, mdb_strerror(rc), name); + } + uint32_t amount = stat.ms_entries; + mdb_txn_abort(txn); + return amount; +} + +void LMDBDataBase::StorageBase::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { + abortTransaction(txn); + if (rc == MDB_KEYEXIST) + throwDuplicate(key); + else + throwUnknown(rc); +} + +void LMDBDataBase::StorageBase::throwNotFoundOrUnknown(int rc, LMDBDataBase::TransactionID txn, const std::string& key) const { + abortTransaction(txn); + if (rc == MDB_NOTFOUND) + throwNotFound(key); + else + throwUnknown(rc); +} + +void LMDBDataBase::StorageBase::throwUnknown(int rc, LMDBDataBase::TransactionID txn) const { + abortTransaction(txn); + throwUnknown(rc); +} + +void LMDBDataBase::StorageBase::throwUnknown(int rc) const { + throw Unknown(db->name, mdb_strerror(rc), name);} + +void LMDBDataBase::StorageBase::throwDuplicate(const std::string& key) const { + throw Exist(key, db->name, name);} + +void LMDBDataBase::StorageBase::throwNotFound(const std::string& key) const { + throw NotFound(key, db->name, name);} + +LMDBDataBase::TransactionID LMDBDataBase::StorageBase::beginReadOnlyTransaction() const { + return db->beginReadOnlyTransaction(name);} + +LMDBDataBase::TransactionID LMDBDataBase::StorageBase::beginTransaction() const { + return db->beginTransaction(name);} + +void LMDBDataBase::StorageBase::abortTransaction(LMDBDataBase::TransactionID id) const { + db->abortTransaction(id);} + +void LMDBDataBase::StorageBase::commitTransaction(LMDBDataBase::TransactionID id) const { + db->commitTransaction(id);} diff --git a/table.h b/storage.h similarity index 51% rename from table.h rename to storage.h index 8279237..19990c1 100644 --- a/table.h +++ b/storage.h @@ -14,30 +14,61 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef CORE_TABLE_H -#define CORE_TABLE_H +#ifndef LMDBDATABASE_STORAGE_H +#define LMDBDATABASE_STORAGE_H #include "database.h" #include "serializer.h" -class DataBase::_Table { +namespace LMDBDataBase { + +class StorageBase { friend class DataBase; protected: - _Table(const std::string& name, DataBase* parent); - virtual ~_Table(); + StorageBase(const std::string& name, DataBase* parent); + virtual ~StorageBase(); virtual int createTable(MDB_txn * transaction) = 0; virtual int drop(MDB_txn * transaction); + bool isDBOpened() const; + const std::string& dbName() const; + + void ensureOpened(const std::string& methodName) const; + void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwUnknown(int rc, TransactionID txn) const; + void throwUnknown(int rc) const; + void throwDuplicate(const std::string& key) const; + void throwNotFound(const std::string& key)const ; + public: virtual void drop(); virtual uint32_t count() const; + TransactionID beginReadOnlyTransaction() const; + TransactionID beginTransaction() const; + void commitTransaction(TransactionID id) const; + void abortTransaction(TransactionID id) const; + protected: MDB_dbi dbi; DataBase* db; const std::string name; + inline static const std::string dropMethodName = "drop"; + inline static const std::string countMethodName = "count"; + + inline static const std::string addRecordMethodName = "addRecord"; + inline static const std::string forceRecordMethodName = "forceRecord"; + inline static const std::string changeRecordMethodName = "changeRecord"; + inline static const std::string removeRecordMethodName = "removeRecord"; + inline static const std::string checkRecordMethodName = "checkRecord"; + inline static const std::string getRecordMethodName = "getRecord"; + inline static const std::string readAllMethodName = "readAllRecord"; + inline static const std::string replaceAllMethodName = "replaceAll"; + inline static const std::string addRecordsMethodName = "addRecords"; + protected: template int makeTable(MDB_txn* transaction); @@ -47,14 +78,14 @@ protected: }; template -class DataBase::Table : public DataBase::_Table { +class Storage : public StorageBase { friend class DataBase; protected: - Table(const std::string& name, DataBase* parent); - ~Table() override; + Storage(const std::string& name, DataBase* parent); + ~Storage() override; public: - using DataBase::_Table::drop; + using StorageBase::drop; virtual void addRecord(const K& key, const V& value); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change virtual void changeRecord(const K& key, const V& value); @@ -72,6 +103,8 @@ protected: int createTable(MDB_txn* transaction) override; }; -#include "table.hpp" +} -#endif // CORE_TABLE_H +#include "storage.hpp" + +#endif // LMDBDATABASE_STORAGE_H diff --git a/storage.hpp b/storage.hpp new file mode 100644 index 0000000..198fcf1 --- /dev/null +++ b/storage.hpp @@ -0,0 +1,291 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef LMDBDATABASE_STORAGE_HPP +#define LMDBDATABASE_STORAGE_HPP + +#include "storage.h" +#include "exceptions.h" + +template +LMDBDataBase::Storage::Storage(const std::string& p_name, DataBase* parent): + StorageBase(p_name, parent), + keySerializer(new Serializer()), + valueSerializer(new Serializer()) +{} + +template +LMDBDataBase::Storage::~Storage() { + delete valueSerializer; + delete keySerializer; +} + +template +void LMDBDataBase::Storage::addRecord(const K& key, const V& value) { + ensureOpened(addRecordMethodName); + + TransactionID txn = beginTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData = valueSerializer->setData(value); + + int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != MDB_SUCCESS) + throwDuplicateOrUnknown(rc, txn, toString(key)); + + commitTransaction(txn); +} + +template +bool LMDBDataBase::Storage::forceRecord(const K& key, const V& value) { + ensureOpened(forceRecordMethodName); + + bool added; + TransactionID txn = beginTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData; + + int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + switch (rc) { + case MDB_SUCCESS: + added = false; + break; + case MDB_NOTFOUND: + added = true; + break; + default: + added = false; + throwUnknown(rc, txn); + } + + lmdbData = valueSerializer->setData(value); + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + + commitTransaction(txn); + + return added; +} + +template +void LMDBDataBase::Storage::changeRecord(const K& key, const V& value) { + ensureOpened(changeRecordMethodName); + + TransactionID txn = beginTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData = valueSerializer->setData(value); + + int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + + commitTransaction(txn); +} + +template +V LMDBDataBase::Storage::getRecord(const K& key) const { + ensureOpened(getRecordMethodName); + + TransactionID txn = beginReadOnlyTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData; + + int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + if (rc != MDB_SUCCESS) + throwNotFoundOrUnknown(rc, txn, toString(key)); + + V value = valueSerializer->deserialize(lmdbData); + abortTransaction(txn); + + return value; +} + +template +bool LMDBDataBase::Storage::checkRecord(const K& key) const { + ensureOpened(checkRecordMethodName); + + TransactionID txn = beginReadOnlyTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbData; + + int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + abortTransaction(txn); + + if (rc == MDB_SUCCESS) + return true; + + if (rc != MDB_NOTFOUND) + throwUnknown(rc); + + return false; +} + +template +std::map LMDBDataBase::Storage::readAll() const { + ensureOpened(readAllMethodName); + + TransactionID txn = beginReadOnlyTransaction(); + std::map result; + MDB_cursor* cursor; + MDB_val lmdbKey, lmdbData; + + int rc = mdb_cursor_open(txn, dbi, &cursor); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + while (rc == MDB_SUCCESS) { + K key = keySerializer->deserialize(lmdbKey); + V value = valueSerializer->deserialize(lmdbData); + result.insert(std::make_pair(key, value)); + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); + } + + abortTransaction(txn); + if (rc != MDB_NOTFOUND) + throwUnknown(rc); + + return result; +} + +template +void LMDBDataBase::Storage::replaceAll(const std::map& data) { + ensureOpened(replaceAllMethodName); + + TransactionID txn = beginTransaction(); + int rc = drop(txn); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + + MDB_val lmdbKey, lmdbData; + for (const std::pair& pair : data) { + lmdbKey = keySerializer->setData(pair.first); + lmdbData = valueSerializer->setData(pair.second); + + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + } + commitTransaction(txn); +} + +template +uint32_t LMDBDataBase::Storage::addRecords(const std::map& data, bool overwrite) { + ensureOpened(addRecordsMethodName); + + TransactionID txn = beginTransaction(); + MDB_val lmdbKey, lmdbData; + int rc; + + for (const std::pair& pair : data) { + lmdbKey = keySerializer->setData(pair.first); + lmdbData = valueSerializer->setData(pair.second); + + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + } + + MDB_stat stat; + rc = mdb_stat(txn, dbi, &stat); + if (rc != MDB_SUCCESS) + throwUnknown(rc, txn); + + uint32_t amount = stat.ms_entries; + commitTransaction(txn); + + return amount; +} + +template +void LMDBDataBase::Storage::removeRecord(const K& key) { + ensureOpened(removeRecordMethodName); + + TransactionID txn = beginTransaction(); + MDB_val lmdbKey = keySerializer->setData(key); + int rc = mdb_del(txn, dbi, &lmdbKey, NULL); + if (rc != MDB_SUCCESS) + throwNotFoundOrUnknown(rc, txn, toString(key)); + + commitTransaction(txn); +} + +template +int LMDBDataBase::Storage::createTable(MDB_txn* transaction) { + return makeTable(transaction); +} + +template +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template<> +inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { + return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); +} + +template +inline std::string LMDBDataBase::StorageBase::toString(const T& value) { + return std::to_string(value); +} + +template<> +inline std::string LMDBDataBase::StorageBase::toString(const QString& value) { + return value.toStdString(); +} + +template<> +inline std::string LMDBDataBase::StorageBase::toString(const std::string& value) { + return value; +} + +#endif //LMDBDATABASE_STORAGE_HPP diff --git a/table.cpp b/table.cpp deleted file mode 100644 index 80ac7bc..0000000 --- a/table.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "table.h" - -DataBase::_Table::_Table(const std::string& p_name, DataBase* parent): - dbi(), - db(parent), - name(p_name) -{ -} - -DataBase::_Table::~_Table() {} - - -void DataBase::_Table::drop() -{ - if (!db->opened) { - throw Closed("drop", db->name, name); - } - - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - rc = drop(txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - mdb_txn_commit(txn); -} - -int DataBase::_Table::drop(MDB_txn* transaction) -{ - return mdb_drop(transaction, dbi, 0); -} - -uint32_t DataBase::_Table::count() const -{ - if (!db->opened) { - throw Closed("count", db->name, name); - } - - MDB_txn *txn; - MDB_stat stat; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - rc = mdb_stat(txn, dbi, &stat); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - uint32_t amount = stat.ms_entries; - mdb_txn_abort(txn); - return amount; -} diff --git a/table.hpp b/table.hpp deleted file mode 100644 index 49cb38e..0000000 --- a/table.hpp +++ /dev/null @@ -1,385 +0,0 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#ifndef CORE_TABLE_HPP -#define CORE_TABLE_HPP - -#include "table.h" -#include "exceptions.h" - -template -DataBase::Table::Table(const std::string& p_name, DataBase* parent): - _Table(p_name, parent), - keySerializer(new Serializer()), - valueSerializer(new Serializer()) -{ -} - -template -DataBase::Table::~Table() { - delete valueSerializer; - delete keySerializer; -} - -template -void DataBase::Table::addRecord(const K& key, const V& value) { - if (!db->opened) { - throw Closed("addRecord", db->name, name); - } - - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); - if (rc != 0) { - mdb_txn_abort(txn); - if (rc == MDB_KEYEXIST) { - throw Exist(toString(key), db->name, name); - } else { - throw Unknown(db->name, mdb_strerror(rc), name); - } - } else { - mdb_txn_commit(txn); - } -} - -template -bool DataBase::Table::forceRecord(const K& key, const V& value) { - if (!db->opened) { - throw Closed("forceRecord", db->name, name); - } - - bool added; - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData; - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); - if (rc == 0) { - added = false; - } else if (rc == MDB_NOTFOUND) { - added = true; - } else { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - lmdbData = valueSerializer->setData(value); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); - if (rc != 0) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } else { - mdb_txn_commit(txn); - } - - return added; -} - -template -void DataBase::Table::changeRecord(const K& key, const V& value) { - if (!db->opened) { - throw Closed("changeRecord", db->name, name); - } - - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); - MDB_txn *txn; - - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); - if (rc != 0) { - mdb_txn_abort(txn); - if (rc) { - throw Unknown(db->name, mdb_strerror(rc), name); - } - } else { - mdb_txn_commit(txn); - } -} - -template -V DataBase::Table::getRecord(const K& key) const { - if (!db->opened) { - throw Closed("getRecord", db->name, name); - } - - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData; - - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); - if (rc) { - mdb_txn_abort(txn); - if (rc == MDB_NOTFOUND) { - throw NotFound(toString(key), db->name, name); - } else { - throw Unknown(db->name, mdb_strerror(rc), name); - } - } else { - V value = valueSerializer->deserialize(lmdbData); - mdb_txn_abort(txn); - - return value; - } -} - -template -bool DataBase::Table::checkRecord(const K& key) const { - if (!db->opened) { - throw Closed("checkRecord", db->name, name); - } - - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData; - - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); - mdb_txn_abort(txn); - - if (rc) { - if (rc == MDB_NOTFOUND) { - return false; - } else { - throw Unknown(db->name, mdb_strerror(rc), name); - } - } else { - return true; - } -} - -template -std::map DataBase::Table::readAll() const { - if (!db->opened) { - throw Closed("readAll", db->name, name); - } - - std::map result; - MDB_txn *txn; - MDB_cursor* cursor; - MDB_val lmdbKey, lmdbData; - int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - rc = mdb_cursor_open(txn, dbi, &cursor); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); - while (rc == MDB_SUCCESS) { - K key = keySerializer->deserialize(lmdbKey); - V value = valueSerializer->deserialize(lmdbData); - result.insert(std::make_pair(key, value)); - rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); - } - - mdb_txn_abort(txn); - if (rc != MDB_NOTFOUND) { - throw Unknown(db->name, mdb_strerror(rc), name); - } - - return result; -} - -template -void DataBase::Table::replaceAll(const std::map& data) { - if (!db->opened) { - throw Closed("replaceAll", db->name, name); - } - - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - rc = drop(txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - MDB_val lmdbKey, lmdbData; - for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); - - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); - if (rc != 0) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - } - mdb_txn_commit(txn); -} - -template -uint32_t DataBase::Table::addRecords(const std::map& data, bool overwrite) { - if (!db->opened) { - throw Closed("addRecords", db->name, name); - } - - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - - MDB_val lmdbKey, lmdbData; - for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); - - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); - if (rc != 0) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - } - - MDB_stat stat; - rc = mdb_stat(txn, dbi, &stat); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - uint32_t amount = stat.ms_entries; - - mdb_txn_commit(txn); - return amount; -} - -template -void DataBase::Table::removeRecord(const K& key) { - if (!db->opened) { - throw Closed("removeRecord", db->name, name); - } - - MDB_val lmdbKey = keySerializer->setData(key); - - MDB_txn *txn; - int rc; - mdb_txn_begin(db->environment, NULL, 0, &txn); - rc = mdb_del(txn, dbi, &lmdbKey, NULL); - if (rc) { - mdb_txn_abort(txn); - if (rc == MDB_NOTFOUND) { - throw NotFound(toString(key), db->name, name); - } else { - throw Unknown(db->name, mdb_strerror(rc), name); - } - } else { - mdb_txn_commit(txn); - } -} - -template -int DataBase::Table::createTable(MDB_txn* transaction) { - return makeTable(transaction); -} - -template -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template<> -inline int DataBase::_Table::makeTable(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -template -inline std::string DataBase::_Table::toString(const T& value) { - return std::to_string(value); -} - -template<> -inline std::string DataBase::_Table::toString(const QString& value) { - return value.toStdString(); -} - -template<> -inline std::string DataBase::_Table::toString(const std::string& value) { - return value; -} - -#endif //CORE_TABLE_HPP diff --git a/test/basic.cpp b/test/basic.cpp index 2ee0262..cc92a5f 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -1,7 +1,7 @@ #include #include "database.h" -#include "table.h" +#include "storage.h" #include "cache.h" #include @@ -18,7 +18,7 @@ protected: static void SetUpTestSuite() { if (db == nullptr) { - db = new DataBase("testBase"); + db = new LMDBDataBase::DataBase("testBase"); db->addTable("table1"); db->addTable("table2"); db->addCache("cache1"); @@ -32,15 +32,15 @@ protected: db = nullptr; } - static DataBase* db; + static LMDBDataBase::DataBase* db; - DataBase::Table* t1; - DataBase::Table* t2; - DataBase::Cache* c1; + LMDBDataBase::Storage* t1; + LMDBDataBase::Storage* t2; + LMDBDataBase::Cache* c1; }; -DataBase* DataBaseTest::db = nullptr; +LMDBDataBase::DataBase* DataBaseTest::db = nullptr; TEST_F(DataBaseTest, RemovingDirectory) { EXPECT_EQ(db->removeDirectory(), true); @@ -88,7 +88,7 @@ TEST_F(DataBaseTest, AddingRepeatingIntegerKey) { bool thrown = false; try { t1->addRecord(3, 24); - } catch (const DataBase::Exist e) { + } catch (const LMDBDataBase::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -100,7 +100,7 @@ TEST_F(DataBaseTest, AddingRepeatingStringKey) { bool thrown = false; try { t2->addRecord("sdfhga", "world"); - } catch (const DataBase::Exist e) { + } catch (const LMDBDataBase::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -112,7 +112,7 @@ TEST_F(DataBaseTest, AddingRepeatingCacheKey) { bool thrown = false; try { c1->addRecord(-4, "world"); - } catch (const DataBase::Exist e) { + } catch (const LMDBDataBase::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -124,7 +124,7 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { bool thrown = false; try { QString wrong = t2->getRecord("almonds"); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -132,7 +132,7 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { thrown = false; try { uint32_t wrong = t1->getRecord(64); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -140,7 +140,7 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { thrown = false; try { std::string wrong = c1->getRecord(21); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -151,7 +151,7 @@ TEST_F(DataBaseTest, Persistence) { db->close(); delete db; - db = new DataBase("testBase"); + db = new LMDBDataBase::DataBase("testBase"); t1 = db->addTable("table1"); t2 = db->addTable("table2"); c1 = db->addCache("cache1"); @@ -175,7 +175,7 @@ TEST_F(DataBaseTest, Persistence) { bool thrown = false; try { QString wrong = t2->getRecord("cats"); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -183,7 +183,7 @@ TEST_F(DataBaseTest, Persistence) { thrown = false; try { uint32_t wrong = t1->getRecord(7893); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -191,7 +191,7 @@ TEST_F(DataBaseTest, Persistence) { thrown = false; try { std::string wrong = c1->getRecord(89); - } catch (const DataBase::NotFound e) { + } catch (const LMDBDataBase::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; From 7377f135349ddb3aaa1aeda1aeced15b76c4429f Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 21 Mar 2023 14:05:54 +0300 Subject: [PATCH 023/125] project name change --- CMakeLists.txt | 59 ++++------------- src/CMakeLists.txt | 37 +++++++++++ database.cpp => src/base.cpp | 48 +++++++------- database.h => src/base.h | 40 +++++------ cache.h => src/cache.h | 12 ++-- cache.hpp => src/cache.hpp | 58 ++++++++-------- exceptions.cpp => src/exceptions.cpp | 32 ++++----- exceptions.h => src/exceptions.h | 8 +-- operators.hpp => src/operators.hpp | 6 +- serializer.h => src/serializer.h | 10 +-- serializer.hpp => src/serializer.hpp | 22 +++---- .../serializer_double.hpp | 8 +-- .../serializer_float.hpp | 8 +-- .../serializer_int16.hpp | 8 +-- .../serializer_int32.hpp | 8 +-- .../serializer_int64.hpp | 8 +-- .../serializer_int8.hpp | 8 +-- .../serializer_qbytearray.hpp | 8 +-- .../serializer_qstring.hpp | 8 +-- .../serializer_stdstring.hpp | 8 +-- .../serializer_uint16.hpp | 8 +-- .../serializer_uint32.hpp | 8 +-- .../serializer_uint64.hpp | 8 +-- .../serializer_uint8.hpp | 8 +-- storage.cpp => src/storage.cpp | 36 +++++----- storage.h => src/storage.h | 28 ++++---- storage.hpp => src/storage.hpp | 56 ++++++++-------- test/CMakeLists.txt | 4 +- test/basic.cpp | 66 +++++++++---------- 29 files changed, 316 insertions(+), 310 deletions(-) create mode 100644 src/CMakeLists.txt rename database.cpp => src/base.cpp (73%) rename database.h => src/base.h (77%) rename cache.h => src/cache.h (91%) rename cache.hpp => src/cache.hpp (77%) rename exceptions.cpp => src/exceptions.cpp (76%) rename exceptions.h => src/exceptions.h (95%) rename operators.hpp => src/operators.hpp (98%) rename serializer.h => src/serializer.h (91%) rename serializer.hpp => src/serializer.hpp (73%) rename serializer_double.hpp => src/serializer_double.hpp (89%) rename serializer_float.hpp => src/serializer_float.hpp (89%) rename serializer_int16.hpp => src/serializer_int16.hpp (89%) rename serializer_int32.hpp => src/serializer_int32.hpp (89%) rename serializer_int64.hpp => src/serializer_int64.hpp (89%) rename serializer_int8.hpp => src/serializer_int8.hpp (89%) rename serializer_qbytearray.hpp => src/serializer_qbytearray.hpp (89%) rename serializer_qstring.hpp => src/serializer_qstring.hpp (90%) rename serializer_stdstring.hpp => src/serializer_stdstring.hpp (89%) rename serializer_uint16.hpp => src/serializer_uint16.hpp (89%) rename serializer_uint32.hpp => src/serializer_uint32.hpp (89%) rename serializer_uint64.hpp => src/serializer_uint64.hpp (89%) rename serializer_uint8.hpp => src/serializer_uint8.hpp (89%) rename storage.cpp => src/storage.cpp (63%) rename storage.h => src/storage.h (88%) rename storage.hpp => src/storage.hpp (77%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eb24f6..61b7919 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(storage VERSION 0.0.1 LANGUAGES CXX) +project(lmdbal VERSION 0.2.0 LANGUAGES CXX) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) @@ -28,63 +28,32 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif () -set(SOURCES - exceptions.cpp - storage.cpp - database.cpp -) - -set(HEADERS - database.h - exceptions.h - storage.h - storage.hpp - cache.h - cache.hpp - serializer.h - serializer.hpp - serializer_uint8.hpp - serializer_uint16.hpp - serializer_uint32.hpp - serializer_uint64.hpp - serializer_int8.hpp - serializer_int16.hpp - serializer_int32.hpp - serializer_int64.hpp - serializer_float.hpp - serializer_double.hpp - serializer_stdstring.hpp - serializer_qstring.hpp - serializer_qbytearray.hpp - operators.hpp -) - if (BUILD_STATIC) - add_library(storage STATIC ${SOURCES}) + add_library(lmdbal STATIC ${SOURCES}) else () - add_library(storage SHARED ${SOURCES}) + add_library(lmdbal SHARED ${SOURCES}) endif() +add_subdirectory(src) + if (BUILD_TESTS) add_subdirectory(test) endif () -set_target_properties(storage PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_include_directories(lmdbal PUBLIC ${CMAKE_SOURCE_DIR}/src) +target_include_directories(lmdbal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(lmdbal PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(lmdbal PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) -target_include_directories(storage PUBLIC ${CMAKE_SOURCE_DIR}) -target_include_directories(storage PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) -target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) - -target_link_libraries(storage +target_link_libraries(lmdbal PRIVATE Qt${QT_VERSION_MAJOR}::Core ) -target_link_libraries(storage PRIVATE lmdb) +target_link_libraries(lmdbal PRIVATE lmdb) -install(TARGETS storage +install(TARGETS lmdbal RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/storage - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/storage + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/lmdbal + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lmdbal ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f5ca470 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,37 @@ +set(SOURCES + exceptions.cpp + storage.cpp + base.cpp +) + +set(HEADERS + base.h + exceptions.h + storage.h + storage.hpp + cache.h + cache.hpp + serializer.h + serializer.hpp + serializer_uint8.hpp + serializer_uint16.hpp + serializer_uint32.hpp + serializer_uint64.hpp + serializer_int8.hpp + serializer_int16.hpp + serializer_int32.hpp + serializer_int64.hpp + serializer_float.hpp + serializer_double.hpp + serializer_stdstring.hpp + serializer_qstring.hpp + serializer_qbytearray.hpp + operators.hpp +) + +target_sources(lmdbal PRIVATE + ${SOURCES} + ${HEADERS} +) + +set_target_properties(lmdbal PROPERTIES PUBLIC_HEADER "${HEADERS}") diff --git a/database.cpp b/src/base.cpp similarity index 73% rename from database.cpp rename to src/base.cpp index 87f4419..1f9003a 100644 --- a/database.cpp +++ b/src/base.cpp @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "database.h" +#include "base.h" #include "exceptions.h" #include "storage.h" -LMDBDataBase::DataBase::DataBase(const QString& p_name, uint16_t mapSize): +LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), size(mapSize), @@ -27,19 +27,19 @@ LMDBDataBase::DataBase::DataBase(const QString& p_name, uint16_t mapSize): transactions(new Transactions()) {} -LMDBDataBase::DataBase::~DataBase() { +LMDBAL::Base::~Base() { close(); delete transactions; - for (const std::pair& pair : tables) + for (const std::pair& pair : tables) delete pair.second; } -void LMDBDataBase::DataBase::close() { +void LMDBAL::Base::close() { if (opened) { - for (const std::pair& pair : tables) { - StorageBase* table = pair.second; + for (const std::pair& pair : tables) { + iStorage* table = pair.second; mdb_dbi_close(environment, table->dbi); } mdb_env_close(environment); @@ -47,7 +47,7 @@ void LMDBDataBase::DataBase::close() { } } -void LMDBDataBase::DataBase::open() { +void LMDBAL::Base::open() { if (!opened) { mdb_env_create(&environment); QString path = createDirectory(); @@ -59,8 +59,8 @@ void LMDBDataBase::DataBase::open() { MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); - for (const std::pair& pair : tables) { - StorageBase* table = pair.second; + for (const std::pair& pair : tables) { + iStorage* table = pair.second; int rc = table->createTable(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); @@ -70,7 +70,7 @@ void LMDBDataBase::DataBase::open() { } } -bool LMDBDataBase::DataBase::removeDirectory() { +bool LMDBAL::Base::removeDirectory() { if (opened) throw Opened(name, "remove database directory"); @@ -84,7 +84,7 @@ bool LMDBDataBase::DataBase::removeDirectory() { return true; } -QString LMDBDataBase::DataBase::createDirectory() { +QString LMDBAL::Base::createDirectory() { if (opened) throw Opened(name, "create database directory"); @@ -101,13 +101,13 @@ QString LMDBDataBase::DataBase::createDirectory() { return path; } -QString LMDBDataBase::DataBase::getName() const { +QString LMDBAL::Base::getName() const { return QString::fromStdString(name);} -bool LMDBDataBase::DataBase::ready() const { +bool LMDBAL::Base::ready() const { return opened;} -void LMDBDataBase::DataBase::drop() { +void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); @@ -116,7 +116,7 @@ void LMDBDataBase::DataBase::drop() { if (rc) throw Unknown(name, mdb_strerror(rc)); - for (const std::pair& pair : tables) { + for (const std::pair& pair : tables) { rc = pair.second->drop(txn); if (rc) throw Unknown(name, mdb_strerror(rc), pair.first); @@ -125,20 +125,20 @@ void LMDBDataBase::DataBase::drop() { mdb_txn_commit(txn); } -LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginReadOnlyTransaction() const { +LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { return beginReadOnlyTransaction(emptyName);} -LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginTransaction() const { +LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const { return beginTransaction(emptyName);} -void LMDBDataBase::DataBase::abortTransaction(LMDBDataBase::TransactionID id) const { +void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { return abortTransaction(id, emptyName);} -void LMDBDataBase::DataBase::commitTransaction(LMDBDataBase::TransactionID id) const { +void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) const { return commitTransaction(id, emptyName);} -LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginReadOnlyTransaction(const std::string& storageName) const { +LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginReadOnlyTransaction", name, storageName); @@ -152,7 +152,7 @@ LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginReadOnlyTransaction(con return txn; } -LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginTransaction(const std::string& storageName) const { +LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginTransaction", name, storageName); @@ -166,7 +166,7 @@ LMDBDataBase::TransactionID LMDBDataBase::DataBase::beginTransaction(const std:: return txn; } -void LMDBDataBase::DataBase::abortTransaction(LMDBDataBase::TransactionID id, const std::string& storageName) const { +void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); @@ -178,7 +178,7 @@ void LMDBDataBase::DataBase::abortTransaction(LMDBDataBase::TransactionID id, co transactions->erase(itr); } -void LMDBDataBase::DataBase::commitTransaction(LMDBDataBase::TransactionID id, const std::string& storageName) const { +void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); diff --git a/database.h b/src/base.h similarity index 77% rename from database.h rename to src/base.h index 27f318e..42cc26c 100644 --- a/database.h +++ b/src/base.h @@ -1,21 +1,21 @@ -// Squawk messenger. +// Squawk messenger. // Copyright (C) 2019 Yury Gubich -// +// // 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 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 General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_DATABASE_H -#define LMDBDATABASE_DATABASE_H +#ifndef LMDBAL_BASE_H +#define LMDBAL_BASE_H #include #include @@ -31,9 +31,9 @@ #include "exceptions.h" -namespace LMDBDataBase { +namespace LMDBAL { -class StorageBase; +class iStorage; template class Serializer; @@ -46,12 +46,12 @@ class Cache; typedef MDB_txn* TransactionID; -class DataBase { - friend class StorageBase; +class Base { + friend class iStorage; public: - DataBase(const QString& name, uint16_t mapSize = 10); - ~DataBase(); + Base(const QString& name, uint16_t mapSize = 10); + ~Base(); void open(); void close(); @@ -79,7 +79,7 @@ public: Cache* getCache(const std::string& name); private: - typedef std::map Tables; + typedef std::map Tables; typedef std::set Transactions; TransactionID beginReadOnlyTransaction(const std::string& storageName) const; @@ -102,33 +102,33 @@ private: #include "operators.hpp" template -LMDBDataBase::Storage* LMDBDataBase::DataBase::addTable(const std::string& p_name) { +LMDBAL::Storage* LMDBAL::Base::addTable(const std::string& p_name) { if (opened) { throw Opened(name, "add table " + p_name); } Storage* table = new Storage(p_name, this); - tables.insert(std::make_pair(p_name, (StorageBase*)table)); + tables.insert(std::make_pair(p_name, (iStorage*)table)); return table; } template -LMDBDataBase::Cache * LMDBDataBase::DataBase::addCache(const std::string& p_name) { +LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { if (opened) { throw Opened(name, "add cache " + p_name); } Cache* cache = new Cache(p_name, this); - tables.insert(std::make_pair(p_name, (StorageBase*)cache)); + tables.insert(std::make_pair(p_name, (iStorage*)cache)); return cache; } template -LMDBDataBase::Storage* LMDBDataBase::DataBase::getTable(const std::string& p_name) { +LMDBAL::Storage* LMDBAL::Base::getTable(const std::string& p_name) { return static_cast*>(tables.at(p_name)); } template -LMDBDataBase::Cache* LMDBDataBase::DataBase::getCache(const std::string& p_name) { +LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& p_name) { return static_cast*>(tables.at(p_name)); } -#endif // LMDBDATABASE_DATABASE_H +#endif //LMDBAL_BASE_H diff --git a/cache.h b/src/cache.h similarity index 91% rename from cache.h rename to src/cache.h index ba72f46..52dd88a 100644 --- a/cache.h +++ b/src/cache.h @@ -14,26 +14,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_CACHE_H -#define LMDBDATABASE_CACHE_H +#ifndef LMDBAL_CACHE_H +#define LMDBAL_CACHE_H #include #include #include "storage.h" -namespace LMDBDataBase { +namespace LMDBAL { template class Cache : public Storage { - friend class DataBase; + friend class Base; enum class Mode { //it's a cache state when we: nothing, // - know nothing about records in database on disk size, // - know just an amount of records full // - shure that our cache is equal to the database on disk }; protected: - Cache(const std::string& name, DataBase* parent); + Cache(const std::string& name, Base* parent); ~Cache() override; private: @@ -65,4 +65,4 @@ protected: #include "cache.hpp" -#endif // LMDBDATABASE_CACHE_H +#endif // LMDBAL_CACHE_H diff --git a/cache.hpp b/src/cache.hpp similarity index 77% rename from cache.hpp rename to src/cache.hpp index 1212f4e..e0cc1d0 100644 --- a/cache.hpp +++ b/src/cache.hpp @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_CACHE_HPP -#define LMDBDATABASE_CACHE_HPP +#ifndef LMDBAL_CACHE_HPP +#define LMDBAL_CACHE_HPP #include "cache.h" #include "exceptions.h" template -LMDBDataBase::Cache::Cache(const std::string& p_name, DataBase* parent): +LMDBAL::Cache::Cache(const std::string& p_name, Base* parent): Storage(p_name, parent), mode(new Mode), cache(new std::map()), @@ -33,7 +33,7 @@ LMDBDataBase::Cache::Cache(const std::string& p_name, DataBase* parent): } template -LMDBDataBase::Cache::~Cache() { +LMDBAL::Cache::~Cache() { delete sizeDifference; delete mode; delete cache; @@ -41,11 +41,11 @@ LMDBDataBase::Cache::~Cache() { } template -void LMDBDataBase::Cache::addRecord(const K& key, const V& value) { - StorageBase::ensureOpened(StorageBase::addRecordMethodName); +void LMDBAL::Cache::addRecord(const K& key, const V& value) { + iStorage::ensureOpened(iStorage::addRecordMethodName); if (cache->count(key) > 0) - StorageBase::throwDuplicate(StorageBase::toString(key)); + iStorage::throwDuplicate(iStorage::toString(key)); Storage::addRecord(key, value); cache->insert(std::make_pair(key, value)); @@ -55,8 +55,8 @@ void LMDBDataBase::Cache::addRecord(const K& key, const V& value) { } template -bool LMDBDataBase::Cache::forceRecord(const K& key, const V& value) { - StorageBase::ensureOpened(StorageBase::forceRecordMethodName); +bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { + iStorage::ensureOpened(iStorage::forceRecordMethodName); bool added = Storage::forceRecord(key, value); if (*mode == Mode::full) { @@ -76,19 +76,19 @@ bool LMDBDataBase::Cache::forceRecord(const K& key, const V& value) { } template -void LMDBDataBase::Cache::changeRecord(const K& key, const V& value) { - StorageBase::ensureOpened(StorageBase::changeRecordMethodName); +void LMDBAL::Cache::changeRecord(const K& key, const V& value) { + iStorage::ensureOpened(iStorage::changeRecordMethodName); if (*mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) - StorageBase::throwNotFound(StorageBase::toString(key)); + iStorage::throwNotFound(iStorage::toString(key)); Storage::changeRecord(key, value); itr->second = value; } else { if (abscent->count(key) > 0) - StorageBase::throwNotFound(StorageBase::toString(key)); + iStorage::throwNotFound(iStorage::toString(key)); try { Storage::changeRecord(key, value); @@ -105,15 +105,15 @@ void LMDBDataBase::Cache::changeRecord(const K& key, const V& value) { } template -V LMDBDataBase::Cache::getRecord(const K& key) const { - StorageBase::ensureOpened(StorageBase::getRecordMethodName); +V LMDBAL::Cache::getRecord(const K& key) const { + iStorage::ensureOpened(iStorage::getRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) return itr->second; if (*mode == Mode::full || abscent->count(key) != 0) - StorageBase::throwNotFound(StorageBase::toString(key)); + iStorage::throwNotFound(iStorage::toString(key)); try { V value = Storage::getRecord(key); @@ -129,8 +129,8 @@ V LMDBDataBase::Cache::getRecord(const K& key) const { } template -bool LMDBDataBase::Cache::checkRecord(const K& key) const { - StorageBase::ensureOpened(StorageBase::checkRecordMethodName); +bool LMDBAL::Cache::checkRecord(const K& key) const { + iStorage::ensureOpened(iStorage::checkRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) @@ -153,8 +153,8 @@ bool LMDBDataBase::Cache::checkRecord(const K& key) const { } template -std::map LMDBDataBase::Cache::readAll() const { - StorageBase::ensureOpened(StorageBase::readAllMethodName); +std::map LMDBAL::Cache::readAll() const { + iStorage::ensureOpened(iStorage::readAllMethodName); if (*mode != Mode::full) { //there is a room for optimization *mode = Mode::full; //I can read and deserialize only those values @@ -167,7 +167,7 @@ std::map LMDBDataBase::Cache::readAll() const { } template -void LMDBDataBase::Cache::replaceAll(const std::map& data) { +void LMDBAL::Cache::replaceAll(const std::map& data) { Storage::replaceAll(data); *cache = data; @@ -179,7 +179,7 @@ void LMDBDataBase::Cache::replaceAll(const std::map& data) { } template -uint32_t LMDBDataBase::Cache::addRecords(const std::map& data, bool overwrite) { +uint32_t LMDBAL::Cache::addRecords(const std::map& data, bool overwrite) { uint32_t newSize = Storage::addRecords(data, overwrite); Mode& m = *mode; @@ -210,12 +210,12 @@ uint32_t LMDBDataBase::Cache::addRecords(const std::map& data, bool } template -void LMDBDataBase::Cache::removeRecord(const K& key) { - StorageBase::ensureOpened(StorageBase::removeRecordMethodName); +void LMDBAL::Cache::removeRecord(const K& key) { + iStorage::ensureOpened(iStorage::removeRecordMethodName); typename std::pair::const_iterator, bool> pair = abscent->insert(key); if (!pair.second) - StorageBase::throwNotFound(StorageBase::toString(key)); + iStorage::throwNotFound(iStorage::toString(key)); Storage::removeRecord(key); if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease @@ -226,7 +226,7 @@ void LMDBDataBase::Cache::removeRecord(const K& key) { } template -uint32_t LMDBDataBase::Cache::count() const { +uint32_t LMDBAL::Cache::count() const { switch (*mode) { case Mode::nothing: { @@ -250,7 +250,7 @@ uint32_t LMDBDataBase::Cache::count() const { } template -void LMDBDataBase::Cache::handleMode() const { +void LMDBAL::Cache::handleMode() const { if (*mode == Mode::size) { --(*sizeDifference); if (*sizeDifference == 0) { @@ -261,7 +261,7 @@ void LMDBDataBase::Cache::handleMode() const { } template -int LMDBDataBase::Cache::drop(MDB_txn * transaction) { +int LMDBAL::Cache::drop(MDB_txn * transaction) { int res = Storage::drop(transaction); cache->clear(); abscent->clear(); @@ -270,4 +270,4 @@ int LMDBDataBase::Cache::drop(MDB_txn * transaction) { return res; } -#endif //LMDBDATABASE_CACHE_HPP +#endif //LMDBAL_CACHE_HPP diff --git a/exceptions.cpp b/src/exceptions.cpp similarity index 76% rename from exceptions.cpp rename to src/exceptions.cpp index 16a8010..49ca0a4 100644 --- a/exceptions.cpp +++ b/src/exceptions.cpp @@ -16,25 +16,25 @@ #include "exceptions.h" -LMDBDataBase::Exception::Exception(): +LMDBAL::Exception::Exception(): std::exception() {} -LMDBDataBase::Exception::~Exception() {} +LMDBAL::Exception::~Exception() {} -const char* LMDBDataBase::Exception::what() const noexcept( true ) { +const char* LMDBAL::Exception::what() const noexcept( true ) { std::string* msg = new std::string(getMessage()); return msg->c_str(); } -LMDBDataBase::Directory::Directory(const std::string& p_path): +LMDBAL::Directory::Directory(const std::string& p_path): Exception(), path(p_path) {} -std::string LMDBDataBase::Directory::getMessage() const { +std::string LMDBAL::Directory::getMessage() const { return "Can't create directory for database at " + path;} -LMDBDataBase::Closed::Closed( +LMDBAL::Closed::Closed( const std::string& p_operation, const std::string& p_dbName, const std::optional& p_tableName @@ -44,7 +44,7 @@ LMDBDataBase::Closed::Closed( dbName(p_dbName), tableName(p_tableName) {} -std::string LMDBDataBase::Closed::getMessage() const { +std::string LMDBAL::Closed::getMessage() const { std::string msg = "An attempt to perform operation " + operation + " on closed database " + dbName; if (tableName.has_value()) @@ -53,19 +53,19 @@ std::string LMDBDataBase::Closed::getMessage() const { return msg; } -LMDBDataBase::Opened::Opened(const std::string& p_dbName, const std::string& p_action): +LMDBAL::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), action(p_action) {} -std::string LMDBDataBase::Opened::getMessage() const { +std::string LMDBAL::Opened::getMessage() const { return "An attempt to " + action + " (the database " + dbName - + ") but it's can't be done because the DataBase is already opened"; + + ") but it's can't be done because the Base is already opened"; } -LMDBDataBase::NotFound::NotFound( +LMDBAL::NotFound::NotFound( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -75,12 +75,12 @@ LMDBDataBase::NotFound::NotFound( dbName(p_dbName), tableName(p_tableName) {} -std::string LMDBDataBase::NotFound::getMessage() const { +std::string LMDBAL::NotFound::getMessage() const { return "Element for id " + key + " wasn't found " + " in database " + dbName + " in table " + tableName;} -LMDBDataBase::Exist::Exist( +LMDBAL::Exist::Exist( const std::string& p_key, const std::string& p_dbName, const std::string& p_tableName @@ -90,14 +90,14 @@ LMDBDataBase::Exist::Exist( dbName(p_dbName), tableName(p_tableName) {} -std::string LMDBDataBase::Exist::getMessage() const { +std::string LMDBAL::Exist::getMessage() const { return "An attempt to insert element with key " + key + " to database " + dbName + " to table " + tableName + " but it already has an element with given id"; } -LMDBDataBase::Unknown::Unknown( +LMDBAL::Unknown::Unknown( const std::string& p_dbName, const std::string& message, const std::optional& p_tableName @@ -107,7 +107,7 @@ LMDBDataBase::Unknown::Unknown( tableName(p_tableName), msg(message) {} -std::string LMDBDataBase::Unknown::getMessage() const { +std::string LMDBAL::Unknown::getMessage() const { std::string result = "Unknown error in database " + dbName; if (tableName.has_value()) result += " in table " + tableName.value(); diff --git a/exceptions.h b/src/exceptions.h similarity index 95% rename from exceptions.h rename to src/exceptions.h index 87dc438..98db230 100644 --- a/exceptions.h +++ b/src/exceptions.h @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_EXCEPTIONS_H -#define LMDBDATABASE_EXCEPTIONS_H +#ifndef LMDBAL_EXCEPTIONS_H +#define LMDBAL_EXCEPTIONS_H #include #include #include -namespace LMDBDataBase { +namespace LMDBAL { class Exception : public std::exception { public: @@ -98,4 +98,4 @@ private: } -#endif //LMDBDATABASE_EXCEPTIONS_H +#endif //LMDBAL_EXCEPTIONS_H diff --git a/operators.hpp b/src/operators.hpp similarity index 98% rename from operators.hpp rename to src/operators.hpp index c4010ff..352534a 100644 --- a/operators.hpp +++ b/src/operators.hpp @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef CORE_OPERATORS_HPP -#define CORE_OPERATORS_HPP +#ifndef LMDBAL_OPERATORS_HPP +#define LMDBAL_OPERATORS_HPP #include #include @@ -206,4 +206,4 @@ QDataStream& operator >> (QDataStream &in, std::list& container) { return in; } -#endif //CORE_OPERATORS_HPP +#endif //LMDBAL_OPERATORS_HPP diff --git a/serializer.h b/src/serializer.h similarity index 91% rename from serializer.h rename to src/serializer.h index 8f00db9..145fe5e 100644 --- a/serializer.h +++ b/src/serializer.h @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_H -#define LMDBDATABASE_SERIALIZER_H +#ifndef LMDBAL_SERIALIZER_H +#define LMDBAL_SERIALIZER_H #include @@ -23,9 +23,9 @@ #include #include -#include "database.h" +#include -namespace LMDBDataBase { +namespace LMDBAL { template class Serializer { @@ -66,4 +66,4 @@ private: #include "serializer_qstring.hpp" #include "serializer_qbytearray.hpp" -#endif // LMDBDATABASE_SERIALIZER_H +#endif // LMDBAL_SERIALIZER_H diff --git a/serializer.hpp b/src/serializer.hpp similarity index 73% rename from serializer.hpp rename to src/serializer.hpp index a580a88..a03a32d 100644 --- a/serializer.hpp +++ b/src/serializer.hpp @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_HPP -#define LMDBDATABASE_SERIALIZER_HPP +#ifndef LMDBAL_SERIALIZER_HPP +#define LMDBAL_SERIALIZER_HPP #include "serializer.h" template -LMDBDataBase::Serializer::Serializer() : +LMDBAL::Serializer::Serializer() : bytes(), buffer(&bytes), stream(&buffer) @@ -29,7 +29,7 @@ LMDBDataBase::Serializer::Serializer() : } template -LMDBDataBase::Serializer::Serializer(const T& value) : +LMDBAL::Serializer::Serializer(const T& value) : bytes(), buffer(&bytes), stream(&buffer) @@ -39,19 +39,19 @@ LMDBDataBase::Serializer::Serializer(const T& value) : } template -LMDBDataBase::Serializer::~Serializer() { +LMDBAL::Serializer::~Serializer() { buffer.close(); } template -MDB_val LMDBDataBase::Serializer::setData(const T& value) { +MDB_val LMDBAL::Serializer::setData(const T& value) { clear(); _setData(value); return getData(); } template -T LMDBDataBase::Serializer::deserialize(const MDB_val& value) { +T LMDBAL::Serializer::deserialize(const MDB_val& value) { clear(); bytes.setRawData((char*)value.mv_data, value.mv_size); T result; @@ -61,19 +61,19 @@ T LMDBDataBase::Serializer::deserialize(const MDB_val& value) { } template -void LMDBDataBase::Serializer::_setData(const T& value) { +void LMDBAL::Serializer::_setData(const T& value) { stream << value; } template -void LMDBDataBase::Serializer::clear() { +void LMDBAL::Serializer::clear() { if (buffer.pos() > 0) { buffer.seek(0); } } template -MDB_val LMDBDataBase::Serializer::getData() { +MDB_val LMDBAL::Serializer::getData() { MDB_val val; val.mv_size = buffer.pos(); @@ -82,4 +82,4 @@ MDB_val LMDBDataBase::Serializer::getData() { return val; } -#endif //LMDBDATABASE_SERIALIZER_HPP +#endif //LMDBAL_SERIALIZER_HPP diff --git a/serializer_double.hpp b/src/serializer_double.hpp similarity index 89% rename from serializer_double.hpp rename to src/serializer_double.hpp index 6cdbf01..8a165fe 100644 --- a/serializer_double.hpp +++ b/src/serializer_double.hpp @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_DOUBLE_HPP -#define LMDBDATABASE_SERIALIZER_DOUBLE_HPP +#ifndef LMDBAL_SERIALIZER_DOUBLE_HPP +#define LMDBAL_SERIALIZER_DOUBLE_HPP -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -48,7 +48,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_DOUBLE_HPP +#endif //LMDBAL_SERIALIZER_DOUBLE_HPP diff --git a/serializer_float.hpp b/src/serializer_float.hpp similarity index 89% rename from serializer_float.hpp rename to src/serializer_float.hpp index 29ce0e0..223968b 100644 --- a/serializer_float.hpp +++ b/src/serializer_float.hpp @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_FLOAT_HPP -#define LMDBDATABASE_SERIALIZER_FLOAT_HPP +#ifndef LMDBAL_SERIALIZER_FLOAT_HPP +#define LMDBAL_SERIALIZER_FLOAT_HPP -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -48,7 +48,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_FLOAT_HPP +#endif //LMDBAL_SERIALIZER_FLOAT_HPP diff --git a/serializer_int16.hpp b/src/serializer_int16.hpp similarity index 89% rename from serializer_int16.hpp rename to src/serializer_int16.hpp index ff07821..81f4c62 100644 --- a/serializer_int16.hpp +++ b/src/serializer_int16.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_INT16_HPP -#define LMDBDATABASE_SERIALIZER_INT16_HPP +#ifndef LMDBAL_SERIALIZER_INT16_HPP +#define LMDBAL_SERIALIZER_INT16_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,4 +50,4 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_INT16_HPP +#endif //LMDBAL_SERIALIZER_INT16_HPP diff --git a/serializer_int32.hpp b/src/serializer_int32.hpp similarity index 89% rename from serializer_int32.hpp rename to src/serializer_int32.hpp index 28da663..cc0e352 100644 --- a/serializer_int32.hpp +++ b/src/serializer_int32.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_INT32_HPP -#define LMDBDATABASE_SERIALIZER_INT32_HPP +#ifndef LMDBAL_SERIALIZER_INT32_HPP +#define LMDBAL_SERIALIZER_INT32_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,7 +50,7 @@ private: } -#endif //CORE_DATABASE_SERIALIZER_INT32_HPP +#endif //LMDBAL_SERIALIZER_INT32_HPP diff --git a/serializer_int64.hpp b/src/serializer_int64.hpp similarity index 89% rename from serializer_int64.hpp rename to src/serializer_int64.hpp index 14c7426..7ef667e 100644 --- a/serializer_int64.hpp +++ b/src/serializer_int64.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_INT64_HPP -#define LMDBDATABASE_SERIALIZER_INT64_HPP +#ifndef LMDBAL_SERIALIZER_INT64_HPP +#define LMDBAL_SERIALIZER_INT64_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,7 +50,7 @@ private: } -#endif //CORE_DATABASE_SERIALIZER_INT64_HPP +#endif //LMDBAL_SERIALIZER_INT64_HPP diff --git a/serializer_int8.hpp b/src/serializer_int8.hpp similarity index 89% rename from serializer_int8.hpp rename to src/serializer_int8.hpp index d6df4c1..0656d7a 100644 --- a/serializer_int8.hpp +++ b/src/serializer_int8.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_INT8_HPP -#define LMDBDATABASE_SERIALIZER_INT8_HPP +#ifndef LMDBAL_SERIALIZER_INT8_HPP +#define LMDBAL_SERIALIZER_INT8_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,7 +50,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_INT8_HPP +#endif //LMDBAL_SERIALIZER_INT8_HPP diff --git a/serializer_qbytearray.hpp b/src/serializer_qbytearray.hpp similarity index 89% rename from serializer_qbytearray.hpp rename to src/serializer_qbytearray.hpp index bbb8050..86e72d6 100644 --- a/serializer_qbytearray.hpp +++ b/src/serializer_qbytearray.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP -#define LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP +#ifndef LMDBAL_SERIALIZER_QBYTEARRAY_HPP +#define LMDBAL_SERIALIZER_QBYTEARRAY_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -52,7 +52,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_QBYTEARRAY_HPP +#endif //LMDBAL_SERIALIZER_QBYTEARRAY_HPP diff --git a/serializer_qstring.hpp b/src/serializer_qstring.hpp similarity index 90% rename from serializer_qstring.hpp rename to src/serializer_qstring.hpp index 6073435..67f3bdf 100644 --- a/serializer_qstring.hpp +++ b/src/serializer_qstring.hpp @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_QSTRING_HPP -#define LMDBDATABASE_SERIALIZER_QSTRING_HPP +#ifndef LMDBAL_SERIALIZER_QSTRING_HPP +#define LMDBAL_SERIALIZER_QSTRING_HPP #include #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -51,7 +51,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_QSTRING_HPP +#endif //LMDBAL_SERIALIZER_QSTRING_HPP diff --git a/serializer_stdstring.hpp b/src/serializer_stdstring.hpp similarity index 89% rename from serializer_stdstring.hpp rename to src/serializer_stdstring.hpp index 9fdc152..c056469 100644 --- a/serializer_stdstring.hpp +++ b/src/serializer_stdstring.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_STDSTRING_HPP -#define LMDBDATABASE_SERIALIZER_STDSTRING_HPP +#ifndef LMDBAL_SERIALIZER_STDSTRING_HPP +#define LMDBAL_SERIALIZER_STDSTRING_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,7 +50,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_STDSTRING_HPP +#endif //LMDBAL_SERIALIZER_STDSTRING_HPP diff --git a/serializer_uint16.hpp b/src/serializer_uint16.hpp similarity index 89% rename from serializer_uint16.hpp rename to src/serializer_uint16.hpp index ea3324c..41bef80 100644 --- a/serializer_uint16.hpp +++ b/src/serializer_uint16.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_UINT16_HPP -#define LMDBDATABASE_SERIALIZER_UINT16_HPP +#ifndef LMDBAL_SERIALIZER_UINT16_HPP +#define LMDBAL_SERIALIZER_UINT16_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,6 +50,6 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_UINT16_HPP +#endif //LMDBAL_SERIALIZER_UINT16_HPP diff --git a/serializer_uint32.hpp b/src/serializer_uint32.hpp similarity index 89% rename from serializer_uint32.hpp rename to src/serializer_uint32.hpp index bd07534..8e4c763 100644 --- a/serializer_uint32.hpp +++ b/src/serializer_uint32.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_UINT32_HPP -#define LMDBDATABASE_SERIALIZER_UINT32_HPP +#ifndef LMDBAL_SERIALIZER_UINT32_HPP +#define LMDBAL_SERIALIZER_UINT32_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,4 +50,4 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_UINT32_HPP +#endif //LMDBAL_SERIALIZER_UINT32_HPP diff --git a/serializer_uint64.hpp b/src/serializer_uint64.hpp similarity index 89% rename from serializer_uint64.hpp rename to src/serializer_uint64.hpp index 9015378..6ea39d6 100644 --- a/serializer_uint64.hpp +++ b/src/serializer_uint64.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_UINT64_HPP -#define LMDBDATABASE_SERIALIZER_UINT64_HPP +#ifndef LMDBAL_SERIALIZER_UINT64_HPP +#define LMDBAL_SERIALIZER_UINT64_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,5 +50,5 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_UINT64_HPP +#endif //LMDBAL_SERIALIZER_UINT64_HPP diff --git a/serializer_uint8.hpp b/src/serializer_uint8.hpp similarity index 89% rename from serializer_uint8.hpp rename to src/serializer_uint8.hpp index ba2526c..4c77a25 100644 --- a/serializer_uint8.hpp +++ b/src/serializer_uint8.hpp @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_SERIALIZER_UINT8_HPP -#define LMDBDATABASE_SERIALIZER_UINT8_HPP +#ifndef LMDBAL_SERIALIZER_UINT8_HPP +#define LMDBAL_SERIALIZER_UINT8_HPP #include -namespace LMDBDataBase { +namespace LMDBAL { template<> class Serializer { @@ -50,7 +50,7 @@ private: } -#endif //LMDBDATABASE_SERIALIZER_UINT8_HPP +#endif //LMDBAL_SERIALIZER_UINT8_HPP diff --git a/storage.cpp b/src/storage.cpp similarity index 63% rename from storage.cpp rename to src/storage.cpp index 213a0f2..3ce6c25 100644 --- a/storage.cpp +++ b/src/storage.cpp @@ -16,15 +16,15 @@ #include "storage.h" -LMDBDataBase::StorageBase::StorageBase(const std::string& p_name, DataBase* parent): +LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): dbi(), db(parent), name(p_name) {} -LMDBDataBase::StorageBase::~StorageBase() {} +LMDBAL::iStorage::~iStorage() {} -void LMDBDataBase::StorageBase::drop() { +void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); MDB_txn *txn; @@ -42,22 +42,22 @@ void LMDBDataBase::StorageBase::drop() { mdb_txn_commit(txn); } -int LMDBDataBase::StorageBase::drop(MDB_txn* transaction) { +int LMDBAL::iStorage::drop(MDB_txn* transaction) { return mdb_drop(transaction, dbi, 0); } -const std::string & LMDBDataBase::StorageBase::dbName() const { +const std::string & LMDBAL::iStorage::dbName() const { return db->name;} -bool LMDBDataBase::StorageBase::isDBOpened() const { +bool LMDBAL::iStorage::isDBOpened() const { return db->opened;} -void LMDBDataBase::StorageBase::ensureOpened(const std::string& methodName) const { +void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { if (!db->opened) throw Closed(methodName, db->name, name); } -uint32_t LMDBDataBase::StorageBase::count() const { +uint32_t LMDBAL::iStorage::count() const { ensureOpened(countMethodName); MDB_txn *txn; @@ -78,7 +78,7 @@ uint32_t LMDBDataBase::StorageBase::count() const { return amount; } -void LMDBDataBase::StorageBase::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { +void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); if (rc == MDB_KEYEXIST) throwDuplicate(key); @@ -86,7 +86,7 @@ void LMDBDataBase::StorageBase::throwDuplicateOrUnknown(int rc, TransactionID tx throwUnknown(rc); } -void LMDBDataBase::StorageBase::throwNotFoundOrUnknown(int rc, LMDBDataBase::TransactionID txn, const std::string& key) const { +void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { abortTransaction(txn); if (rc == MDB_NOTFOUND) throwNotFound(key); @@ -94,28 +94,28 @@ void LMDBDataBase::StorageBase::throwNotFoundOrUnknown(int rc, LMDBDataBase::Tra throwUnknown(rc); } -void LMDBDataBase::StorageBase::throwUnknown(int rc, LMDBDataBase::TransactionID txn) const { +void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { abortTransaction(txn); throwUnknown(rc); } -void LMDBDataBase::StorageBase::throwUnknown(int rc) const { +void LMDBAL::iStorage::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} -void LMDBDataBase::StorageBase::throwDuplicate(const std::string& key) const { +void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { throw Exist(key, db->name, name);} -void LMDBDataBase::StorageBase::throwNotFound(const std::string& key) const { +void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} -LMDBDataBase::TransactionID LMDBDataBase::StorageBase::beginReadOnlyTransaction() const { +LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { return db->beginReadOnlyTransaction(name);} -LMDBDataBase::TransactionID LMDBDataBase::StorageBase::beginTransaction() const { +LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { return db->beginTransaction(name);} -void LMDBDataBase::StorageBase::abortTransaction(LMDBDataBase::TransactionID id) const { +void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { db->abortTransaction(id);} -void LMDBDataBase::StorageBase::commitTransaction(LMDBDataBase::TransactionID id) const { +void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) const { db->commitTransaction(id);} diff --git a/storage.h b/src/storage.h similarity index 88% rename from storage.h rename to src/storage.h index 19990c1..af2d77e 100644 --- a/storage.h +++ b/src/storage.h @@ -14,19 +14,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_STORAGE_H -#define LMDBDATABASE_STORAGE_H +#ifndef LMDBAL_STORAGE_H +#define LMDBAL_STORAGE_H -#include "database.h" +#include "base.h" #include "serializer.h" -namespace LMDBDataBase { +namespace LMDBAL { -class StorageBase { - friend class DataBase; +class iStorage { + friend class Base; protected: - StorageBase(const std::string& name, DataBase* parent); - virtual ~StorageBase(); + iStorage(const std::string& name, Base* parent); + virtual ~iStorage(); virtual int createTable(MDB_txn * transaction) = 0; virtual int drop(MDB_txn * transaction); @@ -53,7 +53,7 @@ public: protected: MDB_dbi dbi; - DataBase* db; + Base* db; const std::string name; inline static const std::string dropMethodName = "drop"; @@ -78,14 +78,14 @@ protected: }; template -class Storage : public StorageBase { - friend class DataBase; +class Storage : public iStorage { + friend class Base; protected: - Storage(const std::string& name, DataBase* parent); + Storage(const std::string& name, Base* parent); ~Storage() override; public: - using StorageBase::drop; + using iStorage::drop; virtual void addRecord(const K& key, const V& value); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change virtual void changeRecord(const K& key, const V& value); @@ -107,4 +107,4 @@ protected: #include "storage.hpp" -#endif // LMDBDATABASE_STORAGE_H +#endif //LMDBAL_STORAGE_H diff --git a/storage.hpp b/src/storage.hpp similarity index 77% rename from storage.hpp rename to src/storage.hpp index 198fcf1..bfecd4d 100644 --- a/storage.hpp +++ b/src/storage.hpp @@ -14,27 +14,27 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef LMDBDATABASE_STORAGE_HPP -#define LMDBDATABASE_STORAGE_HPP +#ifndef LMDBAL_STORAGE_HPP +#define LMDBAL_STORAGE_HPP #include "storage.h" #include "exceptions.h" template -LMDBDataBase::Storage::Storage(const std::string& p_name, DataBase* parent): - StorageBase(p_name, parent), +LMDBAL::Storage::Storage(const std::string& p_name, Base* parent): + iStorage(p_name, parent), keySerializer(new Serializer()), valueSerializer(new Serializer()) {} template -LMDBDataBase::Storage::~Storage() { +LMDBAL::Storage::~Storage() { delete valueSerializer; delete keySerializer; } template -void LMDBDataBase::Storage::addRecord(const K& key, const V& value) { +void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); TransactionID txn = beginTransaction(); @@ -49,7 +49,7 @@ void LMDBDataBase::Storage::addRecord(const K& key, const V& value) { } template -bool LMDBDataBase::Storage::forceRecord(const K& key, const V& value) { +bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { ensureOpened(forceRecordMethodName); bool added; @@ -81,7 +81,7 @@ bool LMDBDataBase::Storage::forceRecord(const K& key, const V& value) { } template -void LMDBDataBase::Storage::changeRecord(const K& key, const V& value) { +void LMDBAL::Storage::changeRecord(const K& key, const V& value) { ensureOpened(changeRecordMethodName); TransactionID txn = beginTransaction(); @@ -96,7 +96,7 @@ void LMDBDataBase::Storage::changeRecord(const K& key, const V& value) { } template -V LMDBDataBase::Storage::getRecord(const K& key) const { +V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -114,7 +114,7 @@ V LMDBDataBase::Storage::getRecord(const K& key) const { } template -bool LMDBDataBase::Storage::checkRecord(const K& key) const { +bool LMDBAL::Storage::checkRecord(const K& key) const { ensureOpened(checkRecordMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -134,7 +134,7 @@ bool LMDBDataBase::Storage::checkRecord(const K& key) const { } template -std::map LMDBDataBase::Storage::readAll() const { +std::map LMDBAL::Storage::readAll() const { ensureOpened(readAllMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -162,7 +162,7 @@ std::map LMDBDataBase::Storage::readAll() const { } template -void LMDBDataBase::Storage::replaceAll(const std::map& data) { +void LMDBAL::Storage::replaceAll(const std::map& data) { ensureOpened(replaceAllMethodName); TransactionID txn = beginTransaction(); @@ -183,7 +183,7 @@ void LMDBDataBase::Storage::replaceAll(const std::map& data) { } template -uint32_t LMDBDataBase::Storage::addRecords(const std::map& data, bool overwrite) { +uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool overwrite) { ensureOpened(addRecordsMethodName); TransactionID txn = beginTransaction(); @@ -211,7 +211,7 @@ uint32_t LMDBDataBase::Storage::addRecords(const std::map& data, boo } template -void LMDBDataBase::Storage::removeRecord(const K& key) { +void LMDBAL::Storage::removeRecord(const K& key) { ensureOpened(removeRecordMethodName); TransactionID txn = beginTransaction(); @@ -224,68 +224,68 @@ void LMDBDataBase::Storage::removeRecord(const K& key) { } template -int LMDBDataBase::Storage::createTable(MDB_txn* transaction) { +int LMDBAL::Storage::createTable(MDB_txn* transaction) { return makeTable(transaction); } template -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBDataBase::StorageBase::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template -inline std::string LMDBDataBase::StorageBase::toString(const T& value) { +inline std::string LMDBAL::iStorage::toString(const T& value) { return std::to_string(value); } template<> -inline std::string LMDBDataBase::StorageBase::toString(const QString& value) { +inline std::string LMDBAL::iStorage::toString(const QString& value) { return value.toStdString(); } template<> -inline std::string LMDBDataBase::StorageBase::toString(const std::string& value) { +inline std::string LMDBAL::iStorage::toString(const std::string& value) { return value; } -#endif //LMDBDATABASE_STORAGE_HPP +#endif //LMDBAL_STORAGE_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d35802f..0b5208c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,14 +8,14 @@ add_executable(runUnitTests target_compile_options(runUnitTests PRIVATE -fPIC) -target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}/src) target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) target_link_libraries( runUnitTests GTest::gtest_main - storage + lmdbal ) include(GoogleTest) diff --git a/test/basic.cpp b/test/basic.cpp index cc92a5f..9c9ad62 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -1,24 +1,24 @@ #include -#include "database.h" +#include "base.h" #include "storage.h" #include "cache.h" #include -class DataBaseTest : public ::testing::Test { +class BaseTest : public ::testing::Test { protected: - DataBaseTest(): + BaseTest(): ::testing::Test(), t1(db->getTable("table1")), t2(db->getTable("table2")), c1(db->getCache("cache1")) {} - ~DataBaseTest() {} + ~BaseTest() {} static void SetUpTestSuite() { if (db == nullptr) { - db = new LMDBDataBase::DataBase("testBase"); + db = new LMDBAL::Base("testBase"); db->addTable("table1"); db->addTable("table2"); db->addCache("cache1"); @@ -32,21 +32,21 @@ protected: db = nullptr; } - static LMDBDataBase::DataBase* db; + static LMDBAL::Base* db; - LMDBDataBase::Storage* t1; - LMDBDataBase::Storage* t2; - LMDBDataBase::Cache* c1; + LMDBAL::Storage* t1; + LMDBAL::Storage* t2; + LMDBAL::Cache* c1; }; -LMDBDataBase::DataBase* DataBaseTest::db = nullptr; +LMDBAL::Base* BaseTest::db = nullptr; -TEST_F(DataBaseTest, RemovingDirectory) { +TEST_F(BaseTest, RemovingDirectory) { EXPECT_EQ(db->removeDirectory(), true); } -TEST_F(DataBaseTest, OpeningClosingDatabase) { +TEST_F(BaseTest, OpeningClosingDatabase) { EXPECT_EQ(db->ready(), false); db->open(); EXPECT_EQ(db->ready(), true); @@ -56,7 +56,7 @@ TEST_F(DataBaseTest, OpeningClosingDatabase) { EXPECT_EQ(db->ready(), true); } -TEST_F(DataBaseTest, AddingIntegerKey) { +TEST_F(BaseTest, AddingIntegerKey) { EXPECT_EQ(db->ready(), true); t1->addRecord(1, 2); t1->addRecord(2, 2); @@ -64,7 +64,7 @@ TEST_F(DataBaseTest, AddingIntegerKey) { EXPECT_EQ(t1->getRecord(1), 2); } -TEST_F(DataBaseTest, AddingQStringKey) { +TEST_F(BaseTest, AddingQStringKey) { EXPECT_EQ(db->ready(), true); t2->addRecord("hello", "world"); t2->addRecord("aaa", "gagdfsdf"); @@ -73,7 +73,7 @@ TEST_F(DataBaseTest, AddingQStringKey) { EXPECT_EQ(t2->getRecord("hello"), "world"); } -TEST_F(DataBaseTest, AddingKeysToCache) { +TEST_F(BaseTest, AddingKeysToCache) { EXPECT_EQ(db->ready(), true); c1->addRecord(2, "blah balah"); c1->addRecord(-4, "testing goes brrr"); @@ -83,48 +83,48 @@ TEST_F(DataBaseTest, AddingKeysToCache) { EXPECT_EQ(c1->getRecord(-116), "whatever"); } -TEST_F(DataBaseTest, AddingRepeatingIntegerKey) { +TEST_F(BaseTest, AddingRepeatingIntegerKey) { EXPECT_EQ(db->ready(), true); bool thrown = false; try { t1->addRecord(3, 24); - } catch (const LMDBDataBase::Exist e) { + } catch (const LMDBAL::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; EXPECT_EQ(t1->getRecord(3), 15); } -TEST_F(DataBaseTest, AddingRepeatingStringKey) { +TEST_F(BaseTest, AddingRepeatingStringKey) { EXPECT_EQ(db->ready(), true); bool thrown = false; try { t2->addRecord("sdfhga", "world"); - } catch (const LMDBDataBase::Exist e) { + } catch (const LMDBAL::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); } -TEST_F(DataBaseTest, AddingRepeatingCacheKey) { +TEST_F(BaseTest, AddingRepeatingCacheKey) { EXPECT_EQ(db->ready(), true); bool thrown = false; try { c1->addRecord(-4, "world"); - } catch (const LMDBDataBase::Exist e) { + } catch (const LMDBAL::Exist e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); } -TEST_F(DataBaseTest, GettingNotExistingKeys) { +TEST_F(BaseTest, GettingNotExistingKeys) { EXPECT_EQ(db->ready(), true); bool thrown = false; try { QString wrong = t2->getRecord("almonds"); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -132,7 +132,7 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { thrown = false; try { uint32_t wrong = t1->getRecord(64); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -140,18 +140,18 @@ TEST_F(DataBaseTest, GettingNotExistingKeys) { thrown = false; try { std::string wrong = c1->getRecord(21); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; } -TEST_F(DataBaseTest, Persistence) { +TEST_F(BaseTest, Persistence) { EXPECT_EQ(db->ready(), true); db->close(); delete db; - db = new LMDBDataBase::DataBase("testBase"); + db = new LMDBAL::Base("testBase"); t1 = db->addTable("table1"); t2 = db->addTable("table2"); c1 = db->addCache("cache1"); @@ -175,7 +175,7 @@ TEST_F(DataBaseTest, Persistence) { bool thrown = false; try { QString wrong = t2->getRecord("cats"); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -183,7 +183,7 @@ TEST_F(DataBaseTest, Persistence) { thrown = false; try { uint32_t wrong = t1->getRecord(7893); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; @@ -191,13 +191,13 @@ TEST_F(DataBaseTest, Persistence) { thrown = false; try { std::string wrong = c1->getRecord(89); - } catch (const LMDBDataBase::NotFound e) { + } catch (const LMDBAL::NotFound e) { thrown = true; } ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; } -TEST_F(DataBaseTest, CountAndDrop) { +TEST_F(BaseTest, CountAndDrop) { EXPECT_EQ(db->ready(), true); EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t2->count(), 4); @@ -219,7 +219,7 @@ TEST_F(DataBaseTest, CountAndDrop) { EXPECT_EQ(c1->count(), 2); } -TEST_F(DataBaseTest, Change) { +TEST_F(BaseTest, Change) { EXPECT_EQ(db->ready(), true); EXPECT_EQ(t1->count(), 1); EXPECT_EQ(t2->count(), 1); @@ -253,7 +253,7 @@ TEST_F(DataBaseTest, Change) { EXPECT_EQ(c1->count(), 3); } -TEST_F(DataBaseTest, Force) { +TEST_F(BaseTest, Force) { EXPECT_EQ(db->ready(), true); t1->forceRecord(58, 35); //changing From f5612dc3c59c8528b83fb5bc550097e3db2c8e1d Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 22 Mar 2023 19:30:41 +0300 Subject: [PATCH 024/125] cmake magick to make package usable and discoverable by another CMake projects, readme, pkgbuild --- CMakeLists.txt | 72 ++++++++++++++++++++++++--------- README.md | 77 ++++++++++++++++++++++++++++++++++++ cmake/Config.cmake.in | 5 +++ packaging/Archlinux/PKGBUILD | 23 +++++++++++ src/CMakeLists.txt | 4 +- test/CMakeLists.txt | 2 +- 6 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 README.md create mode 100644 cmake/Config.cmake.in create mode 100644 packaging/Archlinux/PKGBUILD diff --git a/CMakeLists.txt b/CMakeLists.txt index 61b7919..9261ed4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,18 @@ cmake_minimum_required(VERSION 3.16) -project(lmdbal VERSION 0.2.0 LANGUAGES CXX) +project(LMDBAL VERSION 0.2.0 LANGUAGES CXX) +string(TOLOWER ${PROJECT_NAME} PROJECT_LOW) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) -option(BUILD_STATIC "Builds library as static library" ON) -option(BUILD_TESTS "Builds tests" ON) +option(BUILD_STATIC "Builds library as static library" OFF) +option(BUILD_TESTS "Builds tests" OFF) + include(GNUInstallDirs) +include(CMakePackageConfigHelpers) set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") @@ -29,31 +30,64 @@ if (NOT CMAKE_BUILD_TYPE) endif () if (BUILD_STATIC) - add_library(lmdbal STATIC ${SOURCES}) + add_library(${PROJECT_NAME} STATIC) else () - add_library(lmdbal SHARED ${SOURCES}) + add_library(${PROJECT_NAME} SHARED) endif() +set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version}) +set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1) +set_property(TARGET ${PROJECT_NAME} PROPERTY + INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1) +set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY + COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION +) + add_subdirectory(src) if (BUILD_TESTS) add_subdirectory(test) endif () -target_include_directories(lmdbal PUBLIC ${CMAKE_SOURCE_DIR}/src) -target_include_directories(lmdbal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(lmdbal PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) -target_include_directories(lmdbal PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) - -target_link_libraries(lmdbal - PRIVATE - Qt${QT_VERSION_MAJOR}::Core +target_include_directories(${PROJECT_NAME} PUBLIC + "$" + "$" ) -target_link_libraries(lmdbal PRIVATE lmdb) +target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) -install(TARGETS lmdbal +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + lmdb +) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} +) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake" + VERSION "${version}" + COMPATIBILITY AnyNewerVersion +) + +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_LOW}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/lmdbal - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lmdbal + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW} ) +install(EXPORT ${PROJECT_LOW}Targets + FILE ${PROJECT_LOW}Targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..89ce3fd --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# LMDBAL - Lightning Memory Data Base Abstraction Level + +[![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) +[![AUR version](https://img.shields.io/aur/version/lmdbal?style=flat-square)](https://aur.archlinux.org/packages/lmdbal/) +[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) + +### Prerequisites + +- QT 5 *(lower versions might work but it wasn't tested)* +- lmdb +- CMake 3.16 or higher + +### Using with CMake + +#### As a system library + +If you're using LMDBAL as a system library you probably have no control over it's build options. The easiest way to include the project is to add following + +``` +find_package(lmdbal) +if (LMDBAL_FOUND) + target_include_directories(yourTarget PRIVATE {LMDBAL_INCLUDE_DIRS}) + target_link_libraries(yourTarget PRIVATE LMDBAL::LMDBAL) +endif() +``` + +#### As an embeded subproject +//TODO + +### Building + +LMDBAL uses CMake as a build system. + +Please check the prerequisites and install them before building. + +Here is an easy way to build a project + +``` +$ git clone https://git.macaw.me/blue/lmdbal +$ cd lmdbal +$ mkdir build +$ cd build +$ cmake .. [ *optional keys* ] +$ cmake --build . +$ cmake --install . --prefix install +``` + +This way will create you a `lmdbal/build` directory with temporary files, and `lmdbal/build/install` with all the export files for installation to the system. + +After `cmake ..` you can specify keys to alter the building process. In this context building keys are transfered like so + +``` +cmake .. -D KEY1=VALUE1 -D KEY2=VALUE2 ... +``` + +#### List of keys + +Here is the list of keys you can pass to configuration phase of `cmake ..`: + +- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`); +- `BUILD_STATIC` - `True` builds project as a static library, `False` builds as dynamic (default is `False`); +- `BUILD_TESTS` - `True` build unit tests, `False` does not (default is `False`); +- `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically; + +#### Running tests + +If you built the library with `-D BUILD_TESTS=True`, then there will be `lmdbal/build/tests/runUnitTests` executable file. You can simply run it as + +``` +./runUnitTests +``` + +if you're in the same directory with it + +## License + +This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 0000000..8fea032 --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/lmdbalTargets.cmake") + +check_required_components(lmdbal) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD new file mode 100644 index 0000000..7dc7ed7 --- /dev/null +++ b/packaging/Archlinux/PKGBUILD @@ -0,0 +1,23 @@ +# Maintainer: Yury Gubich +pkgname=lmdbal +pkgver=0.2.0 +pkgrel=1 +pkgdesc="LMDB Abstraction Layer, qt5 version" +arch=('i686' 'x86_64') +url="https://git.macaw.me/blue/lmdbal" +license=('GPL3') +depends=( 'lmdb' 'qt5-base') +makedepends=('cmake>=3.16') +optdepends=() + +source=("$pkgname-$pkgver.tar.gz") +sha256sums=('SKIP') +build() { + cd "$srcdir/$pkgname" + cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -D QT_VERSION_MAJOR=5 + cmake --build . +} +package() { + cd "$srcdir/$pkgname" + DESTDIR="$pkgdir/" cmake --install . +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f5ca470..7b0d5ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,9 +29,9 @@ set(HEADERS operators.hpp ) -target_sources(lmdbal PRIVATE +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES} ${HEADERS} ) -set_target_properties(lmdbal PROPERTIES PUBLIC_HEADER "${HEADERS}") +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0b5208c..1270e31 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,7 +15,7 @@ target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCL target_link_libraries( runUnitTests GTest::gtest_main - lmdbal + ${PROJECT_NAME} ) include(GoogleTest) From 763d956bf8f904dd0f621e636ee64e6772aa85bf Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 23 Mar 2023 20:27:46 +0300 Subject: [PATCH 025/125] licensing, initial documentation initiative --- CMakeLists.txt | 9 + LICENSE.md | 675 ++++++++++++++++++++++++++++++++++ README.md | 4 +- doc/CMakeLists.txt | 10 + doc/mainpage.dox | 30 ++ src/base.cpp | 39 +- src/base.h | 58 +-- src/cache.h | 32 +- src/cache.hpp | 32 +- src/exceptions.cpp | 32 +- src/exceptions.h | 32 +- src/operators.hpp | 32 +- src/serializer.h | 32 +- src/serializer.hpp | 32 +- src/serializer_double.hpp | 32 +- src/serializer_float.hpp | 32 +- src/serializer_int16.hpp | 32 +- src/serializer_int32.hpp | 32 +- src/serializer_int64.hpp | 32 +- src/serializer_int8.hpp | 32 +- src/serializer_qbytearray.hpp | 32 +- src/serializer_qstring.hpp | 32 +- src/serializer_stdstring.hpp | 32 +- src/serializer_uint16.hpp | 32 +- src/serializer_uint32.hpp | 32 +- src/serializer_uint64.hpp | 32 +- src/serializer_uint8.hpp | 32 +- src/storage.cpp | 32 +- src/storage.h | 32 +- src/storage.hpp | 32 +- 30 files changed, 1178 insertions(+), 383 deletions(-) create mode 100644 LICENSE.md create mode 100644 doc/CMakeLists.txt create mode 100644 doc/mainpage.dox diff --git a/CMakeLists.txt b/CMakeLists.txt index 9261ed4..d301fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ cmake_policy(SET CMP0079 NEW) option(BUILD_STATIC "Builds library as static library" OFF) option(BUILD_TESTS "Builds tests" OFF) +option(BUILD_DOC "Builds documentation" OFF) include(GNUInstallDirs) include(CMakePackageConfigHelpers) @@ -44,6 +45,14 @@ set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY ) add_subdirectory(src) +if (BUILD_DOC) + find_package(Doxygen) + if (DOXYGEN_FOUND) + add_subdirectory(doc) + else() + message("Was trying to build documentation, but Doxygen was not found, skipping documentation") + endif() +endif() if (BUILD_TESTS) add_subdirectory(test) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2fb2e74 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,675 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +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. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +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 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. Use with the GNU Affero General Public License. + +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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU 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 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 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 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. + + + Copyright (C) + + 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +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 GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . diff --git a/README.md b/README.md index 89ce3fd..073be29 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ ### Prerequisites -- QT 5 *(lower versions might work but it wasn't tested)* +- Qt 5 or higher - lmdb - CMake 3.16 or higher +- Doxygen (optional, for documentation) ### Using with CMake @@ -60,6 +61,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`: - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`); - `BUILD_STATIC` - `True` builds project as a static library, `False` builds as dynamic (default is `False`); - `BUILD_TESTS` - `True` build unit tests, `False` does not (default is `False`); +- `BUILD_DOC` - `True` build doxygen documentation, `False` does not (default is `False`); - `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically; #### Running tests diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..4998bb0 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,10 @@ +set(DOXYGEN_GENERATE_HTML YES) +set(DOXYGEN_GENERATE_MAN YES) + +doxygen_add_docs( + documentation + ${PROJECT_SOURCE_DIR}/src + mainpage.dox + ALL + COMMENT "Generate man and html pages" +) diff --git a/doc/mainpage.dox b/doc/mainpage.dox new file mode 100644 index 0000000..95db214 --- /dev/null +++ b/doc/mainpage.dox @@ -0,0 +1,30 @@ +/*! \mainpage Getting Started + * + * Everything begins with a data nase represented by the class LMDBAL::Base. + * It repesents a collection of key-value storages that are going to be stored in a sigle data base directory. + * To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine. + * + * LMDBAL::Base creates or opens existing directory with the given name in the location acquired with + * QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + * so, the file system destination of your data would depend on the + * QCoreApplication configuration of your app. + * + * After you have created a LMDBAL::Base you probably want to obtain storage handlers. + * Now there are only two available types of them: LMDBAL::Storage and LMDBAL::Cache. + * The only difference between them is that LMDBAL::Cache additionally stores elements in a + * std::map + * to speed up the access. + * + * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&) or LMDBAL::Base::addCache(const std::string& name). + * Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them. + * You are not obliged to save those handlers, + * you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&) + * calling them with the same template types and names. + * + * After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open(). + * At this point you are not allowed to add any more storages, otherwise LMDBAL::Opened exception will be thrown. + * It's currently the limitation of this little library and I might solve it in the future. + * Database will throw no exception if you will try to close the closed LMDBAL::Base or open again already opened one. + * Also it will automatically close itself you will try to destoroy onpened LMDBAL::Base. + * + */ diff --git a/src/base.cpp b/src/base.cpp index 1f9003a..15e833b 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "base.h" #include "exceptions.h" @@ -38,15 +40,22 @@ LMDBAL::Base::~Base() { void LMDBAL::Base::close() { if (opened) { + for (LMDBAL::TransactionID id : *transactions) + mdb_txn_abort(id); + for (const std::pair& pair : tables) { iStorage* table = pair.second; mdb_dbi_close(environment, table->dbi); } mdb_env_close(environment); + transactions->clear(); opened = false; } } +/** + * Almost every LMDBAL::Base require it to be opened, this function opens it. It laso creates the directory for the database if it was an initial launch + */ void LMDBAL::Base::open() { if (!opened) { mdb_env_create(&environment); diff --git a/src/base.h b/src/base.h index 42cc26c..024eb98 100644 --- a/src/base.h +++ b/src/base.h @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_BASE_H #define LMDBAL_BASE_H @@ -67,19 +69,19 @@ public: void abortTransaction(TransactionID id) const; template - Storage* addTable(const std::string& name); + LMDBAL::Storage* addStorage(const std::string& name); template - Cache* addCache(const std::string& name); + LMDBAL::Cache* addCache(const std::string& name); template - Storage* getTable(const std::string& name); + LMDBAL::Storage* getStorage(const std::string& name); template - Cache* getCache(const std::string& name); + LMDBAL::Cache* getCache(const std::string& name); private: - typedef std::map Tables; + typedef std::map Tables; typedef std::set Transactions; TransactionID beginReadOnlyTransaction(const std::string& storageName) const; @@ -101,8 +103,11 @@ private: } #include "operators.hpp" +/** + * Adds LMDBAL::Storage with the given name to the LMDBAL::Base, also returns it. The LMDBAL::Base must be closed + */ template -LMDBAL::Storage* LMDBAL::Base::addTable(const std::string& p_name) { +LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { if (opened) { throw Opened(name, "add table " + p_name); } @@ -111,6 +116,9 @@ LMDBAL::Storage* LMDBAL::Base::addTable(const std::string& p_name) { return table; } +/** + * Adds LMDBAL::Cache with given the name to the LMDBAL::Base, also returns it. The LMDBAL::Base must be closed + */ template LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { if (opened) { @@ -121,11 +129,17 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { return cache; } +/** + * Returns LMDBAL::Storage with the given name + */ template -LMDBAL::Storage* LMDBAL::Base::getTable(const std::string& p_name) { +LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { return static_cast*>(tables.at(p_name)); } +/** + * Returns LMDBAL::Cache with the given name + */ template LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& p_name) { return static_cast*>(tables.at(p_name)); diff --git a/src/cache.h b/src/cache.h index 52dd88a..368c801 100644 --- a/src/cache.h +++ b/src/cache.h @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_CACHE_H #define LMDBAL_CACHE_H diff --git a/src/cache.hpp b/src/cache.hpp index e0cc1d0..4838ee6 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_CACHE_HPP #define LMDBAL_CACHE_HPP diff --git a/src/exceptions.cpp b/src/exceptions.cpp index 49ca0a4..e286ee0 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "exceptions.h" diff --git a/src/exceptions.h b/src/exceptions.h index 98db230..87caecc 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_EXCEPTIONS_H #define LMDBAL_EXCEPTIONS_H diff --git a/src/operators.hpp b/src/operators.hpp index 352534a..795496a 100644 --- a/src/operators.hpp +++ b/src/operators.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_OPERATORS_HPP #define LMDBAL_OPERATORS_HPP diff --git a/src/serializer.h b/src/serializer.h index 145fe5e..84a368b 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_H #define LMDBAL_SERIALIZER_H diff --git a/src/serializer.hpp b/src/serializer.hpp index a03a32d..c0c5172 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_HPP #define LMDBAL_SERIALIZER_HPP diff --git a/src/serializer_double.hpp b/src/serializer_double.hpp index 8a165fe..8e9dc5d 100644 --- a/src/serializer_double.hpp +++ b/src/serializer_double.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_DOUBLE_HPP #define LMDBAL_SERIALIZER_DOUBLE_HPP diff --git a/src/serializer_float.hpp b/src/serializer_float.hpp index 223968b..d385150 100644 --- a/src/serializer_float.hpp +++ b/src/serializer_float.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_FLOAT_HPP #define LMDBAL_SERIALIZER_FLOAT_HPP diff --git a/src/serializer_int16.hpp b/src/serializer_int16.hpp index 81f4c62..05d0091 100644 --- a/src/serializer_int16.hpp +++ b/src/serializer_int16.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_INT16_HPP #define LMDBAL_SERIALIZER_INT16_HPP diff --git a/src/serializer_int32.hpp b/src/serializer_int32.hpp index cc0e352..be6c6a9 100644 --- a/src/serializer_int32.hpp +++ b/src/serializer_int32.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_INT32_HPP #define LMDBAL_SERIALIZER_INT32_HPP diff --git a/src/serializer_int64.hpp b/src/serializer_int64.hpp index 7ef667e..1729557 100644 --- a/src/serializer_int64.hpp +++ b/src/serializer_int64.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_INT64_HPP #define LMDBAL_SERIALIZER_INT64_HPP diff --git a/src/serializer_int8.hpp b/src/serializer_int8.hpp index 0656d7a..37c1588 100644 --- a/src/serializer_int8.hpp +++ b/src/serializer_int8.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_INT8_HPP #define LMDBAL_SERIALIZER_INT8_HPP diff --git a/src/serializer_qbytearray.hpp b/src/serializer_qbytearray.hpp index 86e72d6..43713ec 100644 --- a/src/serializer_qbytearray.hpp +++ b/src/serializer_qbytearray.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_QBYTEARRAY_HPP #define LMDBAL_SERIALIZER_QBYTEARRAY_HPP diff --git a/src/serializer_qstring.hpp b/src/serializer_qstring.hpp index 67f3bdf..af716a7 100644 --- a/src/serializer_qstring.hpp +++ b/src/serializer_qstring.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_QSTRING_HPP #define LMDBAL_SERIALIZER_QSTRING_HPP diff --git a/src/serializer_stdstring.hpp b/src/serializer_stdstring.hpp index c056469..64a8970 100644 --- a/src/serializer_stdstring.hpp +++ b/src/serializer_stdstring.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_STDSTRING_HPP #define LMDBAL_SERIALIZER_STDSTRING_HPP diff --git a/src/serializer_uint16.hpp b/src/serializer_uint16.hpp index 41bef80..b78d948 100644 --- a/src/serializer_uint16.hpp +++ b/src/serializer_uint16.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_UINT16_HPP #define LMDBAL_SERIALIZER_UINT16_HPP diff --git a/src/serializer_uint32.hpp b/src/serializer_uint32.hpp index 8e4c763..f41a845 100644 --- a/src/serializer_uint32.hpp +++ b/src/serializer_uint32.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_UINT32_HPP #define LMDBAL_SERIALIZER_UINT32_HPP diff --git a/src/serializer_uint64.hpp b/src/serializer_uint64.hpp index 6ea39d6..f59b91e 100644 --- a/src/serializer_uint64.hpp +++ b/src/serializer_uint64.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_UINT64_HPP #define LMDBAL_SERIALIZER_UINT64_HPP diff --git a/src/serializer_uint8.hpp b/src/serializer_uint8.hpp index 4c77a25..ba19ee3 100644 --- a/src/serializer_uint8.hpp +++ b/src/serializer_uint8.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_SERIALIZER_UINT8_HPP #define LMDBAL_SERIALIZER_UINT8_HPP diff --git a/src/storage.cpp b/src/storage.cpp index 3ce6c25..fd4ee4d 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "storage.h" diff --git a/src/storage.h b/src/storage.h index af2d77e..0d53fc7 100644 --- a/src/storage.h +++ b/src/storage.h @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_STORAGE_H #define LMDBAL_STORAGE_H diff --git a/src/storage.hpp b/src/storage.hpp index bfecd4d..816dd2f 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -1,18 +1,20 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef LMDBAL_STORAGE_HPP #define LMDBAL_STORAGE_HPP From c83369f34761e7a053d62312bd07fe5b3db3a519 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 26 Mar 2023 21:42:52 +0300 Subject: [PATCH 026/125] fixed tests build --- test/CMakeLists.txt | 3 ++- test/basic.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1270e31..5f1a0e0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,7 +16,8 @@ target_link_libraries( runUnitTests GTest::gtest_main ${PROJECT_NAME} + Qt${QT_VERSION_MAJOR}::Core + lmdb ) - include(GoogleTest) gtest_discover_tests(runUnitTests) diff --git a/test/basic.cpp b/test/basic.cpp index 9c9ad62..509f896 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -10,8 +10,8 @@ class BaseTest : public ::testing::Test { protected: BaseTest(): ::testing::Test(), - t1(db->getTable("table1")), - t2(db->getTable("table2")), + t1(db->getStorage("table1")), + t2(db->getStorage("table2")), c1(db->getCache("cache1")) {} ~BaseTest() {} @@ -19,8 +19,8 @@ protected: static void SetUpTestSuite() { if (db == nullptr) { db = new LMDBAL::Base("testBase"); - db->addTable("table1"); - db->addTable("table2"); + db->addStorage("table1"); + db->addStorage("table2"); db->addCache("cache1"); } } @@ -152,8 +152,8 @@ TEST_F(BaseTest, Persistence) { delete db; db = new LMDBAL::Base("testBase"); - t1 = db->addTable("table1"); - t2 = db->addTable("table2"); + t1 = db->addStorage("table1"); + t2 = db->addStorage("table2"); c1 = db->addCache("cache1"); db->open(); From f39d44890acddca1dccf83a8b1d3ebc402003db4 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 26 Mar 2023 22:12:23 +0300 Subject: [PATCH 027/125] some fixes in README --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 073be29..b81524c 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,30 @@ If you're using LMDBAL as a system library you probably have no control over it' ``` find_package(lmdbal) if (LMDBAL_FOUND) - target_include_directories(yourTarget PRIVATE {LMDBAL_INCLUDE_DIRS}) + target_include_directories(yourTarget PRIVATE ${LMDBAL_INCLUDE_DIRS}) target_link_libraries(yourTarget PRIVATE LMDBAL::LMDBAL) endif() ``` #### As an embeded subproject -//TODO + +If you're using LMDBAL as a embeded library you might want to control it's build options, for example you can run +``` +set(BUILD_STATIC ON) +``` + +before including the library in your project. This will set the library to be build in a static mode. + +Then you want to run something like this +``` +add_subdirectory(pathTo/yourEmbedded/libraries/lmdbal) +add_library(LMDBAL::LMDBAL ALIAS LMDBAL) +... + +target_link_libraries(yourTarget PRIVATE LMDBAL::LMDBAL) +``` + +The headers are added as `PUBLIC` so you might not even need to `target_link_libraries` them ### Building From e2dbea21d10e7c4ed38406b2b3b0acc9f0bf8f39 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 28 Mar 2023 23:45:35 +0300 Subject: [PATCH 028/125] external transaction methods for storage --- packaging/Archlinux/PKGBUILD | 2 +- src/serializer.h | 1 + src/serializer.hpp | 11 +- src/serializer_double.hpp | 5 +- src/serializer_float.hpp | 5 +- src/serializer_int16.hpp | 5 +- src/serializer_int32.hpp | 5 +- src/serializer_int64.hpp | 5 +- src/serializer_int8.hpp | 5 +- src/serializer_qbytearray.hpp | 5 +- src/serializer_qstring.hpp | 4 + src/serializer_stdstring.hpp | 5 +- src/serializer_uint16.hpp | 5 +- src/serializer_uint32.hpp | 5 +- src/serializer_uint64.hpp | 5 +- src/serializer_uint8.hpp | 5 +- src/storage.cpp | 12 +- src/storage.h | 15 +++ src/storage.hpp | 226 ++++++++++++++++++++++++++++------ 19 files changed, 273 insertions(+), 58 deletions(-) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 7dc7ed7..c8451c9 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -10,7 +10,7 @@ depends=( 'lmdb' 'qt5-base') makedepends=('cmake>=3.16') optdepends=() -source=("$pkgname-$pkgver.tar.gz") +source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") sha256sums=('SKIP') build() { cd "$srcdir/$pkgname" diff --git a/src/serializer.h b/src/serializer.h index 84a368b..734ac51 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -37,6 +37,7 @@ public: ~Serializer(); T deserialize(const MDB_val& value); + void deserialize(const MDB_val& value, T& result); MDB_val setData(const T& value); MDB_val getData(); void clear(); diff --git a/src/serializer.hpp b/src/serializer.hpp index c0c5172..36bb7d5 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -54,14 +54,19 @@ MDB_val LMDBAL::Serializer::setData(const T& value) { template T LMDBAL::Serializer::deserialize(const MDB_val& value) { - clear(); - bytes.setRawData((char*)value.mv_data, value.mv_size); T result; - stream >> result; + deserialize(value, result); return result; } +template +void LMDBAL::Serializer::deserialize(const MDB_val& value, T& result) { + clear(); + bytes.setRawData((char*)value.mv_data, value.mv_size); + stream >> result; +} + template void LMDBAL::Serializer::_setData(const T& value) { stream << value; diff --git a/src/serializer_double.hpp b/src/serializer_double.hpp index 8e9dc5d..4582459 100644 --- a/src/serializer_double.hpp +++ b/src/serializer_double.hpp @@ -29,9 +29,12 @@ public: ~Serializer() {}; double deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 8); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, double& result) { + std::memcpy(&result, data.mv_data, 8); + } MDB_val setData(const double& data) { value = data; return getData(); diff --git a/src/serializer_float.hpp b/src/serializer_float.hpp index d385150..be2c72d 100644 --- a/src/serializer_float.hpp +++ b/src/serializer_float.hpp @@ -29,9 +29,12 @@ public: ~Serializer() {}; float deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 4); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, float& result) { + std::memcpy(&result, data.mv_data, 4); + } MDB_val setData(const float& data) { value = data; return getData(); diff --git a/src/serializer_int16.hpp b/src/serializer_int16.hpp index 05d0091..17e5ea2 100644 --- a/src/serializer_int16.hpp +++ b/src/serializer_int16.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; int16_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 2); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, int16_t& result) { + std::memcpy(&result, data.mv_data, 2); + } MDB_val setData(const int16_t& data) { value = data; return getData(); diff --git a/src/serializer_int32.hpp b/src/serializer_int32.hpp index be6c6a9..78405c5 100644 --- a/src/serializer_int32.hpp +++ b/src/serializer_int32.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; int32_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 4); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, int32_t& result) { + std::memcpy(&result, data.mv_data, 4); + } MDB_val setData(const int32_t& data) { value = data; return getData(); diff --git a/src/serializer_int64.hpp b/src/serializer_int64.hpp index 1729557..4992ae1 100644 --- a/src/serializer_int64.hpp +++ b/src/serializer_int64.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; int64_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 8); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, int64_t& result) { + std::memcpy(&result, data.mv_data, 8); + } MDB_val setData(const int64_t& data) { value = data; return getData(); diff --git a/src/serializer_int8.hpp b/src/serializer_int8.hpp index 37c1588..eafd5c6 100644 --- a/src/serializer_int8.hpp +++ b/src/serializer_int8.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; int8_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 1); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, int8_t& result) { + std::memcpy(&result, data.mv_data, 1); + } MDB_val setData(const int8_t& data) { value = data; return getData(); diff --git a/src/serializer_qbytearray.hpp b/src/serializer_qbytearray.hpp index 43713ec..6f1c5d1 100644 --- a/src/serializer_qbytearray.hpp +++ b/src/serializer_qbytearray.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; QByteArray deserialize(const MDB_val& data) { - value.setRawData((char*)data.mv_data, data.mv_size); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, QByteArray& result) { + result.setRawData((char*)data.mv_data, data.mv_size); + } MDB_val setData(const QByteArray& data) { value = data; return getData(); diff --git a/src/serializer_qstring.hpp b/src/serializer_qstring.hpp index af716a7..1a36fcb 100644 --- a/src/serializer_qstring.hpp +++ b/src/serializer_qstring.hpp @@ -35,6 +35,10 @@ public: value = QByteArray((char*)data.mv_data, data.mv_size); return QString::fromUtf8(value); }; + void deserialize(const MDB_val& data, QString& result) { + value = QByteArray((char*)data.mv_data, data.mv_size); + result = QString::fromUtf8(value); + } MDB_val setData(const QString& data) { value = data.toUtf8(); return getData(); diff --git a/src/serializer_stdstring.hpp b/src/serializer_stdstring.hpp index 64a8970..416cfd6 100644 --- a/src/serializer_stdstring.hpp +++ b/src/serializer_stdstring.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; std::string deserialize(const MDB_val& data) { - value = std::string((char*)data.mv_data, data.mv_size); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, std::string& result) { + result.assign((char*)data.mv_data, data.mv_size); + } MDB_val setData(const std::string& data) { value = data; return getData(); diff --git a/src/serializer_uint16.hpp b/src/serializer_uint16.hpp index b78d948..d648695 100644 --- a/src/serializer_uint16.hpp +++ b/src/serializer_uint16.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; uint16_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 2); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, uint16_t& result) { + std::memcpy(&result, data.mv_data, 2); + } MDB_val setData(const uint16_t& data) { value = data; return getData(); diff --git a/src/serializer_uint32.hpp b/src/serializer_uint32.hpp index f41a845..211f151 100644 --- a/src/serializer_uint32.hpp +++ b/src/serializer_uint32.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; uint32_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 4); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, uint32_t& result) { + std::memcpy(&result, data.mv_data, 4); + } MDB_val setData(const uint32_t& data) { value = data; return getData(); diff --git a/src/serializer_uint64.hpp b/src/serializer_uint64.hpp index f59b91e..250ec03 100644 --- a/src/serializer_uint64.hpp +++ b/src/serializer_uint64.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; uint64_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 8); + deserialize(data, value); return value; }; + void deserialize(const MDB_val& data, uint64_t& result) { + std::memcpy(&result, data.mv_data, 8); + } MDB_val setData(const uint64_t& data) { value = data; return getData(); diff --git a/src/serializer_uint8.hpp b/src/serializer_uint8.hpp index ba19ee3..8f4cd04 100644 --- a/src/serializer_uint8.hpp +++ b/src/serializer_uint8.hpp @@ -31,9 +31,12 @@ public: ~Serializer() {}; uint8_t deserialize(const MDB_val& data) { - std::memcpy(&value, data.mv_data, 1); + deserialzie(data, value); return value; }; + void deserialzie(const MDB_val& data, uint8_t& result) { + std::memcpy(&result, data.mv_data, 1); + } MDB_val setData(const uint8_t& data) { value = data; return getData(); diff --git a/src/storage.cpp b/src/storage.cpp index fd4ee4d..1823f44 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -82,14 +82,22 @@ uint32_t LMDBAL::iStorage::count() const { void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); + throwDuplicateOrUnknown(rc, key); +} + +void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { + abortTransaction(txn); + throwNotFoundOrUnknown(rc, key); +} + +void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) const { if (rc == MDB_KEYEXIST) throwDuplicate(key); else throwUnknown(rc); } -void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { - abortTransaction(txn); +void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) const { if (rc == MDB_NOTFOUND) throwNotFound(key); else diff --git a/src/storage.h b/src/storage.h index 0d53fc7..ba7d022 100644 --- a/src/storage.h +++ b/src/storage.h @@ -37,7 +37,9 @@ protected: const std::string& dbName() const; void ensureOpened(const std::string& methodName) const; + void throwDuplicateOrUnknown(int rc, const std::string& key) const; void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, const std::string& key) const; void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; void throwUnknown(int rc, TransactionID txn) const; void throwUnknown(int rc) const; @@ -89,14 +91,27 @@ protected: public: using iStorage::drop; virtual void addRecord(const K& key, const V& value); + virtual void addRecord(const K& key, const V& value, TransactionID txn); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change + virtual bool forceRecord(const K& key, const V& value, TransactionID txn); virtual void changeRecord(const K& key, const V& value); + virtual void changeRecord(const K& key, const V& value, TransactionID txn); virtual void removeRecord(const K& key); + virtual void removeRecord(const K& key, TransactionID txn); virtual bool checkRecord(const K& key) const; //checks if there is a record with given key + virtual bool checkRecord(const K& key, TransactionID txn) const; + virtual void getRecord(const K& key, V& value) const; + virtual void getRecord(const K& key, V& value, TransactionID txn) const; virtual V getRecord(const K& key) const; + virtual V getRecord(const K& key, TransactionID txn) const; virtual std::map readAll() const; + virtual std::map readAll(TransactionID txn) const; + virtual void readAll(std::map& result) const; + virtual void readAll(std::map& result, TransactionID txn) const; virtual void replaceAll(const std::map& data); + virtual void replaceAll(const std::map& data, TransactionID txn); virtual uint32_t addRecords(const std::map& data, bool overwrite = false); + virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); protected: Serializer* keySerializer; diff --git a/src/storage.hpp b/src/storage.hpp index 816dd2f..8e7da60 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -38,24 +38,51 @@ LMDBAL::Storage::~Storage() { template void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); - TransactionID txn = beginTransaction(); + try { + addRecord(key, value, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); +} + +template +void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { + ensureOpened(addRecordMethodName); + MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData = valueSerializer->setData(value); int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) - throwDuplicateOrUnknown(rc, txn, toString(key)); - - commitTransaction(txn); + throwDuplicateOrUnknown(rc, toString(key)); } template bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { ensureOpened(forceRecordMethodName); - bool added; TransactionID txn = beginTransaction(); + bool added; + try { + added = forceRecord(key, value, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); + return added; +} + +template +bool LMDBAL::Storage::forceRecord(const K& key, const V& value, TransactionID txn) { + ensureOpened(forceRecordMethodName); + + bool added; MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; @@ -69,15 +96,13 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { break; default: added = false; - throwUnknown(rc, txn); + throwUnknown(rc); } lmdbData = valueSerializer->setData(value); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); - - commitTransaction(txn); + throwUnknown(rc); return added; } @@ -87,32 +112,73 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { ensureOpened(changeRecordMethodName); TransactionID txn = beginTransaction(); + try { + changeRecord(key, value, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); +} + +template +void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { + ensureOpened(changeRecordMethodName); + MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData = valueSerializer->setData(value); int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); - - commitTransaction(txn); + throwUnknown(rc); } template V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); + V value; + getRecord(key, value); + return value; +} + +template +void LMDBAL::Storage::getRecord(const K& key, V& value) const { + ensureOpened(getRecordMethodName); + TransactionID txn = beginReadOnlyTransaction(); + try { + getRecord(key, value, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + abortTransaction(txn); +} + +template +V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { + ensureOpened(getRecordMethodName); + + V value; + getRecord(key, value, txn); + return value; +} + +template +void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { + ensureOpened(getRecordMethodName); + MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc != MDB_SUCCESS) - throwNotFoundOrUnknown(rc, txn, toString(key)); + throwNotFoundOrUnknown(rc, toString(key)); - V value = valueSerializer->deserialize(lmdbData); - abortTransaction(txn); - - return value; + valueSerializer->deserialize(lmdbData, value); } template @@ -120,12 +186,26 @@ bool LMDBAL::Storage::checkRecord(const K& key) const { ensureOpened(checkRecordMethodName); TransactionID txn = beginReadOnlyTransaction(); + bool result; + try { + result = checkRecord(key, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + abortTransaction(txn); + return result; +} + +template +bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { + ensureOpened(checkRecordMethodName); + MDB_val lmdbKey = keySerializer->setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); - abortTransaction(txn); - if (rc == MDB_SUCCESS) return true; @@ -139,28 +219,57 @@ template std::map LMDBAL::Storage::readAll() const { ensureOpened(readAllMethodName); - TransactionID txn = beginReadOnlyTransaction(); std::map result; + readAll(result); + return result; +} + +template +void LMDBAL::Storage::readAll(std::map& result) const { + ensureOpened(readAllMethodName); + + TransactionID txn = beginReadOnlyTransaction(); + try { + readAll(result, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + abortTransaction(txn); +} + +template +std::map LMDBAL::Storage::readAll(TransactionID txn) const { + ensureOpened(readAllMethodName); + + std::map result; + readAll(result, txn); + return result; +} + +template +void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) const { + ensureOpened(readAllMethodName); + MDB_cursor* cursor; MDB_val lmdbKey, lmdbData; int rc = mdb_cursor_open(txn, dbi, &cursor); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); + throwUnknown(rc); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); while (rc == MDB_SUCCESS) { - K key = keySerializer->deserialize(lmdbKey); - V value = valueSerializer->deserialize(lmdbData); - result.insert(std::make_pair(key, value)); + K key; + keySerializer->deserialize(lmdbKey, key); + V& value = result[key]; + valueSerializer->deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } - abortTransaction(txn); if (rc != MDB_NOTFOUND) throwUnknown(rc); - - return result; } template @@ -168,9 +277,23 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { ensureOpened(replaceAllMethodName); TransactionID txn = beginTransaction(); + try { + replaceAll(data, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); +} + +template +void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID txn) { + ensureOpened(replaceAllMethodName); + int rc = drop(txn); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); + throwUnknown(rc); MDB_val lmdbKey, lmdbData; for (const std::pair& pair : data) { @@ -179,9 +302,8 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); + throwUnknown(rc); } - commitTransaction(txn); } template @@ -189,27 +311,39 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool over ensureOpened(addRecordsMethodName); TransactionID txn = beginTransaction(); + uint32_t amount; + try { + amount = addRecords(data, txn, overwrite); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); + return amount; +} + +template +uint32_t LMDBAL::Storage::addRecords(const std::map& data, TransactionID txn, bool overwrite) { + ensureOpened(addRecordsMethodName); + MDB_val lmdbKey, lmdbData; int rc; - for (const std::pair& pair : data) { lmdbKey = keySerializer->setData(pair.first); lmdbData = valueSerializer->setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); + throwUnknown(rc); } MDB_stat stat; rc = mdb_stat(txn, dbi, &stat); if (rc != MDB_SUCCESS) - throwUnknown(rc, txn); + throwUnknown(rc); - uint32_t amount = stat.ms_entries; - commitTransaction(txn); - - return amount; + return stat.ms_entries; } template @@ -217,12 +351,24 @@ void LMDBAL::Storage::removeRecord(const K& key) { ensureOpened(removeRecordMethodName); TransactionID txn = beginTransaction(); + try { + removeRecord(key, txn); + } catch (...) { + abortTransaction(txn); + throw; + } + + commitTransaction(txn); +} + +template +void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { + ensureOpened(removeRecordMethodName); + MDB_val lmdbKey = keySerializer->setData(key); int rc = mdb_del(txn, dbi, &lmdbKey, NULL); if (rc != MDB_SUCCESS) - throwNotFoundOrUnknown(rc, txn, toString(key)); - - commitTransaction(txn); + throwNotFoundOrUnknown(rc, toString(key)); } template From 17fb37075c780b7029f83e5faba079c641a6f75d Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 30 Mar 2023 20:00:56 +0300 Subject: [PATCH 029/125] unit tests for serialization --- src/operators.hpp | 30 ++-- src/serializer.hpp | 2 +- src/serializer_uint8.hpp | 4 +- test/CMakeLists.txt | 1 + test/serialization.cpp | 333 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 352 insertions(+), 18 deletions(-) create mode 100644 test/serialization.cpp diff --git a/src/operators.hpp b/src/operators.hpp index 795496a..8adea66 100644 --- a/src/operators.hpp +++ b/src/operators.hpp @@ -75,21 +75,21 @@ QDataStream& operator >> (QDataStream &in, std::multimap& container) { return in; } -template -QDataStream& operator << (QDataStream &out, const std::pair& pair) { - out << pair.first; - out << pair.second; - - return out; -} - -template -QDataStream& operator >> (QDataStream &in, std::pair& container) { - in >> container.first; - in >> container.second; - - return in; -} +// template +// QDataStream& operator << (QDataStream &out, const std::pair& pair) { +// out << pair.first; +// out << pair.second; +// +// return out; +// } +// +// template +// QDataStream& operator >> (QDataStream &in, std::pair& container) { +// in >> container.first; +// in >> container.second; +// +// return in; +// } template QDataStream& operator << (QDataStream &out, const std::set& container) { diff --git a/src/serializer.hpp b/src/serializer.hpp index 36bb7d5..bbece55 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -37,7 +37,7 @@ LMDBAL::Serializer::Serializer(const T& value) : stream(&buffer) { buffer.open(QIODevice::ReadWrite); - _setValue(value); + _setData(value); } template diff --git a/src/serializer_uint8.hpp b/src/serializer_uint8.hpp index 8f4cd04..31daca6 100644 --- a/src/serializer_uint8.hpp +++ b/src/serializer_uint8.hpp @@ -31,10 +31,10 @@ public: ~Serializer() {}; uint8_t deserialize(const MDB_val& data) { - deserialzie(data, value); + deserialize(data, value); return value; }; - void deserialzie(const MDB_val& data, uint8_t& result) { + void deserialize(const MDB_val& data, uint8_t& result) { std::memcpy(&result, data.mv_data, 1); } MDB_val setData(const uint8_t& data) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5f1a0e0..d01a810 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${GTEST_INCLUDE_DIR}) add_executable(runUnitTests basic.cpp + serialization.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/serialization.cpp b/test/serialization.cpp new file mode 100644 index 0000000..1d7d0b3 --- /dev/null +++ b/test/serialization.cpp @@ -0,0 +1,333 @@ +#include + +#include +#include + +TEST(Serialization, Double) { + double source = 5344.6542; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + double destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + double dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, Float) { + float source = 5.156; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + float destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + float dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, Int8) { + int8_t source = 38; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + int8_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + int8_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, Int16) { + int16_t source = -3469; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + int16_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + int16_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, Int32) { + int32_t source = 454832; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + int32_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + int32_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, Int64) { + int64_t source = -875525478136; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + int64_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + int64_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, UInt8) { + uint8_t source = 196; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + uint8_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + uint8_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, UInt16) { + uint16_t source = 8634; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + uint16_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + uint16_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, UInt32) { + uint32_t source = 115469; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + uint32_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + uint32_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, UInt64) { + uint64_t source = 498763546873; + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + uint64_t destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + uint64_t dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, StdString) { + std::string source("days just go by, some good and some are bad"); + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + std::string destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + std::string dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, QString) { + QString source("may be will find nothing new, may be I'll end up just just like you"); + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + QString destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + QString dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, QByteArray) { + QByteArray source = QByteArray::fromHex("84 be 81 6c d3 5e c3 49 94 51 6f 8f a7 3b 0c d8 29 23 a3 21 4d b8 3f 73"); + LMDBAL::Serializer serializer; + LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + QByteArray destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + QByteArray dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} + +TEST(Serialization, StdMap) { + std::map source({ + {467, 123}, + {1397, 8}, + {551, -9}, + {864, 114}, + {9615, -85}, + {32, 32} + }); + LMDBAL::Serializer> serializer; + LMDBAL::Serializer> serializer2(source); + LMDBAL::Serializer> deserializer; + + serializer.setData(source); + MDB_val data = serializer.getData(); + MDB_val data2 = serializer2.getData(); + + EXPECT_EQ(data.mv_size, data2.mv_size); + EXPECT_EQ(std::memcmp(data.mv_data, data2.mv_data, data.mv_size), 0); + + std::map destination; + serializer.deserialize(data, destination); + + EXPECT_EQ(source, destination); + + std::map dest2 = serializer.deserialize(data); + + EXPECT_EQ(source, dest2); +} From a4bb7e6269df1906a5ed0818fa25246ec39ecbe5 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 1 Apr 2023 17:45:20 +0300 Subject: [PATCH 030/125] a method to get the size of the storage with opened transaction, tests for readAll, and some test for transactions --- src/storage.cpp | 28 +++++---- src/storage.h | 7 ++- test/CMakeLists.txt | 1 + test/basic.cpp | 35 ++++++++++-- test/serialization.cpp | 8 +-- test/storagetransaction.cpp | 111 ++++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 test/storagetransaction.cpp diff --git a/src/storage.cpp b/src/storage.cpp index 1823f44..b28afc3 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -59,25 +59,31 @@ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { throw Closed(methodName, db->name, name); } -uint32_t LMDBAL::iStorage::count() const { +LMDBAL::SizeType LMDBAL::iStorage::count() const { ensureOpened(countMethodName); - MDB_txn *txn; + TransactionID txn = beginReadOnlyTransaction(); + SizeType amount; + try { + amount = count(txn); + } catch (...) { + abortTransaction(txn); + throw; + } + abortTransaction(txn); + + return amount; +} + +LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { MDB_stat stat; - int rc = mdb_txn_begin(db->environment, NULL, MDB_RDONLY, &txn); + int rc = mdb_stat(txn, dbi, &stat); if (rc) { mdb_txn_abort(txn); throw Unknown(db->name, mdb_strerror(rc), name); } - rc = mdb_stat(txn, dbi, &stat); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - uint32_t amount = stat.ms_entries; - mdb_txn_abort(txn); - return amount; + return stat.ms_entries; } void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { diff --git a/src/storage.h b/src/storage.h index ba7d022..dc00d38 100644 --- a/src/storage.h +++ b/src/storage.h @@ -24,8 +24,12 @@ namespace LMDBAL { +typedef uint32_t SizeType; + class iStorage { friend class Base; +public: + protected: iStorage(const std::string& name, Base* parent); virtual ~iStorage(); @@ -48,7 +52,8 @@ protected: public: virtual void drop(); - virtual uint32_t count() const; + virtual SizeType count() const; + virtual SizeType count(TransactionID txn) const; TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d01a810..e961886 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,6 +5,7 @@ include_directories(${GTEST_INCLUDE_DIR}) add_executable(runUnitTests basic.cpp serialization.cpp + storagetransaction.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/basic.cpp b/test/basic.cpp index 509f896..fdef128 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -256,12 +256,12 @@ TEST_F(BaseTest, Change) { TEST_F(BaseTest, Force) { EXPECT_EQ(db->ready(), true); - t1->forceRecord(58, 35); //changing - t1->forceRecord(68, 36); //adding - t2->forceRecord("prophecy", "dumpling"); //adding - t2->forceRecord("lawfirm", "paracetamol"); //changing - c1->forceRecord(89, "canine"); //changing - c1->forceRecord(98, "duration"); //adding + EXPECT_EQ(t1->forceRecord(58, 35), false); //changing + EXPECT_EQ(t1->forceRecord(68, 36), true); //adding + EXPECT_EQ(t2->forceRecord("prophecy", "dumpling"), true); //adding + EXPECT_EQ(t2->forceRecord("lawfirm", "paracetamol"), false); //changing + EXPECT_EQ(c1->forceRecord(89, "canine"), false); //changing + EXPECT_EQ(c1->forceRecord(98, "duration"), true); //adding EXPECT_EQ(t1->getRecord(2), 49); EXPECT_EQ(t1->getRecord(58), 35); @@ -278,3 +278,26 @@ TEST_F(BaseTest, Force) { EXPECT_EQ(t2->count(), 3); EXPECT_EQ(c1->count(), 4); } + +TEST_F(BaseTest, ReadAll) { + EXPECT_EQ(db->ready(), true); + + std::map m1 = t1->readAll(); + std::map m2 = t2->readAll(); + std::map m3 = c1->readAll(); + + EXPECT_EQ(m1.at(2), 49); + EXPECT_EQ(m1.at(58), 35); + EXPECT_EQ(m1.at(68), 36); + EXPECT_EQ(m2.at("sdfhga"), "void"); + EXPECT_EQ(m2.at("prophecy"), "dumpling"); + EXPECT_EQ(m2.at("lawfirm"), "paracetamol"); + EXPECT_EQ(m3.at(15), "recording"); + EXPECT_EQ(m3.at(12), "thermal"); + EXPECT_EQ(m3.at(89), "canine"); + EXPECT_EQ(m3.at(98), "duration"); + + EXPECT_EQ(m1.size(), 3); + EXPECT_EQ(m2.size(), 3); + EXPECT_EQ(m3.size(), 4); +} diff --git a/test/serialization.cpp b/test/serialization.cpp index 1d7d0b3..b413d42 100644 --- a/test/serialization.cpp +++ b/test/serialization.cpp @@ -19,11 +19,11 @@ TEST(Serialization, Double) { double destination; serializer.deserialize(data, destination); - EXPECT_EQ(source, destination); + EXPECT_DOUBLE_EQ(source, destination); double dest2 = serializer.deserialize(data); - EXPECT_EQ(source, dest2); + EXPECT_DOUBLE_EQ(source, dest2); } TEST(Serialization, Float) { @@ -42,11 +42,11 @@ TEST(Serialization, Float) { float destination; serializer.deserialize(data, destination); - EXPECT_EQ(source, destination); + EXPECT_FLOAT_EQ(source, destination); float dest2 = serializer.deserialize(data); - EXPECT_EQ(source, dest2); + EXPECT_FLOAT_EQ(source, dest2); } TEST(Serialization, Int8) { diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp new file mode 100644 index 0000000..294c1d2 --- /dev/null +++ b/test/storagetransaction.cpp @@ -0,0 +1,111 @@ +#include + +#include "base.h" +#include "storage.h" + +class StorageTransactionsTest : public testing::Test { +protected: + StorageTransactionsTest(): + testing::Test(), + t1(db->getStorage("table1")), + t2(db->getStorage("table2")) {} + + ~StorageTransactionsTest() {} + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("storageTrnansactionsTestBase"); + db->addStorage("table1"); + db->addStorage("table2"); + } + + db->open(); + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static LMDBAL::Base* db; + + LMDBAL::Storage* t1; + LMDBAL::Storage* t2; +}; + + +LMDBAL::Base* StorageTransactionsTest::db = nullptr; + +TEST_F(StorageTransactionsTest, Adding) { + EXPECT_EQ(db->ready(), true); + EXPECT_EQ(t1->count(), 0); + EXPECT_EQ(t2->count(), 0); + + LMDBAL::TransactionID txn = db->beginTransaction(); + t1->addRecord(5, 13, txn); + t1->addRecord(-53, 782, txn); + t1->addRecord(58392, -37829, txn); + + t2->addRecord("lorem", 481, txn); + t2->addRecord("decallence", 8532.48, txn); + t2->addRecord("prevent recovery", -64.64, txn); + + EXPECT_EQ(t1->count(), 0); + EXPECT_EQ(t2->count(), 0); + + db->commitTransaction(txn); + + EXPECT_EQ(t1->count(), 3); + EXPECT_EQ(t1->getRecord(5), 13); + EXPECT_EQ(t1->getRecord(-53), 782); + EXPECT_EQ(t1->getRecord(58392), -37829); + + EXPECT_EQ(t2->count(), 3); + EXPECT_FLOAT_EQ(t2->getRecord("lorem"), 481); + EXPECT_FLOAT_EQ(t2->getRecord("decallence"), 8532.48); + EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery"), -64.64); +} + +TEST_F(StorageTransactionsTest, Aborting) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType s1 = t1->count(); + LMDBAL::SizeType s2 = t2->count(); + + LMDBAL::TransactionID txn = t1->beginTransaction(); + t1->addRecord(18, 40, txn); + t1->addRecord(85, -4, txn); + t1->addRecord(-5, -3, txn); + + t2->addRecord("tapestry", .053, txn); + t2->addRecord("pepper plants are beautifull", -7, txn); + t2->addRecord("horrots", -23.976, txn); + + EXPECT_EQ(t1->count(), s1); + EXPECT_EQ(t2->count(), s2); + + t1->abortTransaction(txn); + + EXPECT_EQ(t1->count(), s1); + EXPECT_EQ(t2->count(), s2); +} + +TEST_F(StorageTransactionsTest, Reading) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + + EXPECT_EQ(t1->count(txn), 3); + EXPECT_EQ(t1->getRecord(5, txn), 13); + EXPECT_EQ(t1->getRecord(-53, txn), 782); + EXPECT_EQ(t1->getRecord(58392, txn), -37829); + + EXPECT_EQ(t2->count(txn), 3); + EXPECT_FLOAT_EQ(t2->getRecord("lorem", txn), 481); + EXPECT_FLOAT_EQ(t2->getRecord("decallence", txn), 8532.48); + EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery", txn), -64.64); + + db->abortTransaction(txn); +} From f0779ae2aa6be26405967ef1cc6d029af4d61a66 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 2 Apr 2023 16:00:21 +0300 Subject: [PATCH 031/125] some ideas about transaction --- CMakeLists.txt | 11 ++++ src/base.cpp | 101 ++++++++++++++++++++++-------------- src/base.h | 23 ++++---- src/storage.cpp | 24 +++++++-- src/storage.h | 19 ++++--- src/storage.hpp | 2 +- test/storagetransaction.cpp | 10 ++-- 7 files changed, 125 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d301fa2..fbbeb34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,17 @@ else () add_library(${PROJECT_NAME} SHARED) endif() +if (CMAKE_BUILD_TYPE STREQUAL "Release") + list(APPEND COMPILE_OPTIONS -O3) +elseif (CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND COMPILE_OPTIONS -g) + list(APPEND COMPILE_OPTIONS -Wall) + list(APPEND COMPILE_OPTIONS -Wextra) +endif() + +message("Compilation options: " ${COMPILE_OPTIONS}) +target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) + set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version}) set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1) set_property(TARGET ${PROJECT_NAME} PROPERTY diff --git a/src/base.cpp b/src/base.cpp index 15e833b..48f7dcf 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -20,12 +20,14 @@ #include "exceptions.h" #include "storage.h" +#define UNUSED(x) (void)(x) + LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), size(mapSize), environment(), - tables(), + storages(), transactions(new Transactions()) {} @@ -34,18 +36,18 @@ LMDBAL::Base::~Base() { delete transactions; - for (const std::pair& pair : tables) + for (const std::pair& pair : storages) delete pair.second; } void LMDBAL::Base::close() { if (opened) { for (LMDBAL::TransactionID id : *transactions) - mdb_txn_abort(id); + abortTransaction(id, emptyName); - for (const std::pair& pair : tables) { - iStorage* table = pair.second; - mdb_dbi_close(environment, table->dbi); + for (const std::pair& pair : storages) { + iStorage* storage = pair.second; + mdb_dbi_close(environment, storage->dbi); } mdb_env_close(environment); transactions->clear(); @@ -61,20 +63,18 @@ void LMDBAL::Base::open() { mdb_env_create(&environment); QString path = createDirectory(); - mdb_env_set_maxdbs(environment, tables.size()); + mdb_env_set_maxdbs(environment, storages.size()); mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - - for (const std::pair& pair : tables) { - iStorage* table = pair.second; - int rc = table->createTable(txn); + TransactionID txn = beginPrivateTransaction(emptyName); + for (const std::pair& pair : storages) { + iStorage* storage = pair.second; + int rc = storage->createStorage(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); } - mdb_txn_commit(txn); + commitPrivateTransaction(txn, emptyName); opened = true; } } @@ -120,18 +120,13 @@ void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); - MDB_txn *txn; - int rc = mdb_txn_begin(environment, NULL, 0, &txn); - if (rc) - throw Unknown(name, mdb_strerror(rc)); - - for (const std::pair& pair : tables) { - rc = pair.second->drop(txn); + TransactionID txn = beginPrivateTransaction(emptyName); + for (const std::pair& pair : storages) { + int rc = pair.second->drop(txn); if (rc) throw Unknown(name, mdb_strerror(rc), pair.first); } - - mdb_txn_commit(txn); + commitPrivateTransaction(txn, emptyName); } LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { @@ -146,18 +141,15 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) const { return commitTransaction(id, emptyName);} - LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginReadOnlyTransaction", name, storageName); - MDB_txn* txn; - int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(name, mdb_strerror(rc), storageName); - } + TransactionID txn = beginPrivateReadOnlyTransaction(storageName); transactions->emplace(txn); + for (const std::pair& pair : storages) + pair.second->transactionStarted(txn, true); + return txn; } @@ -165,13 +157,11 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN if (!opened) throw Closed("beginTransaction", name, storageName); - MDB_txn* txn; - int rc = mdb_txn_begin(environment, NULL, 0, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(name, mdb_strerror(rc), storageName); - } + TransactionID txn = beginPrivateTransaction(storageName); transactions->emplace(txn); + for (const std::pair& pair : storages) + pair.second->transactionStarted(txn, false); + return txn; } @@ -183,7 +173,10 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to abort transaction: transaction was not found", storageName); - mdb_txn_abort(id); + abortPrivateTransaction(id, storageName); + for (const std::pair& pair : storages) + pair.second->transactionAborted(id); + transactions->erase(itr); } @@ -195,8 +188,40 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to commit transaction: transaction was not found", storageName); - int rc = mdb_txn_commit(id); + commitPrivateTransaction(id, storageName); + for (const std::pair& pair : storages) + pair.second->transactionCommited(id); + transactions->erase(itr); +} + +LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const { + MDB_txn* txn; + int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(name, mdb_strerror(rc), storageName); + } + return txn; +} + +LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& storageName) const { + MDB_txn* txn; + int rc = mdb_txn_begin(environment, NULL, 0, &txn); + if (rc) { + mdb_txn_abort(txn); + throw Unknown(name, mdb_strerror(rc), storageName); + } + return txn; +} + +void LMDBAL::Base::abortPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { + UNUSED(storageName); + mdb_txn_abort(id); +} + +void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { + int rc = mdb_txn_commit(id); if (rc != MDB_SUCCESS) throw Unknown(name, mdb_strerror(rc), storageName); } diff --git a/src/base.h b/src/base.h index 024eb98..c1110cb 100644 --- a/src/base.h +++ b/src/base.h @@ -81,7 +81,7 @@ public: LMDBAL::Cache* getCache(const std::string& name); private: - typedef std::map Tables; + typedef std::map Storages; typedef std::set Transactions; TransactionID beginReadOnlyTransaction(const std::string& storageName) const; @@ -89,12 +89,17 @@ private: void commitTransaction(TransactionID id, const std::string& storageName) const; void abortTransaction(TransactionID id, const std::string& storageName) const; + TransactionID beginPrivateReadOnlyTransaction(const std::string& storageName) const; + TransactionID beginPrivateTransaction(const std::string& storageName) const; + void commitPrivateTransaction(TransactionID id, const std::string& storageName) const; + void abortPrivateTransaction(TransactionID id, const std::string& storageName) const; + private: std::string name; bool opened; uint16_t size; MDB_env* environment; - Tables tables; + Storages storages; Transactions* transactions; inline static const std::string emptyName = ""; @@ -109,11 +114,11 @@ private: template LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { if (opened) { - throw Opened(name, "add table " + p_name); + throw Opened(name, "add storage " + p_name); } - Storage* table = new Storage(p_name, this); - tables.insert(std::make_pair(p_name, (iStorage*)table)); - return table; + Storage* storage = new Storage(p_name, this); + storages.insert(std::make_pair(p_name, (iStorage*)storage)); + return storage; } /** @@ -125,7 +130,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { throw Opened(name, "add cache " + p_name); } Cache* cache = new Cache(p_name, this); - tables.insert(std::make_pair(p_name, (iStorage*)cache)); + storages.insert(std::make_pair(p_name, (iStorage*)cache)); return cache; } @@ -134,7 +139,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { */ template LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { - return static_cast*>(tables.at(p_name)); + return static_cast*>(storages.at(p_name)); } /** @@ -142,7 +147,7 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { */ template LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& p_name) { - return static_cast*>(tables.at(p_name)); + return static_cast*>(storages.at(p_name)); } #endif //LMDBAL_BASE_H diff --git a/src/storage.cpp b/src/storage.cpp index b28afc3..8768654 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -18,6 +18,8 @@ #include "storage.h" +#define UNUSED(x) (void)(x) + LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): dbi(), db(parent), @@ -125,13 +127,27 @@ void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { - return db->beginReadOnlyTransaction(name);} + return db->beginPrivateReadOnlyTransaction(name);} LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { - return db->beginTransaction(name);} + return db->beginPrivateTransaction(name);} void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { - db->abortTransaction(id);} + db->abortPrivateTransaction(id, name);} void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) const { - db->commitTransaction(id);} + db->commitPrivateTransaction(id, name);} + +void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { + UNUSED(txn); + UNUSED(readOnly); +} +void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) const { + UNUSED(txn); +} +void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { + UNUSED(txn); +} + + + diff --git a/src/storage.h b/src/storage.h index dc00d38..3986c20 100644 --- a/src/storage.h +++ b/src/storage.h @@ -34,7 +34,7 @@ protected: iStorage(const std::string& name, Base* parent); virtual ~iStorage(); - virtual int createTable(MDB_txn * transaction) = 0; + virtual int createStorage(MDB_txn * transaction) = 0; virtual int drop(MDB_txn * transaction); bool isDBOpened() const; @@ -48,17 +48,20 @@ protected: void throwUnknown(int rc, TransactionID txn) const; void throwUnknown(int rc) const; void throwDuplicate(const std::string& key) const; - void throwNotFound(const std::string& key)const ; - -public: - virtual void drop(); - virtual SizeType count() const; - virtual SizeType count(TransactionID txn) const; + void throwNotFound(const std::string& key) const; TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; void commitTransaction(TransactionID id) const; void abortTransaction(TransactionID id) const; + void transactionStarted(TransactionID txn, bool readOnly) const; + void transactionCommited(TransactionID txn) const; + void transactionAborted(TransactionID txn) const; + +public: + virtual void drop(); + virtual SizeType count() const; + virtual SizeType count(TransactionID txn) const; protected: MDB_dbi dbi; @@ -122,7 +125,7 @@ protected: Serializer* keySerializer; Serializer* valueSerializer; - int createTable(MDB_txn* transaction) override; + int createStorage(MDB_txn* transaction) override; }; } diff --git a/src/storage.hpp b/src/storage.hpp index 8e7da60..52e4422 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -372,7 +372,7 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { } template -int LMDBAL::Storage::createTable(MDB_txn* transaction) { +int LMDBAL::Storage::createStorage(MDB_txn* transaction) { return makeTable(transaction); } diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 294c1d2..e756931 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -46,7 +46,7 @@ TEST_F(StorageTransactionsTest, Adding) { LMDBAL::TransactionID txn = db->beginTransaction(); t1->addRecord(5, 13, txn); t1->addRecord(-53, 782, txn); - t1->addRecord(58392, -37829, txn); + t1->addRecord(5892, -37829, txn); t2->addRecord("lorem", 481, txn); t2->addRecord("decallence", 8532.48, txn); @@ -60,7 +60,7 @@ TEST_F(StorageTransactionsTest, Adding) { EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t1->getRecord(5), 13); EXPECT_EQ(t1->getRecord(-53), 782); - EXPECT_EQ(t1->getRecord(58392), -37829); + EXPECT_EQ(t1->getRecord(5892), -37829); EXPECT_EQ(t2->count(), 3); EXPECT_FLOAT_EQ(t2->getRecord("lorem"), 481); @@ -74,7 +74,7 @@ TEST_F(StorageTransactionsTest, Aborting) { LMDBAL::SizeType s1 = t1->count(); LMDBAL::SizeType s2 = t2->count(); - LMDBAL::TransactionID txn = t1->beginTransaction(); + LMDBAL::TransactionID txn = db->beginTransaction(); t1->addRecord(18, 40, txn); t1->addRecord(85, -4, txn); t1->addRecord(-5, -3, txn); @@ -86,7 +86,7 @@ TEST_F(StorageTransactionsTest, Aborting) { EXPECT_EQ(t1->count(), s1); EXPECT_EQ(t2->count(), s2); - t1->abortTransaction(txn); + db->abortTransaction(txn); EXPECT_EQ(t1->count(), s1); EXPECT_EQ(t2->count(), s2); @@ -100,7 +100,7 @@ TEST_F(StorageTransactionsTest, Reading) { EXPECT_EQ(t1->count(txn), 3); EXPECT_EQ(t1->getRecord(5, txn), 13); EXPECT_EQ(t1->getRecord(-53, txn), 782); - EXPECT_EQ(t1->getRecord(58392, txn), -37829); + EXPECT_EQ(t1->getRecord(5892, txn), -37829); EXPECT_EQ(t2->count(txn), 3); EXPECT_FLOAT_EQ(t2->getRecord("lorem", txn), 481); From 8be63d755142989f3984e728d77bc8216b18c760 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 3 Apr 2023 21:48:13 +0300 Subject: [PATCH 032/125] some more transaction thought on cache --- src/base.cpp | 18 ++--- src/base.h | 6 +- src/cache.h | 41 +++++++++-- src/cache.hpp | 180 +++++++++++++++++++++++++++++++++++++++++++++--- src/storage.cpp | 44 +++++------- src/storage.h | 10 +-- 6 files changed, 241 insertions(+), 58 deletions(-) diff --git a/src/base.cpp b/src/base.cpp index 48f7dcf..0951bd6 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -120,13 +120,15 @@ void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); - TransactionID txn = beginPrivateTransaction(emptyName); + TransactionID txn = beginTransaction(); for (const std::pair& pair : storages) { int rc = pair.second->drop(txn); - if (rc) + if (rc != MDB_SUCCESS) { + abortTransaction(txn); throw Unknown(name, mdb_strerror(rc), pair.first); + } } - commitPrivateTransaction(txn, emptyName); + commitTransaction(txn); } LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { @@ -138,7 +140,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const { void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { return abortTransaction(id, emptyName);} -void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) const { +void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) { return commitTransaction(id, emptyName);} LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { @@ -180,7 +182,7 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& transactions->erase(itr); } -void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { +void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) { if (!opened) throw Closed("abortTransaction", name, storageName); @@ -198,7 +200,7 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - if (rc) { + if (rc != MDB_SUCCESS) { mdb_txn_abort(txn); throw Unknown(name, mdb_strerror(rc), storageName); } @@ -208,7 +210,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::s LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, 0, &txn); - if (rc) { + if (rc != MDB_SUCCESS) { mdb_txn_abort(txn); throw Unknown(name, mdb_strerror(rc), storageName); } @@ -220,7 +222,7 @@ void LMDBAL::Base::abortPrivateTransaction(LMDBAL::TransactionID id, const std:: mdb_txn_abort(id); } -void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { +void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) { int rc = mdb_txn_commit(id); if (rc != MDB_SUCCESS) throw Unknown(name, mdb_strerror(rc), storageName); diff --git a/src/base.h b/src/base.h index c1110cb..349aed4 100644 --- a/src/base.h +++ b/src/base.h @@ -65,7 +65,7 @@ public: TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; - void commitTransaction(TransactionID id) const; + void commitTransaction(TransactionID id); void abortTransaction(TransactionID id) const; template @@ -86,12 +86,12 @@ private: TransactionID beginReadOnlyTransaction(const std::string& storageName) const; TransactionID beginTransaction(const std::string& storageName) const; - void commitTransaction(TransactionID id, const std::string& storageName) const; + void commitTransaction(TransactionID id, const std::string& storageName); void abortTransaction(TransactionID id, const std::string& storageName) const; TransactionID beginPrivateReadOnlyTransaction(const std::string& storageName) const; TransactionID beginPrivateTransaction(const std::string& storageName) const; - void commitPrivateTransaction(TransactionID id, const std::string& storageName) const; + void commitPrivateTransaction(TransactionID id, const std::string& storageName); void abortPrivateTransaction(TransactionID id, const std::string& storageName) const; private: diff --git a/src/cache.h b/src/cache.h index 368c801..2a460b6 100644 --- a/src/cache.h +++ b/src/cache.h @@ -21,6 +21,7 @@ #include #include +#include #include "storage.h" @@ -34,33 +35,63 @@ class Cache : public Storage { size, // - know just an amount of records full // - shure that our cache is equal to the database on disk }; + + enum class Operation { + add, + remove, + change, + force, + drop, + replace, + addMany + }; + + typedef std::pair Entry; + typedef std::list Queue; + typedef std::map TransactionCache; + protected: Cache(const std::string& name, Base* parent); ~Cache() override; + virtual void transactionStarted(TransactionID txn, bool readOnly) const override; + virtual void transactionCommited(TransactionID txn) override; + virtual void transactionAborted(TransactionID txn) const override; private: void handleMode() const; + void handleTransactionEntry(const Entry& entry); + void destroyTransactionEntry(const Entry& entry) const; + + void handleAddRecord(const K& key, const V& value); + void handleRemoveRecord(const K& key); + void handleChangeRecord(const K& key, const V& value); + void handleForceRecord(const K& key, const V& value, bool added); + void handleReplaceAll(std::map* data); + void handleAddRecords(const std::map& data, bool overwrite, SizeType newSize); + void handleDrop(); + public: + using Storage::drop; + virtual int drop(TransactionID transaction) override; virtual void addRecord(const K& key, const V& value) override; virtual bool forceRecord(const K& key, const V& value) override; virtual void changeRecord(const K& key, const V& value) override; virtual void removeRecord(const K& key) override; virtual bool checkRecord(const K& key) const override; virtual V getRecord(const K& key) const override; - virtual uint32_t count() const override; + virtual SizeType count() const override; - using Storage::drop; - virtual int drop(MDB_txn * transaction) override; virtual std::map readAll() const override; virtual void replaceAll(const std::map& data) override; - virtual uint32_t addRecords(const std::map& data, bool overwrite = false) override; + virtual SizeType addRecords(const std::map& data, bool overwrite = false) override; protected: Mode* mode; std::map* cache; std::set* abscent; - uint32_t* sizeDifference; + SizeType* sizeDifference; + TransactionCache* transactionCache; }; } diff --git a/src/cache.hpp b/src/cache.hpp index 4838ee6..d92b0f6 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -28,7 +28,8 @@ LMDBAL::Cache::Cache(const std::string& p_name, Base* parent): mode(new Mode), cache(new std::map()), abscent(new std::set()), - sizeDifference(new uint32_t) + sizeDifference(new uint32_t), + transactionCache(new TransactionCache) { *mode = Mode::nothing; *sizeDifference = 0; @@ -36,6 +37,7 @@ LMDBAL::Cache::Cache(const std::string& p_name, Base* parent): template LMDBAL::Cache::~Cache() { + delete transactionCache; delete sizeDifference; delete mode; delete cache; @@ -50,6 +52,11 @@ void LMDBAL::Cache::addRecord(const K& key, const V& value) { iStorage::throwDuplicate(iStorage::toString(key)); Storage::addRecord(key, value); + handleAddRecord(key, value); +} + +template +void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { cache->insert(std::make_pair(key, value)); if (*mode != Mode::full) @@ -61,6 +68,13 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { iStorage::ensureOpened(iStorage::forceRecordMethodName); bool added = Storage::forceRecord(key, value); + handleForceRecord(key, value, added); + + return added; +} + +template +void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool added) { if (*mode == Mode::full) { (*cache)[key] = value; } else { @@ -73,8 +87,6 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { else if (!added) //this way database had value but cache didn't, so, need to decrease sizeDifference handleMode(); } - - return added; } template @@ -106,6 +118,19 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value) { } } +template +void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { + if (*mode == Mode::full) { + cache->at(key) = value; + } else { + typename std::pair::iterator, bool> res = cache->insert(std::make_pair(key, value)); + if (!res.second) + res.first->second = value; + else + handleMode(); + } +} + template V LMDBAL::Cache::getRecord(const K& key) const { iStorage::ensureOpened(iStorage::getRecordMethodName); @@ -181,9 +206,27 @@ void LMDBAL::Cache::replaceAll(const std::map& data) { } template -uint32_t LMDBAL::Cache::addRecords(const std::map& data, bool overwrite) { - uint32_t newSize = Storage::addRecords(data, overwrite); +void LMDBAL::Cache::handleReplaceAll(std::map* data) { + delete cache; + cache = data; + if (*mode != Mode::full) { + *mode = Mode::full; + abscent->clear(); + *sizeDifference = 0; + } +} + +template +LMDBAL::SizeType LMDBAL::Cache::addRecords(const std::map& data, bool overwrite) { + SizeType newSize = Storage::addRecords(data, overwrite); + handleAddRecords(data, overwrite, newSize); + + return newSize; +} + +template +void LMDBAL::Cache::handleAddRecords(const std::map& data, bool overwrite, SizeType newSize) { Mode& m = *mode; if (m == Mode::nothing) m = Mode::size; @@ -207,19 +250,27 @@ uint32_t LMDBAL::Cache::addRecords(const std::map& data, bool overwr abscent->clear(); } } - - return newSize; } template void LMDBAL::Cache::removeRecord(const K& key) { iStorage::ensureOpened(iStorage::removeRecordMethodName); - typename std::pair::const_iterator, bool> pair = abscent->insert(key); - if (!pair.second) + bool noKey = false; + if (*mode != Mode::full) + noKey = cache->count(key) == 0; + else + noKey = abscent->count(key) > 0; + + if (noKey) iStorage::throwNotFound(iStorage::toString(key)); Storage::removeRecord(key); + handleRemoveRecord(key); +} + +template +void LMDBAL::Cache::handleRemoveRecord(const K& key) { if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease handleMode(); @@ -263,13 +314,120 @@ void LMDBAL::Cache::handleMode() const { } template -int LMDBAL::Cache::drop(MDB_txn * transaction) { +int LMDBAL::Cache::drop(TransactionID transaction) { int res = Storage::drop(transaction); + + transactionCache->at(transaction).emplace_back(Operation::drop, nullptr); + + return res; +} + +template +void LMDBAL::Cache::handleDrop() { cache->clear(); abscent->clear(); *mode = Mode::full; *sizeDifference = 0; - return res; +} + +template +void LMDBAL::Cache::transactionStarted(TransactionID txn, bool readOnly) const { + if (!readOnly) + transactionCache->emplace(txn, Queue()); +} + +template +void LMDBAL::Cache::transactionCommited(TransactionID txn) { + typename TransactionCache::iterator itr = transactionCache->find(txn); + if (itr != transactionCache->end()) { + Queue& queue = itr->second; + for (const Entry& entry : queue) + handleTransactionEntry(entry); + + transactionCache->erase(itr); + } +} + +template +void LMDBAL::Cache::transactionAborted(TransactionID txn) const { + typename TransactionCache::iterator itr = transactionCache->find(txn); + if (itr != transactionCache->end()) { + Queue& queue = itr->second; + for (const Entry& entry : queue) + destroyTransactionEntry(entry); + + transactionCache->erase(itr); + } +} + +template +void LMDBAL::Cache::handleTransactionEntry(const Entry& entry) { + switch (entry.first) { + case Operation::add: { + std::pair* pair = static_cast*>(entry.second); + handleAddRecord(pair->first, pair->second); + delete pair; + } + break; + case Operation::remove: { + K* key = static_cast(entry.second); + handleRemoveRecord(*key); + delete key; + } + + break; + case Operation::change: { + std::pair* pair = static_cast*>(entry.second); + handleChangeRecord(pair->first, pair->second); + delete pair; + } + case Operation::force: { + std::tuple* tuple = static_cast*>(entry.second); + const std::tuple& t = *tuple; + handleForceRecord(std::get<1>(t), std::get<2>(t), std::get<0>(t)); + delete tuple; + } + break; + case Operation::drop: + handleDrop(); + break; + case Operation::replace: + handleReplaceAll(static_cast*>(entry.second)); //I take ownership, no need to delete + break; + case Operation::addMany: { + std::tuple>* tuple = static_cast>*>(entry.second); + const std::tuple>& t = * tuple; + handleAddRecords(std::get<2>(t), std::get<0>(t), std::get<1>(t)); + delete tuple; + } + break; + } +} + +template +void LMDBAL::Cache::destroyTransactionEntry(const Entry& entry) const { + switch (entry.first) { + case Operation::add: + delete static_cast*>(entry.second); + break; + case Operation::remove: + delete static_cast(entry.second); + break; + case Operation::change: + delete static_cast*>(entry.second); + break; + case Operation::force: + delete static_cast*>(entry.second); + break; + case Operation::drop: + break; + case Operation::replace: + delete static_cast*>(entry.second); + break; + case Operation::addMany: + delete static_cast>*>(entry.second); + break; + } } #endif //LMDBAL_CACHE_HPP diff --git a/src/storage.cpp b/src/storage.cpp index 8768654..5949bc7 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -31,31 +31,20 @@ LMDBAL::iStorage::~iStorage() {} void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); - MDB_txn *txn; - int rc = mdb_txn_begin(db->environment, NULL, 0, &txn); - if (rc) { - mdb_txn_abort(txn); - throw Unknown(db->name, mdb_strerror(rc), name); - } - rc = drop(txn); - if (rc) { - mdb_txn_abort(txn); + TransactionID txn = db->beginTransaction(); + int rc = drop(txn); + if (rc != MDB_SUCCESS) { + abortTransaction(txn); throw Unknown(db->name, mdb_strerror(rc), name); } - mdb_txn_commit(txn); + db->commitTransaction(txn); } -int LMDBAL::iStorage::drop(MDB_txn* transaction) { +int LMDBAL::iStorage::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } -const std::string & LMDBAL::iStorage::dbName() const { - return db->name;} - -bool LMDBAL::iStorage::isDBOpened() const { - return db->opened;} - void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { if (!db->opened) throw Closed(methodName, db->name, name); @@ -80,10 +69,8 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { MDB_stat stat; int rc = mdb_stat(txn, dbi, &stat); - if (rc) { - mdb_txn_abort(txn); + if (rc != MDB_SUCCESS) throw Unknown(db->name, mdb_strerror(rc), name); - } return stat.ms_entries; } @@ -117,6 +104,12 @@ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { throwUnknown(rc); } +const std::string & LMDBAL::iStorage::dbName() const { + return db->name;} + +bool LMDBAL::iStorage::isDBOpened() const { + return db->opened;} + void LMDBAL::iStorage::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} @@ -135,19 +128,18 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { db->abortPrivateTransaction(id, name);} -void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) const { +void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { db->commitPrivateTransaction(id, name);} void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { UNUSED(txn); UNUSED(readOnly); } -void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) const { - UNUSED(txn); -} +void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { + UNUSED(txn);} + void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { - UNUSED(txn); -} + UNUSED(txn);} diff --git a/src/storage.h b/src/storage.h index 3986c20..eb3a808 100644 --- a/src/storage.h +++ b/src/storage.h @@ -35,7 +35,6 @@ protected: virtual ~iStorage(); virtual int createStorage(MDB_txn * transaction) = 0; - virtual int drop(MDB_txn * transaction); bool isDBOpened() const; const std::string& dbName() const; @@ -52,14 +51,15 @@ protected: TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; - void commitTransaction(TransactionID id) const; + void commitTransaction(TransactionID id); void abortTransaction(TransactionID id) const; - void transactionStarted(TransactionID txn, bool readOnly) const; - void transactionCommited(TransactionID txn) const; - void transactionAborted(TransactionID txn) const; + virtual void transactionStarted(TransactionID txn, bool readOnly) const; + virtual void transactionCommited(TransactionID txn); + virtual void transactionAborted(TransactionID txn) const; public: virtual void drop(); + virtual int drop(TransactionID transaction); virtual SizeType count() const; virtual SizeType count(TransactionID txn) const; From 150a0b0da97a433242cc2f1d371468eee4749ef3 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 4 Apr 2023 19:14:25 +0300 Subject: [PATCH 033/125] better transaction tests --- test/storagetransaction.cpp | 118 ++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index e756931..eac7ac2 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -1,5 +1,7 @@ #include +#include + #include "base.h" #include "storage.h" @@ -12,6 +14,24 @@ protected: ~StorageTransactionsTest() {} + int waitForChildFork(int pid) { + int status; + if (0 > waitpid(pid, &status, 0)) { + std::cerr << "[----------] Waitpid error!" << std::endl; + return (-1); + } + if (WIFEXITED(status)) { + const int exit_status = WEXITSTATUS(status); + if (exit_status != 0) { + std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl; + } + return exit_status; + } else { + std::cerr << "[----------] Non-normal exit from child!" << std::endl; + return (-2); + } + } + static void SetUpTestSuite() { if (db == nullptr) { db = new LMDBAL::Base("storageTrnansactionsTestBase"); @@ -20,6 +40,7 @@ protected: } db->open(); + db->drop(); } static void TearDownTestSuite() { @@ -109,3 +130,100 @@ TEST_F(StorageTransactionsTest, Reading) { db->abortTransaction(txn); } + +TEST_F(StorageTransactionsTest, ConcurentReading) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType size = t1->count(); + LMDBAL::TransactionID txn = db->beginTransaction(); + EXPECT_EQ(t1->getRecord(5, txn), 13); + EXPECT_EQ(t1->getRecord(5), 13); + + t1->removeRecord(5, txn); + + EXPECT_FALSE(t1->checkRecord(5, txn)); + EXPECT_EQ(t1->getRecord(5), 13); + + t1->addRecord(5, 571, txn); + EXPECT_EQ(t1->getRecord(5, txn), 571); + EXPECT_EQ(t1->getRecord(5), 13); + + t1->forceRecord(5, -472, txn); + EXPECT_EQ(t1->getRecord(5, txn), -472); + EXPECT_EQ(t1->getRecord(5), 13); + + t1->replaceAll({ + {1, 75} + }, txn); + EXPECT_FALSE(t1->checkRecord(5, txn)); + EXPECT_EQ(t1->getRecord(5), 13); + EXPECT_EQ(t1->count(txn), 1); + EXPECT_EQ(t1->count(), size); + + db->commitTransaction(txn); + + EXPECT_FALSE(t1->checkRecord(5)); + EXPECT_EQ(t1->count(), 1); +} + +TEST_F(StorageTransactionsTest, ConcurentModification) { + EXPECT_EQ(db->ready(), true); + + //if you start one writable transaction after another + //in a single thread like so: + // + //LMDBAL::TransactionID txn1 = db->beginTransaction(); + //LMDBAL::TransactionID txn2 = db->beginTransaction(); + // + //the execution should block on the second transaction + //so this test should preform in a sequence + //first the parent, then the child + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining second transaction" << std::endl; + LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the first transaction value" << std::endl; + EXPECT_EQ(t1->getRecord(5, txn2), 812); + + std::cout << "forcing second transaction value" << std::endl; + t1->forceRecord(5, -46, txn2); + + std::cout << "checking second transaction value" << std::endl; + EXPECT_EQ(t1->getRecord(5, txn2), -46); + + std::cout << "checking value independently" << std::endl; + EXPECT_EQ(t1->getRecord(5), 812); + + std::cout << "commiting second transaction" << std::endl; + db->commitTransaction(txn2); + + std::cout << "quitting child thread" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining first transaction" << std::endl; + LMDBAL::TransactionID txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + + std::cout << "adding first transaction value" << std::endl; + t1->addRecord(5, 812, txn1); + + std::cout << "checking first transaction value" << std::endl; + EXPECT_EQ(t1->getRecord(5, txn1), 812); + + std::cout << "checking value independently" << std::endl; + EXPECT_FALSE(t1->checkRecord(5)); + + std::cout << "commiting first transaction" << std::endl; + db->commitTransaction(txn1); + + std::cout << "waiting for the other thread to finish" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking final result" << std::endl; + EXPECT_EQ(t1->getRecord(5), -46); +} From f99d5559cd54f2703ebfea3e191bdff8d2c61d4e Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 5 Apr 2023 02:27:31 +0300 Subject: [PATCH 034/125] some transaction methods for cache, some more tests for transactions --- src/cache.h | 7 + src/cache.hpp | 319 +++++++++++++++++++++++++++++++++++++- src/storage.cpp | 2 +- src/storage.hpp | 26 ++-- test/CMakeLists.txt | 1 + test/cachetransaction.cpp | 231 +++++++++++++++++++++++++++ 6 files changed, 571 insertions(+), 15 deletions(-) create mode 100644 test/cachetransaction.cpp diff --git a/src/cache.h b/src/cache.h index 2a460b6..39e7902 100644 --- a/src/cache.h +++ b/src/cache.h @@ -75,12 +75,19 @@ public: using Storage::drop; virtual int drop(TransactionID transaction) override; virtual void addRecord(const K& key, const V& value) override; + virtual void addRecord(const K& key, const V& value, TransactionID txn) override; virtual bool forceRecord(const K& key, const V& value) override; + virtual bool forceRecord(const K& key, const V& value, TransactionID txn) override; virtual void changeRecord(const K& key, const V& value) override; + virtual void changeRecord(const K& key, const V& value, TransactionID txn) override; virtual void removeRecord(const K& key) override; + virtual void removeRecord(const K& key, TransactionID txn) override; virtual bool checkRecord(const K& key) const override; + virtual bool checkRecord(const K& key, TransactionID txn) const override; virtual V getRecord(const K& key) const override; + virtual V getRecord(const K& key, TransactionID txn) const override; virtual SizeType count() const override; + virtual SizeType count(TransactionID txn) const override; virtual std::map readAll() const override; virtual void replaceAll(const std::map& data) override; diff --git a/src/cache.hpp b/src/cache.hpp index d92b0f6..5c0d9ca 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -55,6 +55,22 @@ void LMDBAL::Cache::addRecord(const K& key, const V& value) { handleAddRecord(key, value); } +template +void LMDBAL::Cache::addRecord(const K& key, const V& value, TransactionID txn) { + iStorage::ensureOpened(iStorage::addRecordMethodName); + + if (cache->count(key) > 0) + iStorage::throwDuplicate(iStorage::toString(key)); + + Storage::addRecord(key, value, txn); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + std::pair* pair = new std::pair(key, value); + tc->second.emplace_back(Operation::add, pair); + } +} + template void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { cache->insert(std::make_pair(key, value)); @@ -73,6 +89,21 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { return added; } +template +bool LMDBAL::Cache::forceRecord(const K& key, const V& value, TransactionID txn) { + iStorage::ensureOpened(iStorage::forceRecordMethodName); + + bool added = Storage::forceRecord(key, value, txn); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + std::tuple* t = new std::tuple(added, key, value); + tc->second.emplace_back(Operation::force, t); + } + + return added; +} + template void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool added) { if (*mode == Mode::full) { @@ -118,6 +149,35 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value) { } } +template +void LMDBAL::Cache::changeRecord(const K& key, const V& value, TransactionID txn) { + iStorage::ensureOpened(iStorage::changeRecordMethodName); + + if (*mode == Mode::full) { + typename std::map::iterator itr = cache->find(key); + if (itr == cache->end()) + iStorage::throwNotFound(iStorage::toString(key)); + + Storage::changeRecord(key, value, txn); + } else { + if (abscent->count(key) > 0) + iStorage::throwNotFound(iStorage::toString(key)); + + try { + Storage::changeRecord(key, value, txn); + } catch (const NotFound& error) { + abscent->insert(key); + throw error; + } + } + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + std::pair* pair = new std::pair(key, value); + tc->second.emplace_back(Operation::add, pair); + } +} + template void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { if (*mode == Mode::full) { @@ -155,6 +215,111 @@ V LMDBAL::Cache::getRecord(const K& key) const { } } +template +V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { + iStorage::ensureOpened(iStorage::getRecordMethodName); + + //if there are any changes made within this transaction + //I will be able to see them among pending changes + //so, I'm going to go through them in reverse order + //and check every key. If it has anything to do this requested key + //there is a way to tell... + std::optional candidate = std::nullopt; + typename TransactionCache::const_iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + const Queue& queue = tc->second; + for (typename Queue::const_reverse_iterator i = queue.rbegin(), end = queue.rend(); i != end; ++i) { + const Entry& entry = *i; + + switch (entry.first) { + case Operation::add: + if (static_cast*>(entry.second)->first == key) + return static_cast*>(entry.second)->second; + break; + case Operation::remove: + if (*static_cast(entry.second) == key) { + if (candidate.has_value()) + return candidate.value(); + else + iStorage::throwNotFound(iStorage::toString(key)); + } + break; + case Operation::change: + if (static_cast*>(entry.second)->first == key) + return static_cast*>(entry.second)->second; + break; + case Operation::force: + if (std::get<1>(*static_cast*>(entry.second)) == key) + return std::get<2>(*static_cast*>(entry.second)); + break; + case Operation::drop: + if (candidate.has_value()) + return candidate.value(); + else + iStorage::throwNotFound(iStorage::toString(key)); + break; + case Operation::replace: { + std::map* newMap = static_cast*>(entry.second); + typename std::map::const_iterator vitr = newMap->find(key); + if (vitr != newMap->end()) + return vitr->second; + } + break; + case Operation::addMany: { + const std::tuple>& tuple = *static_cast>*>(entry.second); + const std::map& newElements = std::get<2>(tuple); + typename std::map::const_iterator vitr = newElements.find(key); + if (vitr != newElements.end()) { + if (std::get<0>(tuple)) //if the command was to overwrite - + return vitr->second; //it's clear, current value is the actual + //but if it wasn't, I'm going to remember + if (!candidate.has_value()) //only the last (which is the first, keeping in mind reverse order) + candidate = vitr->second; //occurance and return it in case I meet any NotFound condition + } + + } + break; + } + } + } + //... but if nothing was found or if the transaction is not the one + //which caused the changes i just need to check it among local cache + + typename std::map::const_iterator itr = cache->find(key); + if (itr != cache->end()) + return itr->second; + + if (*mode == Mode::full || abscent->count(key) != 0) { + if (candidate.has_value()) + return candidate.value(); + else + iStorage::throwNotFound(iStorage::toString(key)); + } + + try { + V value = Storage::getRecord(key); + cache->insert(std::make_pair(key, value)); + handleMode(); + return value; + } catch (const NotFound& error) { + if (*mode != Mode::full) + abscent->insert(key); + + if (candidate.has_value()) { + throw Unknown(iStorage::dbName(), +"Something completely wrong have happened: \ +cache has a pending addition transaction \ +(probably as a result of calling addRecords \ +method with overwrite parameter == false, \ +(default is false)), but the database reports \ +that there is no such element in the database under current transaction", + iStorage::name); + } + + throw error; + } +} + template bool LMDBAL::Cache::checkRecord(const K& key) const { iStorage::ensureOpened(iStorage::checkRecordMethodName); @@ -179,6 +344,75 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { } } +template +bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { + iStorage::ensureOpened(iStorage::checkRecordMethodName); + + //if there are any changes made within this transaction + //I will be able to see them among pending changes + //so, I'm going to go through them in reverse order + //and check every key. If it has anything to do this requested key + //there is a way to tell... + typename TransactionCache::const_iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + const Queue& queue = tc->second; + for (typename Queue::const_reverse_iterator i = queue.rbegin(), end = queue.rend(); i != end; ++i) { + const Entry& entry = *i; + + switch (entry.first) { + case Operation::add: + if (static_cast*>(entry.second)->first == key) + return true; + break; + case Operation::remove: + if (*static_cast(entry.second) == key) + return false; + break; + case Operation::change: + if (static_cast*>(entry.second)->first == key) + return true; + break; + case Operation::force: + if (std::get<1>(*static_cast*>(entry.second)) == key) + return true; + break; + case Operation::drop: + return false; + break; + case Operation::replace: + if (static_cast*>(entry.second)->count(key) > 0) + return true; + break; + case Operation::addMany: + if (std::get<2>(*static_cast>*>(entry.second)).count(key) > 0) + return true; + break; + } + } + } + //... but if nothing was found or if the transaction is not the one + //which caused the changes i just need to check it among local cache + + typename std::map::const_iterator itr = cache->find(key); + if (itr != cache->end()) + return true; + + if (*mode == Mode::full || abscent->count(key) != 0) + return false; + + try { + V value = Storage::getRecord(key, txn); + cache->insert(std::make_pair(key, value)); + handleMode(); + return true; + } catch (const NotFound& error) { + if (*mode != Mode::full) + abscent->insert(key); + + return false; + } +} + template std::map LMDBAL::Cache::readAll() const { iStorage::ensureOpened(iStorage::readAllMethodName); @@ -269,6 +503,26 @@ void LMDBAL::Cache::removeRecord(const K& key) { handleRemoveRecord(key); } +template +void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { + iStorage::ensureOpened(iStorage::removeRecordMethodName); + + bool noKey = false; + if (*mode != Mode::full) + noKey = cache->count(key) == 0; + else + noKey = abscent->count(key) > 0; + + if (noKey) + iStorage::throwNotFound(iStorage::toString(key)); + + Storage::removeRecord(key, txn); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) + tc->second.emplace_back(Operation::remove, new K(key)); +} + template void LMDBAL::Cache::handleRemoveRecord(const K& key) { if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease @@ -302,6 +556,67 @@ uint32_t LMDBAL::Cache::count() const { } } +template +uint32_t LMDBAL::Cache::count(TransactionID txn) const { + + int32_t diff = 0; + bool currentTransaction = false; + typename TransactionCache::const_iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + currentTransaction = true; + const Queue& queue = tc->second; + for (typename Queue::const_reverse_iterator i = queue.rbegin(), end = queue.rend(); i != end; ++i) { + const Entry& entry = *i; + + switch (entry.first) { + case Operation::add: + ++diff; + break; + case Operation::remove: + --diff; + break; + case Operation::change: + break; + case Operation::force: + if (std::get<0>(*static_cast*>(entry.second))) + return diff; + break; + case Operation::drop: + return false; + break; + case Operation::replace: + return static_cast*>(entry.second)->size() + diff; + break; + case Operation::addMany: + return Storage::count(txn); //it's just close to impossible to tell + break; + } + } + } + + switch (*mode) { + case Mode::nothing: { + uint32_t sz = Storage::count(txn); + if (!currentTransaction) { + *sizeDifference = sz - cache->size(); + if (sz == 0) { + *mode = Mode::full; + abscent->clear(); + } else { + *mode = Mode::size; + } + } + return sz; + } + case Mode::size: + return cache->size() + *sizeDifference + diff; + case Mode::full: + return cache->size() + diff; + default: + return 0; //unreachable, no such state, just to suppress the waring + } +} + template void LMDBAL::Cache::handleMode() const { if (*mode == Mode::size) { @@ -317,7 +632,9 @@ template int LMDBAL::Cache::drop(TransactionID transaction) { int res = Storage::drop(transaction); - transactionCache->at(transaction).emplace_back(Operation::drop, nullptr); + typename TransactionCache::iterator tc = transactionCache->find(transaction); + if (tc != transactionCache->end()) + tc->second.emplace_back(Operation::drop, nullptr); return res; } diff --git a/src/storage.cpp b/src/storage.cpp index 5949bc7..351ae9d 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -32,7 +32,7 @@ void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); TransactionID txn = db->beginTransaction(); - int rc = drop(txn); + int rc = iStorage::drop(txn); if (rc != MDB_SUCCESS) { abortTransaction(txn); throw Unknown(db->name, mdb_strerror(rc), name); diff --git a/src/storage.hpp b/src/storage.hpp index 52e4422..68ad50e 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -40,7 +40,7 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); TransactionID txn = beginTransaction(); try { - addRecord(key, value, txn); + Storage::addRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; @@ -68,7 +68,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { TransactionID txn = beginTransaction(); bool added; try { - added = forceRecord(key, value, txn); + added = Storage::forceRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; @@ -113,7 +113,7 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { TransactionID txn = beginTransaction(); try { - changeRecord(key, value, txn); + Storage::changeRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; @@ -139,7 +139,7 @@ V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); V value; - getRecord(key, value); + Storage::getRecord(key, value); return value; } @@ -149,7 +149,7 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { TransactionID txn = beginReadOnlyTransaction(); try { - getRecord(key, value, txn); + Storage::getRecord(key, value, txn); } catch (...) { abortTransaction(txn); throw; @@ -163,7 +163,7 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { ensureOpened(getRecordMethodName); V value; - getRecord(key, value, txn); + Storage::getRecord(key, value, txn); return value; } @@ -188,7 +188,7 @@ bool LMDBAL::Storage::checkRecord(const K& key) const { TransactionID txn = beginReadOnlyTransaction(); bool result; try { - result = checkRecord(key, txn); + result = Storage::checkRecord(key, txn); } catch (...) { abortTransaction(txn); throw; @@ -220,7 +220,7 @@ std::map LMDBAL::Storage::readAll() const { ensureOpened(readAllMethodName); std::map result; - readAll(result); + Storage::readAll(result); return result; } @@ -230,7 +230,7 @@ void LMDBAL::Storage::readAll(std::map& result) const { TransactionID txn = beginReadOnlyTransaction(); try { - readAll(result, txn); + Storage::readAll(result, txn); } catch (...) { abortTransaction(txn); throw; @@ -244,7 +244,7 @@ std::map LMDBAL::Storage::readAll(TransactionID txn) const { ensureOpened(readAllMethodName); std::map result; - readAll(result, txn); + Storage::readAll(result, txn); return result; } @@ -278,7 +278,7 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { TransactionID txn = beginTransaction(); try { - replaceAll(data, txn); + Storage::replaceAll(data, txn); } catch (...) { abortTransaction(txn); throw; @@ -313,7 +313,7 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool over TransactionID txn = beginTransaction(); uint32_t amount; try { - amount = addRecords(data, txn, overwrite); + amount = Storage::addRecords(data, txn, overwrite); } catch (...) { abortTransaction(txn); throw; @@ -352,7 +352,7 @@ void LMDBAL::Storage::removeRecord(const K& key) { TransactionID txn = beginTransaction(); try { - removeRecord(key, txn); + Storage::removeRecord(key, txn); } catch (...) { abortTransaction(txn); throw; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e961886..43480dd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(runUnitTests basic.cpp serialization.cpp storagetransaction.cpp + cachetransaction.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp new file mode 100644 index 0000000..5ae7ea3 --- /dev/null +++ b/test/cachetransaction.cpp @@ -0,0 +1,231 @@ +#include + +#include + +#include "base.h" +#include "cache.h" + +class CacheTransactionsTest : public testing::Test { +protected: + CacheTransactionsTest(): + testing::Test(), + c1(db->getCache("cache1")), + c2(db->getCache("cache2")) {} + + ~CacheTransactionsTest() {} + + int waitForChildFork(int pid) { + int status; + if (0 > waitpid(pid, &status, 0)) { + std::cerr << "[----------] Waitpid error!" << std::endl; + return (-1); + } + if (WIFEXITED(status)) { + const int exit_status = WEXITSTATUS(status); + if (exit_status != 0) { + std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl; + } + return exit_status; + } else { + std::cerr << "[----------] Non-normal exit from child!" << std::endl; + return (-2); + } + } + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("storageTrnansactionsTestBase"); + db->addStorage("cache1"); + db->addStorage("cache2"); + } + + db->open(); + db->drop(); + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static LMDBAL::Base* db; + + LMDBAL::Cache* c1; + LMDBAL::Cache* c2; +}; + + +LMDBAL::Base* CacheTransactionsTest::db = nullptr; + +TEST_F(CacheTransactionsTest, Adding) { + EXPECT_EQ(db->ready(), true); + EXPECT_EQ(c1->count(), 0); + EXPECT_EQ(c2->count(), 0); + + LMDBAL::TransactionID txn = db->beginTransaction(); + c1->addRecord(5, 13, txn); + c1->addRecord(-53, 782, txn); + c1->addRecord(5892, -37829, txn); + + c2->addRecord("lorem", 481, txn); + c2->addRecord("decallence", 8532.48, txn); + c2->addRecord("prevent recovery", -64.64, txn); + + EXPECT_EQ(c1->count(), 0); + EXPECT_EQ(c2->count(), 0); + + db->commitTransaction(txn); + + EXPECT_EQ(c1->count(), 3); + EXPECT_EQ(c1->getRecord(5), 13); + EXPECT_EQ(c1->getRecord(-53), 782); + EXPECT_EQ(c1->getRecord(5892), -37829); + + EXPECT_EQ(c2->count(), 3); + EXPECT_FLOAT_EQ(c2->getRecord("lorem"), 481); + EXPECT_FLOAT_EQ(c2->getRecord("decallence"), 8532.48); + EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery"), -64.64); +} + +TEST_F(CacheTransactionsTest, Aborting) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType s1 = c1->count(); + LMDBAL::SizeType s2 = c2->count(); + + LMDBAL::TransactionID txn = db->beginTransaction(); + c1->addRecord(18, 40, txn); + c1->addRecord(85, -4, txn); + c1->addRecord(-5, -3, txn); + + c2->addRecord("tapestry", .053, txn); + c2->addRecord("pepper plants are beautifull", -7, txn); + c2->addRecord("horrots", -23.976, txn); + + EXPECT_EQ(c1->count(), s1); + EXPECT_EQ(c2->count(), s2); + + db->abortTransaction(txn); + + EXPECT_EQ(c1->count(), s1); + EXPECT_EQ(c2->count(), s2); +} + +TEST_F(CacheTransactionsTest, Reading) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + + EXPECT_EQ(c1->count(txn), 3); + EXPECT_EQ(c1->getRecord(5, txn), 13); + EXPECT_EQ(c1->getRecord(-53, txn), 782); + EXPECT_EQ(c1->getRecord(5892, txn), -37829); + + EXPECT_EQ(c2->count(txn), 3); + EXPECT_FLOAT_EQ(c2->getRecord("lorem", txn), 481); + EXPECT_FLOAT_EQ(c2->getRecord("decallence", txn), 8532.48); + EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery", txn), -64.64); + + db->abortTransaction(txn); +} + +TEST_F(CacheTransactionsTest, ConcurentReading) { + // EXPECT_EQ(db->ready(), true); + // + // LMDBAL::SizeType size = c1->count(); + // LMDBAL::TransactionID txn = db->beginTransaction(); + // EXPECT_EQ(c1->getRecord(5, txn), 13); + // EXPECT_EQ(c1->getRecord(5), 13); + // + // c1->removeRecord(5, txn); + // + // EXPECT_FALSE(c1->checkRecord(5, txn)); + // EXPECT_EQ(c1->getRecord(5), 13); + // + // c1->addRecord(5, 571, txn); + // EXPECT_EQ(c1->getRecord(5, txn), 571); + // EXPECT_EQ(c1->getRecord(5), 13); + // + // c1->forceRecord(5, -472, txn); + // EXPECT_EQ(c1->getRecord(5, txn), -472); + // EXPECT_EQ(c1->getRecord(5), 13); + // + // c1->replaceAll({ + // {1, 75} + // }, txn); + // EXPECT_FALSE(c1->checkRecord(5, txn)); + // EXPECT_EQ(c1->getRecord(5), 13); + // EXPECT_EQ(c1->count(txn), 1); + // EXPECT_EQ(c1->count(), size); + // + // db->commitTransaction(txn); + // + // EXPECT_FALSE(c1->checkRecord(5)); + // EXPECT_EQ(c1->count(), 1); +} + +/* +TEST_F(CacheTransactionsTest, ConcurentModification) { + EXPECT_EQ(db->ready(), true); + + //if you start one writable transaction after another + //in a single thread like so: + // + //LMDBAL::TransactionID txn1 = db->beginTransaction(); + //LMDBAL::TransactionID txn2 = db->beginTransaction(); + // + //the execution should block on the second transaction + //so this test should preform in a sequence + //first the parent, then the child + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining second transaction" << std::endl; + LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the first transaction value" << std::endl; + EXPECT_EQ(c1->getRecord(5, txn2), 812); + + std::cout << "forcing second transaction value" << std::endl; + c1->forceRecord(5, -46, txn2); + + std::cout << "checking second transaction value" << std::endl; + EXPECT_EQ(c1->getRecord(5, txn2), -46); + + std::cout << "checking value independently" << std::endl; + EXPECT_EQ(c1->getRecord(5), 812); + + std::cout << "commiting second transaction" << std::endl; + db->commitTransaction(txn2); + + std::cout << "quitting child thread" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining first transaction" << std::endl; + LMDBAL::TransactionID txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + + std::cout << "adding first transaction value" << std::endl; + c1->addRecord(5, 812, txn1); + + std::cout << "checking first transaction value" << std::endl; + EXPECT_EQ(c1->getRecord(5, txn1), 812); + + std::cout << "checking value independently" << std::endl; + EXPECT_FALSE(c1->checkRecord(5)); + + std::cout << "commiting first transaction" << std::endl; + db->commitTransaction(txn1); + + std::cout << "waiting for the other thread to finish" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking final result" << std::endl; + EXPECT_EQ(c1->getRecord(5), -46); +} +*/ From 181a645efc7dac97c096dc05a9deb946c2d2ce44 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 6 Apr 2023 02:01:24 +0300 Subject: [PATCH 035/125] some more transaction methods, method for valueReference in cache, some errors fix --- src/cache.h | 5 + src/cache.hpp | 255 ++++++++++++++++++++++++++++---------- src/storage.hpp | 3 + test/cachetransaction.cpp | 68 +++++----- 4 files changed, 231 insertions(+), 100 deletions(-) diff --git a/src/cache.h b/src/cache.h index 39e7902..2810f85 100644 --- a/src/cache.h +++ b/src/cache.h @@ -84,14 +84,19 @@ public: virtual void removeRecord(const K& key, TransactionID txn) override; virtual bool checkRecord(const K& key) const override; virtual bool checkRecord(const K& key, TransactionID txn) const override; + virtual void getRecord(const K& key, V& out) const override; + virtual void getRecord(const K& key, V& out, TransactionID txn) const override; virtual V getRecord(const K& key) const override; virtual V getRecord(const K& key, TransactionID txn) const override; virtual SizeType count() const override; virtual SizeType count(TransactionID txn) const override; virtual std::map readAll() const override; + virtual std::map readAll(TransactionID txn) const override; virtual void replaceAll(const std::map& data) override; + virtual void replaceAll(const std::map& data, TransactionID txn) override; virtual SizeType addRecords(const std::map& data, bool overwrite = false) override; + virtual SizeType addRecords(const std::map& data, TransactionID txn, bool overwrite = false) override; protected: Mode* mode; diff --git a/src/cache.hpp b/src/cache.hpp index 5c0d9ca..a28f363 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -112,7 +112,8 @@ void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool a if (added) abscent->erase(key); - std::pair::iterator, bool> result = cache->insert(std::make_pair(key, value)); + std::pair::iterator, bool> result = + cache->insert(std::make_pair(key, value)); if (!result.second) result.first->second = value; else if (!added) //this way database had value but cache didn't, so, need to decrease sizeDifference @@ -137,7 +138,8 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value) { try { Storage::changeRecord(key, value); - typename std::pair::iterator, bool> res = cache->insert(std::make_pair(key, value)); + typename std::pair::iterator, bool> res = + cache->insert(std::make_pair(key, value)); if (!res.second) res.first->second = value; else @@ -183,7 +185,8 @@ void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { if (*mode == Mode::full) { cache->at(key) = value; } else { - typename std::pair::iterator, bool> res = cache->insert(std::make_pair(key, value)); + typename std::pair::iterator, bool> res = + cache->insert(std::make_pair(key, value)); if (!res.second) res.first->second = value; else @@ -195,18 +198,29 @@ template V LMDBAL::Cache::getRecord(const K& key) const { iStorage::ensureOpened(iStorage::getRecordMethodName); + V value; + Cache::getRecord(key, value); + return value; +} + +template +void LMDBAL::Cache::getRecord(const K& key, V& out) const { + iStorage::ensureOpened(iStorage::getRecordMethodName); + typename std::map::const_iterator itr = cache->find(key); - if (itr != cache->end()) - return itr->second; + if (itr != cache->end()) { + out = itr->second; + return; + } if (*mode == Mode::full || abscent->count(key) != 0) iStorage::throwNotFound(iStorage::toString(key)); try { - V value = Storage::getRecord(key); - cache->insert(std::make_pair(key, value)); + Storage::getRecord(key, out); + cache->insert(std::make_pair(key, out)); handleMode(); - return value; + return; } catch (const NotFound& error) { if (*mode != Mode::full) abscent->insert(key); @@ -215,68 +229,79 @@ V LMDBAL::Cache::getRecord(const K& key) const { } } + template V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { iStorage::ensureOpened(iStorage::getRecordMethodName); + V value; + Cache::getRecord(key, value, txn); + return value; +} + +template +void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) const { + iStorage::ensureOpened(iStorage::getRecordMethodName); + //if there are any changes made within this transaction //I will be able to see them among pending changes //so, I'm going to go through them in reverse order //and check every key. If it has anything to do this requested key //there is a way to tell... - std::optional candidate = std::nullopt; + bool currentTransaction = false; typename TransactionCache::const_iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { + currentTransaction = true; const Queue& queue = tc->second; for (typename Queue::const_reverse_iterator i = queue.rbegin(), end = queue.rend(); i != end; ++i) { const Entry& entry = *i; switch (entry.first) { case Operation::add: - if (static_cast*>(entry.second)->first == key) - return static_cast*>(entry.second)->second; - break; - case Operation::remove: - if (*static_cast(entry.second) == key) { - if (candidate.has_value()) - return candidate.value(); - else - iStorage::throwNotFound(iStorage::toString(key)); + if (static_cast*>(entry.second)->first == key) { + out = static_cast*>(entry.second)->second; + return; } break; + case Operation::remove: + iStorage::throwNotFound(iStorage::toString(key)); + break; case Operation::change: - if (static_cast*>(entry.second)->first == key) - return static_cast*>(entry.second)->second; + if (static_cast*>(entry.second)->first == key) { + out = static_cast*>(entry.second)->second; + return; + } + break; case Operation::force: - if (std::get<1>(*static_cast*>(entry.second)) == key) - return std::get<2>(*static_cast*>(entry.second)); + if (std::get<1>(*static_cast*>(entry.second)) == key) { + out = std::get<2>(*static_cast*>(entry.second)); + return; + } break; case Operation::drop: - if (candidate.has_value()) - return candidate.value(); - else - iStorage::throwNotFound(iStorage::toString(key)); + iStorage::throwNotFound(iStorage::toString(key)); break; case Operation::replace: { std::map* newMap = static_cast*>(entry.second); typename std::map::const_iterator vitr = newMap->find(key); - if (vitr != newMap->end()) - return vitr->second; + if (vitr != newMap->end()) { + out = vitr->second; + return; + } else { + iStorage::throwNotFound(iStorage::toString(key)); + } } break; case Operation::addMany: { - const std::tuple>& tuple = *static_cast>*>(entry.second); + const std::tuple>& tuple = + *static_cast>*>(entry.second); const std::map& newElements = std::get<2>(tuple); typename std::map::const_iterator vitr = newElements.find(key); if (vitr != newElements.end()) { - if (std::get<0>(tuple)) //if the command was to overwrite - - return vitr->second; //it's clear, current value is the actual - //but if it wasn't, I'm going to remember - if (!candidate.has_value()) //only the last (which is the first, keeping in mind reverse order) - candidate = vitr->second; //occurance and return it in case I meet any NotFound condition + out = vitr->second; + return; } - } break; } @@ -286,35 +311,24 @@ V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { //which caused the changes i just need to check it among local cache typename std::map::const_iterator itr = cache->find(key); - if (itr != cache->end()) - return itr->second; - - if (*mode == Mode::full || abscent->count(key) != 0) { - if (candidate.has_value()) - return candidate.value(); - else - iStorage::throwNotFound(iStorage::toString(key)); + if (itr != cache->end()) { + out = itr->second; + return; } - try { - V value = Storage::getRecord(key); - cache->insert(std::make_pair(key, value)); - handleMode(); - return value; - } catch (const NotFound& error) { - if (*mode != Mode::full) - abscent->insert(key); + if (*mode == Mode::full || abscent->count(key) != 0) + iStorage::throwNotFound(iStorage::toString(key)); - if (candidate.has_value()) { - throw Unknown(iStorage::dbName(), -"Something completely wrong have happened: \ -cache has a pending addition transaction \ -(probably as a result of calling addRecords \ -method with overwrite parameter == false, \ -(default is false)), but the database reports \ -that there is no such element in the database under current transaction", - iStorage::name); + try { + Storage::getRecord(key, out, txn); + if (!currentTransaction) { + cache->insert(std::make_pair(key, out)); + handleMode(); } + return; + } catch (const NotFound& error) { + if (! currentTransaction && *mode != Mode::full) + abscent->insert(key); throw error; } @@ -353,8 +367,10 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { //so, I'm going to go through them in reverse order //and check every key. If it has anything to do this requested key //there is a way to tell... + bool currentTransaction = false; typename TransactionCache::const_iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { + currentTransaction = true; const Queue& queue = tc->second; for (typename Queue::const_reverse_iterator i = queue.rbegin(), end = queue.rend(); i != end; ++i) { const Entry& entry = *i; @@ -382,9 +398,13 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { case Operation::replace: if (static_cast*>(entry.second)->count(key) > 0) return true; + else + return false; break; case Operation::addMany: - if (std::get<2>(*static_cast>*>(entry.second)).count(key) > 0) + if (std::get<2>( + *static_cast>*>(entry.second) + ).count(key) > 0) return true; break; } @@ -402,11 +422,13 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { try { V value = Storage::getRecord(key, txn); - cache->insert(std::make_pair(key, value)); - handleMode(); + if (!currentTransaction) { + cache->insert(std::make_pair(key, value)); + handleMode(); + } return true; } catch (const NotFound& error) { - if (*mode != Mode::full) + if (!currentTransaction && *mode != Mode::full) abscent->insert(key); return false; @@ -427,6 +449,81 @@ std::map LMDBAL::Cache::readAll() const { return *cache; } +template +std::map LMDBAL::Cache::readAll(TransactionID txn) const { + iStorage::ensureOpened(iStorage::readAllMethodName); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + Queue& queue = tc->second; + if (*mode != Mode::full) { + std::map result = *cache; + + for (typename Queue::const_iterator i = queue.begin(), end = queue.end(); i != end; ++i) { + const Entry& entry = *i; + switch (entry.first) { + case Operation::add: + result.insert(*static_cast*>(entry.second)); + break; + case Operation::remove: + result.erase(*static_cast(entry.second)); + break; + case Operation::change: { + std::pair* pair = static_cast*>(entry.second); + result.at(pair->first) = pair->second; + } + break; + case Operation::force:{ + const std::tuple& tuple = + *static_cast*>(entry.second); + result[std::get<1>(tuple)] = std::get<2>(tuple); + } + break; + case Operation::drop: + result.clear(); + break; + case Operation::replace: + result = *static_cast*>(entry.second); + break; + case Operation::addMany: { + const std::tuple>& t = + *static_cast>*>(entry.second); + const std::map& added = std::get<2>(t); + bool overwrite = std::get<0>(t); + for (const std::pair& pair : added) { + if (overwrite) + result[pair.first] = pair.second; + else + result.insert(pair); + } + } + break; + } + } + + return result; + } else { + std::map* result = new std::map(); + Storage::readAll(*result, txn); + + //queue.clear(); //since I'm getting a complete state of the database + queue.emplace_back(Operation::replace, result); //I can as well erase all previous cache entries + + return *result; + } + + } else { + if (*mode != Mode::full) { //there is a room for optimization + *mode = Mode::full; //I can read and deserialize only those values + *cache = Storage::readAll(txn); //that are missing in the cache + abscent->clear(); + *sizeDifference = 0; + } + + return *cache; + } +} + template void LMDBAL::Cache::replaceAll(const std::map& data) { Storage::replaceAll(data); @@ -439,6 +536,18 @@ void LMDBAL::Cache::replaceAll(const std::map& data) { } } +template +void LMDBAL::Cache::replaceAll(const std::map& data, TransactionID txn) { + Storage::replaceAll(data, txn); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + //queue.clear(); + std::map* map = new std::map(data); //since I'm getting a complete state of the database + tc->second.emplace_back(Operation::replace, map); //I can as well erase all previous cache entries + } +} + template void LMDBAL::Cache::handleReplaceAll(std::map* data) { delete cache; @@ -459,6 +568,20 @@ LMDBAL::SizeType LMDBAL::Cache::addRecords(const std::map& data, boo return newSize; } +template +LMDBAL::SizeType LMDBAL::Cache::addRecords(const std::map& data, TransactionID txn, bool overwrite) { + SizeType newSize = Storage::addRecords(data, txn, overwrite); + + typename TransactionCache::iterator tc = transactionCache->find(txn); + if (tc != transactionCache->end()) { + std::tuple>* tuple = + new std::tuple>(overwrite, newSize, data); + tc->second.emplace_back(Operation::addMany, tuple); + } + + return newSize; +} + template void LMDBAL::Cache::handleAddRecords(const std::map& data, bool overwrite, SizeType newSize) { Mode& m = *mode; @@ -579,7 +702,7 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { break; case Operation::force: if (std::get<0>(*static_cast*>(entry.second))) - return diff; + ++diff; break; case Operation::drop: return false; @@ -588,7 +711,7 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { return static_cast*>(entry.second)->size() + diff; break; case Operation::addMany: - return Storage::count(txn); //it's just close to impossible to tell + return std::get<1>(*static_cast>*>(entry.second)) + diff; break; } } diff --git a/src/storage.hpp b/src/storage.hpp index 68ad50e..8508268 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -334,6 +334,9 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti lmdbData = valueSerializer->setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); + if (rc == MDB_KEYEXIST) + throwDuplicate(toString(pair.first)); + if (rc != MDB_SUCCESS) throwUnknown(rc); } diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index 5ae7ea3..f158f0e 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -132,41 +132,41 @@ TEST_F(CacheTransactionsTest, Reading) { } TEST_F(CacheTransactionsTest, ConcurentReading) { - // EXPECT_EQ(db->ready(), true); - // - // LMDBAL::SizeType size = c1->count(); - // LMDBAL::TransactionID txn = db->beginTransaction(); - // EXPECT_EQ(c1->getRecord(5, txn), 13); - // EXPECT_EQ(c1->getRecord(5), 13); - // - // c1->removeRecord(5, txn); - // - // EXPECT_FALSE(c1->checkRecord(5, txn)); - // EXPECT_EQ(c1->getRecord(5), 13); - // - // c1->addRecord(5, 571, txn); - // EXPECT_EQ(c1->getRecord(5, txn), 571); - // EXPECT_EQ(c1->getRecord(5), 13); - // - // c1->forceRecord(5, -472, txn); - // EXPECT_EQ(c1->getRecord(5, txn), -472); - // EXPECT_EQ(c1->getRecord(5), 13); - // - // c1->replaceAll({ - // {1, 75} - // }, txn); - // EXPECT_FALSE(c1->checkRecord(5, txn)); - // EXPECT_EQ(c1->getRecord(5), 13); - // EXPECT_EQ(c1->count(txn), 1); - // EXPECT_EQ(c1->count(), size); - // - // db->commitTransaction(txn); - // - // EXPECT_FALSE(c1->checkRecord(5)); - // EXPECT_EQ(c1->count(), 1); + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType size = c1->count(); + LMDBAL::TransactionID txn = db->beginTransaction(); + EXPECT_EQ(c1->getRecord(5, txn), 13); + EXPECT_EQ(c1->getRecord(5), 13); + + c1->removeRecord(5, txn); + + EXPECT_FALSE(c1->checkRecord(5, txn)); + EXPECT_EQ(c1->getRecord(5), 13); + + c1->addRecord(5, 571, txn); + EXPECT_EQ(c1->getRecord(5, txn), 571); + EXPECT_EQ(c1->getRecord(5), 13); + + c1->forceRecord(5, -472, txn); + EXPECT_EQ(c1->getRecord(5, txn), -472); + EXPECT_EQ(c1->getRecord(5), 13); + + c1->replaceAll({ + {1, 75} + }, txn); + EXPECT_FALSE(c1->checkRecord(5, txn)); + EXPECT_EQ(c1->getRecord(5), 13); + EXPECT_EQ(c1->count(txn), 1); + EXPECT_EQ(c1->count(), size); + + db->commitTransaction(txn); + + EXPECT_FALSE(c1->checkRecord(5)); + EXPECT_EQ(c1->count(), 1); } -/* + TEST_F(CacheTransactionsTest, ConcurentModification) { EXPECT_EQ(db->ready(), true); @@ -228,4 +228,4 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { std::cout << "checking final result" << std::endl; EXPECT_EQ(c1->getRecord(5), -46); } -*/ + From 064277fa6e39e2f2b6bb32231a3a91f901105648 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 7 Apr 2023 02:51:45 +0300 Subject: [PATCH 036/125] two more methods for getting all records from cache, one more test for reading all --- src/cache.h | 2 ++ src/cache.hpp | 63 +++++++++++++++++++++++++++++++------------------- test/basic.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 24 deletions(-) diff --git a/src/cache.h b/src/cache.h index 2810f85..b7571d5 100644 --- a/src/cache.h +++ b/src/cache.h @@ -93,6 +93,8 @@ public: virtual std::map readAll() const override; virtual std::map readAll(TransactionID txn) const override; + virtual void readAll(std::map& out) const override; + virtual void readAll(std::map& out, TransactionID txn) const override; virtual void replaceAll(const std::map& data) override; virtual void replaceAll(const std::map& data, TransactionID txn) override; virtual SizeType addRecords(const std::map& data, bool overwrite = false) override; diff --git a/src/cache.hpp b/src/cache.hpp index a28f363..93f19dd 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -229,7 +229,6 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { } } - template V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { iStorage::ensureOpened(iStorage::getRecordMethodName); @@ -449,41 +448,64 @@ std::map LMDBAL::Cache::readAll() const { return *cache; } +template +void LMDBAL::Cache::readAll(std::map& out) const { + iStorage::ensureOpened(iStorage::readAllMethodName); + + if (*mode != Mode::full) { //there is a room for optimization + *mode = Mode::full; //I can read and deserialize only those values + Storage::readAll(out); //that are missing in the cache + *cache = out; + abscent->clear(); + *sizeDifference = 0; + } +} + template std::map LMDBAL::Cache::readAll(TransactionID txn) const { iStorage::ensureOpened(iStorage::readAllMethodName); + std::map out; + readAll(out, txn); + + return out; +} + +template +void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const { + iStorage::ensureOpened(iStorage::readAllMethodName); + typename TransactionCache::iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { Queue& queue = tc->second; if (*mode != Mode::full) { - std::map result = *cache; + out = *cache; for (typename Queue::const_iterator i = queue.begin(), end = queue.end(); i != end; ++i) { const Entry& entry = *i; switch (entry.first) { case Operation::add: - result.insert(*static_cast*>(entry.second)); + out.insert(*static_cast*>(entry.second)); break; case Operation::remove: - result.erase(*static_cast(entry.second)); + out.erase(*static_cast(entry.second)); break; case Operation::change: { std::pair* pair = static_cast*>(entry.second); - result.at(pair->first) = pair->second; + out.at(pair->first) = pair->second; } break; case Operation::force:{ const std::tuple& tuple = *static_cast*>(entry.second); - result[std::get<1>(tuple)] = std::get<2>(tuple); + out[std::get<1>(tuple)] = std::get<2>(tuple); } break; case Operation::drop: - result.clear(); + out.clear(); break; case Operation::replace: - result = *static_cast*>(entry.second); + out = *static_cast*>(entry.second); break; case Operation::addMany: { const std::tuple>& t = @@ -492,35 +514,28 @@ std::map LMDBAL::Cache::readAll(TransactionID txn) const { bool overwrite = std::get<0>(t); for (const std::pair& pair : added) { if (overwrite) - result[pair.first] = pair.second; + out[pair.first] = pair.second; else - result.insert(pair); + out.insert(pair); } } break; } } - - return result; } else { - std::map* result = new std::map(); - Storage::readAll(*result, txn); + Storage::readAll(out, txn); - //queue.clear(); //since I'm getting a complete state of the database - queue.emplace_back(Operation::replace, result); //I can as well erase all previous cache entries - - return *result; + //queue.clear(); //since I'm getting a complete state of the database + queue.emplace_back(Operation::replace, new std::map(out)); //I can as well erase all previous cache entries } - } else { - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values - *cache = Storage::readAll(txn); //that are missing in the cache + if (*mode != Mode::full) { //there is a room for optimization + *mode = Mode::full; //I can read and deserialize only those values + Storage::readAll(out); //that are missing in the cache + *cache = out; abscent->clear(); *sizeDifference = 0; } - - return *cache; } } diff --git a/test/basic.cpp b/test/basic.cpp index fdef128..baf48cf 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -301,3 +301,64 @@ TEST_F(BaseTest, ReadAll) { EXPECT_EQ(m2.size(), 3); EXPECT_EQ(m3.size(), 4); } + + + +TEST_F(BaseTest, ReplaceAll) { + EXPECT_EQ(db->ready(), true); + + t1->replaceAll({ + {7, 48}, + {194, 582}, + {857, 39}, + {9717, 8} + }); + t2->replaceAll({ + {"bringin", "keyboard"}, + {"cluster", "throttle"}, + {"ronin", "cheese"} + }); + c1->replaceAll({}); + + EXPECT_EQ(t1->count(), 4); + EXPECT_EQ(t2->count(), 3); + EXPECT_EQ(c1->count(), 0); + + EXPECT_FALSE(t1->checkRecord(2)); + EXPECT_FALSE(t1->checkRecord(58)); + EXPECT_FALSE(t1->checkRecord(68)); + + EXPECT_FALSE(t2->checkRecord("sdfhga")); + EXPECT_FALSE(t2->checkRecord("prophecy")); + EXPECT_FALSE(t2->checkRecord("lawfirm")); + + EXPECT_FALSE(c1->checkRecord(15)); + EXPECT_FALSE(c1->checkRecord(12)); + EXPECT_FALSE(c1->checkRecord(89)); + EXPECT_FALSE(c1->checkRecord(98)); + + EXPECT_EQ(t1->getRecord(7), 48); + EXPECT_EQ(t1->getRecord(194), 582); + EXPECT_EQ(t1->getRecord(857), 39); + EXPECT_EQ(t1->getRecord(9717), 8); + + EXPECT_EQ(t2->getRecord("bringin"), "keyboard"); + EXPECT_EQ(t2->getRecord("cluster"), "throttle"); + EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + + + c1->replaceAll({ + {68, "quality"}, + {31, "ridgid body"}, + {16, "fermentation on your kind"}, + {22, "pseudo"}, + {-117, "lance of Michael"}, + }); + EXPECT_EQ(c1->count(), 5); + + EXPECT_EQ(c1->getRecord(68), "quality"); + EXPECT_EQ(c1->getRecord(31), "ridgid body"); + EXPECT_EQ(c1->getRecord(16), "fermentation on your kind"); + EXPECT_EQ(c1->getRecord(22), "pseudo"); + EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); +} From 6d21ecc1552b3f42387f805e3aae97f69337a6b3 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 8 Apr 2023 02:27:53 +0300 Subject: [PATCH 037/125] one more test for adding multiple entries, fix of leaking cursor, Storage::change method rework, beginning of documentation --- doc/mainpage.dox | 3 +- src/storage.hpp | 189 ++++++++++++++++++++++++++++++++++++++- test/basic.cpp | 223 +++++++++++++++++++++++++++++++---------------- 3 files changed, 334 insertions(+), 81 deletions(-) diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 95db214..7cd5015 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -25,6 +25,7 @@ * At this point you are not allowed to add any more storages, otherwise LMDBAL::Opened exception will be thrown. * It's currently the limitation of this little library and I might solve it in the future. * Database will throw no exception if you will try to close the closed LMDBAL::Base or open again already opened one. - * Also it will automatically close itself you will try to destoroy onpened LMDBAL::Base. + * Also it will automatically close itself if you'll try to destoroy onpened LMDBAL::Base. * + * To discover how to store read and modify data take a look at LMDBAL::Storage and LMDBAL::Cache classes. */ diff --git a/src/storage.hpp b/src/storage.hpp index 8508268..6818552 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -22,6 +22,20 @@ #include "storage.h" #include "exceptions.h" +/** + * \class LMDBAL::Storage + * \brief This is a basic key value storage. + * + * \tparam K type of the keys of the storage + * \tparam V type of the values of the storage + * + * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&) + * if the database is yet closed and you're defining the storages you're going to need. + * Or you can call LMDBAL::Base::getStorage(const std::string&) if the database is opened and you didn't save a pointer to the storage + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + template LMDBAL::Storage::Storage(const std::string& p_name, Base* parent): iStorage(p_name, parent), @@ -35,6 +49,18 @@ LMDBAL::Storage::~Storage() { delete keySerializer; } +/** + * \brief Adds a key-value record to the storage + * + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * + * \param[in] key key of the record + * \param[in] value value of the record + * + * \exception LMDBAL::Exist thrown if the storage already has a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); @@ -49,6 +75,22 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { commitTransaction(txn); } +/** + * \brief Adds a key-value record to the storage (transaction variant) + * + * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::Exist thrown if the storage already has a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(addRecordMethodName); @@ -61,6 +103,15 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI throwDuplicateOrUnknown(rc, toString(key)); } +/** + * \brief Adds a key-value record to the storage, overwrites if it already exists + * \param[in] key key of the record + * \param[in] value value of the record + * \returns true if the record was added, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { ensureOpened(forceRecordMethodName); @@ -78,6 +129,21 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { return added; } +/** + * \brief Adds a key-value record to the storage, overwrites if it already exists (transaction variant) + * + * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::Base::commitTransaction(). + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * \returns true if the record was added, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(forceRecordMethodName); @@ -107,6 +173,18 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio return added; } +/** + * \brief Changes key-value record to the storage. + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record + * \param[in] value new value of the record + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value) { ensureOpened(changeRecordMethodName); @@ -122,18 +200,55 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { commitTransaction(txn); } +/** + * \brief Changes key-value record to the storage (transaction variant) + * + * This function schedules a modification of a key-value record, but doesn't immidiately changes it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record + * \param[in] value new value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(changeRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_cursor* cursor; + int rc = mdb_cursor_open(txn, dbi, &cursor); + if (rc != MDB_SUCCESS) + throwUnknown(rc); - int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + MDB_val lmdbKey = keySerializer->setData(key); + rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET); + if (rc != MDB_SUCCESS) + throwNotFoundOrUnknown(rc, toString(key)); + + MDB_val lmdbData = valueSerializer->setData(value); + rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); + mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); } +/** + * \brief Gets the record from the database + + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \returns the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); @@ -143,6 +258,18 @@ V LMDBAL::Storage::getRecord(const K& key) const { return value; } +/** + * \brief Gets the record from the database (reference variant) + + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[out] value the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::getRecord(const K& key, V& value) const { ensureOpened(getRecordMethodName); @@ -158,6 +285,22 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { abortTransaction(txn); } +/** + * \brief Gets the record from the database (transaction variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[in] txn transaction ID, can be read only transaction + * \returns the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { ensureOpened(getRecordMethodName); @@ -167,6 +310,22 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { return value; } +/** + * \brief Gets the record from the database (transaction, reference variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[out] value the value from the storage + * \param[in] txn transaction ID, can be read only transaction + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { ensureOpened(getRecordMethodName); @@ -181,6 +340,15 @@ void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) valueSerializer->deserialize(lmdbData, value); } +/** + * \brief Chechs if storage has value + * + * \param[in] key key of the record you look for + * \returns true if there was a record with given key, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::checkRecord(const K& key) const { ensureOpened(checkRecordMethodName); @@ -198,6 +366,19 @@ bool LMDBAL::Storage::checkRecord(const K& key) const { return result; } +/** + * \brief Chechs if storage has value (transaction variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * \param[in] key key of the record you look for + * \param[in] txn transaction ID, can be read only transaction + * \returns true if there was a record with given key, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { ensureOpened(checkRecordMethodName); @@ -267,7 +448,7 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c valueSerializer->deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } - + mdb_cursor_close(cursor); if (rc != MDB_NOTFOUND) throwUnknown(rc); } diff --git a/test/basic.cpp b/test/basic.cpp index baf48cf..143e2c7 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -83,67 +83,25 @@ TEST_F(BaseTest, AddingKeysToCache) { EXPECT_EQ(c1->getRecord(-116), "whatever"); } -TEST_F(BaseTest, AddingRepeatingIntegerKey) { +TEST_F(BaseTest, AddingRepeatingKey) { EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - t1->addRecord(3, 24); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + EXPECT_THROW(t1->addRecord(3, 24), LMDBAL::Exist); EXPECT_EQ(t1->getRecord(3), 15); -} -TEST_F(BaseTest, AddingRepeatingStringKey) { - EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - t2->addRecord("sdfhga", "world"); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->addRecord("sdfhga", "world"), LMDBAL::Exist); EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); -} -TEST_F(BaseTest, AddingRepeatingCacheKey) { - EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - c1->addRecord(-4, "world"); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(c1->addRecord(-4, "world"), LMDBAL::Exist); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); } TEST_F(BaseTest, GettingNotExistingKeys) { EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - QString wrong = t2->getRecord("almonds"); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - thrown = false; - try { - uint32_t wrong = t1->getRecord(64); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - std::string wrong = c1->getRecord(21); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound); + EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound); + EXPECT_THROW(c1->getRecord(21), LMDBAL::NotFound); } TEST_F(BaseTest, Persistence) { @@ -172,29 +130,9 @@ TEST_F(BaseTest, Persistence) { EXPECT_EQ(c1->getRecord(-37), "aaaaa tss tsss tsss tsss aaaaaaa"); EXPECT_EQ(c1->getRecord(2), "blah balah"); - bool thrown = false; - try { - QString wrong = t2->getRecord("cats"); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - uint32_t wrong = t1->getRecord(7893); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - std::string wrong = c1->getRecord(89); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound); + EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound); + EXPECT_THROW(c1->getRecord(89), LMDBAL::NotFound); } TEST_F(BaseTest, CountAndDrop) { @@ -239,6 +177,10 @@ TEST_F(BaseTest, Change) { c1->changeRecord(15, "recording"); c1->changeRecord(12, "thermal"); + EXPECT_THROW(t1->changeRecord(37, 49), LMDBAL::NotFound); + EXPECT_THROW(t2->changeRecord("precision", "cryoplastics"), LMDBAL::NotFound); + EXPECT_THROW(c1->changeRecord(-62, "sharks"), LMDBAL::NotFound); + EXPECT_EQ(t1->getRecord(2), 49); EXPECT_EQ(t2->getRecord("sdfhga"), "void"); EXPECT_EQ(c1->getRecord(15), "recording"); @@ -302,8 +244,6 @@ TEST_F(BaseTest, ReadAll) { EXPECT_EQ(m3.size(), 4); } - - TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(db->ready(), true); @@ -346,7 +286,6 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(t2->getRecord("cluster"), "throttle"); EXPECT_EQ(t2->getRecord("ronin"), "cheese"); - c1->replaceAll({ {68, "quality"}, {31, "ridgid body"}, @@ -362,3 +301,135 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(c1->getRecord(22), "pseudo"); EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); } + + + +TEST_F(BaseTest, AddRecords) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType s1 = t1->addRecords({ + {5, 3}, + {800, 9} + }); + EXPECT_EQ(s1, 6); + EXPECT_EQ(t1->getRecord(7), 48); + EXPECT_EQ(t1->getRecord(194), 582); + EXPECT_EQ(t1->getRecord(857), 39); + EXPECT_EQ(t1->getRecord(9717), 8); + EXPECT_EQ(t1->getRecord(5), 3); + EXPECT_EQ(t1->getRecord(800), 9); + s1 = t1->addRecords({ + {194, 371}, + {808, 487}, + {807, 0} + }, true); + EXPECT_EQ(s1, 8); + EXPECT_EQ(t1->count(), 8); + EXPECT_EQ(t1->getRecord(7), 48); + EXPECT_EQ(t1->getRecord(194), 371); + EXPECT_EQ(t1->getRecord(857), 39); + EXPECT_EQ(t1->getRecord(9717), 8); + EXPECT_EQ(t1->getRecord(5), 3); + EXPECT_EQ(t1->getRecord(800), 9); + EXPECT_EQ(t1->getRecord(808), 487); + EXPECT_EQ(t1->getRecord(807), 0); + EXPECT_THROW( + s1 = t1->addRecords({ + {194, 371}, + {808, 487}, + {807, 0} + }), LMDBAL::Exist + ); + EXPECT_EQ(t1->count(), 8); + + LMDBAL::SizeType s2 = t2->addRecords({ + {"lama", "not quite"}, + {"by the shadow", "leech"}, + {"summertime", "curses"} + }); + EXPECT_EQ(s2, 6); + EXPECT_EQ(t2->count(), 6); + EXPECT_EQ(t2->getRecord("bringin"), "keyboard"); + EXPECT_EQ(t2->getRecord("cluster"), "throttle"); + EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + EXPECT_EQ(t2->getRecord("lama"), "not quite"); + EXPECT_EQ(t2->getRecord("by the shadow"), "leech"); + EXPECT_EQ(t2->getRecord("summertime"), "curses"); + s2 = t2->addRecords({ + {"worry not", "for shall you"}, + {"by the shadow", "face the inevitable"}, + {"cluster", "sobing over those"} + }, true); + + EXPECT_EQ(s2, 7); + EXPECT_EQ(t2->count(), 7); + EXPECT_EQ(t2->getRecord("bringin"), "keyboard"); + EXPECT_EQ(t2->getRecord("cluster"), "sobing over those"); + EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + EXPECT_EQ(t2->getRecord("lama"), "not quite"); + EXPECT_EQ(t2->getRecord("by the shadow"), "face the inevitable"); + EXPECT_EQ(t2->getRecord("summertime"), "curses"); + EXPECT_EQ(t2->getRecord("worry not"), "for shall you"); + + EXPECT_THROW( + s2 = t2->addRecords({ + {"within reasonable limits", "occasion"}, + {"ronin", "crest of violence"}, + {"permanent", "of your kind"} + }), LMDBAL::Exist + ); + EXPECT_EQ(t2->count(), 7); + + LMDBAL::SizeType s3 = c1->addRecords({ + {19, "menace"}, + {-7, "failure driven sorrow"}, + {82, "lungache"}, + {4, "drowsy"}, + {44, "pressure"}, + }); + + EXPECT_EQ(c1->count(), 10); + EXPECT_EQ(s3, 10); + + EXPECT_EQ(c1->getRecord(68), "quality"); + EXPECT_EQ(c1->getRecord(31), "ridgid body"); + EXPECT_EQ(c1->getRecord(16), "fermentation on your kind"); + EXPECT_EQ(c1->getRecord(22), "pseudo"); + EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); + EXPECT_EQ(c1->getRecord(19), "menace"); + EXPECT_EQ(c1->getRecord(-7), "failure driven sorrow"); + EXPECT_EQ(c1->getRecord(82), "lungache"); + EXPECT_EQ(c1->getRecord(4), "drowsy"); + EXPECT_EQ(c1->getRecord(44), "pressure"); + + EXPECT_THROW( + s3 = c1->addRecords({ + {-72, "amber"}, + {-9, "going swinging of paleopathy"}, + {82, "regret"} + }), LMDBAL::Exist + ); + EXPECT_EQ(c1->count(), 10); + + s3 = c1->addRecords({ + {19, "to replicated being"}, + {123, "horibly unforseen"}, + {-32, "stitched"}, + {31, "overall"} + }, true); + EXPECT_EQ(c1->count(), 12); + EXPECT_EQ(s3, 12); + EXPECT_EQ(c1->getRecord(68), "quality"); + EXPECT_EQ(c1->getRecord(31), "overall"); + EXPECT_EQ(c1->getRecord(16), "fermentation on your kind"); + EXPECT_EQ(c1->getRecord(22), "pseudo"); + EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); + EXPECT_EQ(c1->getRecord(19), "to replicated being"); + EXPECT_EQ(c1->getRecord(-7), "failure driven sorrow"); + EXPECT_EQ(c1->getRecord(82), "lungache"); + EXPECT_EQ(c1->getRecord(4), "drowsy"); + EXPECT_EQ(c1->getRecord(44), "pressure"); + EXPECT_EQ(c1->getRecord(-32), "stitched"); + EXPECT_EQ(c1->getRecord(123), "horibly unforseen"); + +} From ec0d2d57f09c40f6270aaae51e27e7d5abc953b3 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 9 Apr 2023 14:19:23 -0300 Subject: [PATCH 038/125] finished documentation for Storage object, added doxygen awesome theme support --- CMakeLists.txt | 1 + doc/CMakeLists.txt | 22 ++++++++ src/storage.hpp | 135 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fbbeb34..d56e05d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ cmake_policy(SET CMP0079 NEW) option(BUILD_STATIC "Builds library as static library" OFF) option(BUILD_TESTS "Builds tests" OFF) option(BUILD_DOC "Builds documentation" OFF) +option(BUILD_DOXYGEN_AWESOME "Builds documentation alternative style" OFF) include(GNUInstallDirs) include(CMakePackageConfigHelpers) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 4998bb0..1626a47 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,6 +1,24 @@ set(DOXYGEN_GENERATE_HTML YES) set(DOXYGEN_GENERATE_MAN YES) +if (BUILD_DOXYGEN_AWESOME) + include(ExternalProject) + ExternalProject_Add(doxygen-awesome-css + GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css.git + GIT_TAG "v2.2.0" + CONFIGURE_COMMAND "" + BUILD_COMMAND make + BUILD_IN_SOURCE TRUE + INSTALL_COMMAND make DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css install + ) + set (DOXYGEN_GENERATE_TREEVIEW YES) + set (DOXYGEN_DISABLE_INDEX NO) + set (DOXYGEN_FULL_SIDEBAR NO) + set (DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome.css ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only.css) + set (DOXYGEN_FULL_SIDEBAR NO) + set (DOXYGEN_HTML_COLORSTYLE "LIGHT") +endif() + doxygen_add_docs( documentation ${PROJECT_SOURCE_DIR}/src @@ -8,3 +26,7 @@ doxygen_add_docs( ALL COMMENT "Generate man and html pages" ) + +if (BUILD_DOXYGEN_AWESOME) + add_dependencies(documentation doxygen-awesome-css) +endif() diff --git a/src/storage.hpp b/src/storage.hpp index 6818552..f46ebd8 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -396,6 +396,14 @@ bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { return false; } +/** + * \brief Reads whole storage into a map + * + * Basically just reads all database in an std::map, usefull when you store small storages + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template std::map LMDBAL::Storage::readAll() const { ensureOpened(readAllMethodName); @@ -405,6 +413,16 @@ std::map LMDBAL::Storage::readAll() const { return result; } +/** + * \brief Reads whole storage into a map (reference variant) + * + * Basically just reads all database in an std::map, usefull when you store small storages + * + * \param[out] result a map that is going to contain all data + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::readAll(std::map& result) const { ensureOpened(readAllMethodName); @@ -420,6 +438,18 @@ void LMDBAL::Storage::readAll(std::map& result) const { abortTransaction(txn); } +/** + * \brief Reads whole storage into a map (transaction variant) + * + * Basically just reads all database in an std::map, usefull when you store small storages + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * \param[in] txn transaction ID, can be read only transaction + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template std::map LMDBAL::Storage::readAll(TransactionID txn) const { ensureOpened(readAllMethodName); @@ -429,6 +459,19 @@ std::map LMDBAL::Storage::readAll(TransactionID txn) const { return result; } +/** + * \brief Reads whole storage into a map (transaction, reference variant) + * + * Basically just reads all database in an std::map, usefull when you store small storages + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * \param[out] result a map that is going to contain all data + * \param[in] txn transaction ID, can be read only transaction + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) const { ensureOpened(readAllMethodName); @@ -453,6 +496,16 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c throwUnknown(rc); } +/** + * \brief Replaces the content of the whole storage with the given + * + * Basically this function drops the database and adds all the records from the given map + * + * \param[in] data new data of the storage + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::replaceAll(const std::map& data) { ensureOpened(replaceAllMethodName); @@ -468,6 +521,19 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { commitTransaction(txn); } +/** + * \brief Replaces the content of the whole storage with the given (transaction variant) + * + * Basically this function drops the database and adds all the records from the given map + * This function schedules a data replacement, but doesn't immidiately execute it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * \param[in] data new data of the storage + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID txn) { ensureOpened(replaceAllMethodName); @@ -481,12 +547,23 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID lmdbKey = keySerializer->setData(pair.first); lmdbData = valueSerializer->setData(pair.second); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here? if (rc != MDB_SUCCESS) throwUnknown(rc); } } +/** + * \brief Adds records in bulk + * + * \param[in] data the data to be added + * \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it + * \returns new actual amount of records in the storage + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Exist thrown if overwrite==false and at least one of the keys of data already exists in the storage + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool overwrite) { ensureOpened(addRecordsMethodName); @@ -504,6 +581,21 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool over return amount; } +/** + * \brief Adds records in bulk (transaction variant) + * + * This function schedules a data addition, but doesn't immidiately execute it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * \param[in] data the data to be added + * \param[in] txn transaction ID, needs to be a writable transaction! + * \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it + * \returns new actual amount of records in the storage + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Exist thrown if overwrite==false and at least one of the keys of data already exists in the storage + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template uint32_t LMDBAL::Storage::addRecords(const std::map& data, TransactionID txn, bool overwrite) { ensureOpened(addRecordsMethodName); @@ -530,6 +622,17 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti return stat.ms_entries; } +/** + * \brief Removes one of the records + * + * Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you wish to be removed + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::NotFound thrown if the record with given key wasn't found + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::removeRecord(const K& key) { ensureOpened(removeRecordMethodName); @@ -545,6 +648,20 @@ void LMDBAL::Storage::removeRecord(const K& key) { commitTransaction(txn); } +/** + * \brief Removes one of the records (transaction variant) + * + * Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown + * This function schedules a record removal, but doesn't immidiately execute it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * \param[in] key key of the record you wish to be removed + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::NotFound thrown if the record with given key wasn't found + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { ensureOpened(removeRecordMethodName); @@ -555,6 +672,13 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { throwNotFoundOrUnknown(rc, toString(key)); } +/** + * \brief A private virtual function I need to open database + * + * This and the following collection of specializations are a way to optimise database using + * MDB_INTEGERKEY flag, when the key is actually kind of an integer + * This infrastructure also allowes us to customize mdb_dbi_open call in the future + */ template int LMDBAL::Storage::createStorage(MDB_txn* transaction) { return makeTable(transaction); @@ -605,6 +729,15 @@ inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * This function is mainly used in exceptions, to report which key was duplicated or not found. + * You can define your own specializations to this function in case std::to_string doesn't cover your case + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ template inline std::string LMDBAL::iStorage::toString(const T& value) { return std::to_string(value); From af0e48a684c1f4d583152f1b229809dd4d1de30e Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 10 Apr 2023 18:01:19 -0300 Subject: [PATCH 039/125] Base class documentation, doxugen tweaking, new exception for one case --- CMakeLists.txt | 6 +- README.md | 1 + doc/CMakeLists.txt | 18 +++- doc/custom.css | 3 + doc/header.html | 78 +++++++++++++++++ src/base.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++++- src/base.h | 70 +++++++++++++-- src/exceptions.cpp | 14 +++ src/exceptions.h | 10 +++ src/storage.h | 2 +- src/storage.hpp | 20 ++--- 11 files changed, 408 insertions(+), 20 deletions(-) create mode 100644 doc/custom.css create mode 100644 doc/header.html diff --git a/CMakeLists.txt b/CMakeLists.txt index d56e05d..c60a83e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.16) -project(LMDBAL VERSION 0.2.0 LANGUAGES CXX) +project(LMDBAL + VERSION 0.2.0 + DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" + LANGUAGES CXX +) string(TOLOWER ${PROJECT_NAME} PROJECT_LOW) cmake_policy(SET CMP0076 NEW) diff --git a/README.md b/README.md index b81524c..fa4de83 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`: - `BUILD_STATIC` - `True` builds project as a static library, `False` builds as dynamic (default is `False`); - `BUILD_TESTS` - `True` build unit tests, `False` does not (default is `False`); - `BUILD_DOC` - `True` build doxygen documentation, `False` does not (default is `False`); +- `BUILD_DOXYGEN_AWESOME` - `True` build doxygen awesome theme if `BUILD_DOC` is also `True` (default is `False`); - `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically; #### Running tests diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1626a47..24ee33b 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,5 +1,6 @@ set(DOXYGEN_GENERATE_HTML YES) set(DOXYGEN_GENERATE_MAN YES) +set(DOXYGEN_GENERATE_XML YES) if (BUILD_DOXYGEN_AWESOME) include(ExternalProject) @@ -14,9 +15,18 @@ if (BUILD_DOXYGEN_AWESOME) set (DOXYGEN_GENERATE_TREEVIEW YES) set (DOXYGEN_DISABLE_INDEX NO) set (DOXYGEN_FULL_SIDEBAR NO) - set (DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome.css ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only.css) + set (DOXYGEN_HTML_EXTRA_STYLESHEET + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome.css + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only.css + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css + custom.css + ) + set (DOXYGEN_HTML_EXTRA_FILES + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js + ) set (DOXYGEN_FULL_SIDEBAR NO) set (DOXYGEN_HTML_COLORSTYLE "LIGHT") + set (DOXYGEN_HTML_HEADER header.html) endif() doxygen_add_docs( @@ -26,6 +36,12 @@ doxygen_add_docs( ALL COMMENT "Generate man and html pages" ) +install(DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/man + ${CMAKE_CURRENT_BINARY_DIR}/html + ${CMAKE_CURRENT_BINARY_DIR}/xml + TYPE DOC +) if (BUILD_DOXYGEN_AWESOME) add_dependencies(documentation doxygen-awesome-css) diff --git a/doc/custom.css b/doc/custom.css new file mode 100644 index 0000000..160c40a --- /dev/null +++ b/doc/custom.css @@ -0,0 +1,3 @@ +html { + --top-height: 150px; +} diff --git a/doc/header.html b/doc/header.html new file mode 100644 index 0000000..ca26eba --- /dev/null +++ b/doc/header.html @@ -0,0 +1,78 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/src/base.cpp b/src/base.cpp index 0951bd6..8952652 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -22,6 +22,20 @@ #define UNUSED(x) (void)(x) +/** + * \class LMDBAL::Base + * \brief Database abstraction + * + * This is a basic class that represents the database as a collection of storages. + * Storages is something key-value database has instead of tables in classic SQL databases. + */ + +/** + * \brief Creates the database + * + * \param[in] name - name of the database, it is going to affect folder name that is created to store data + * \param[in] mapSize - LMDB map size (MBi), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() + */ LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): name(p_name.toStdString()), opened(false), @@ -31,6 +45,9 @@ LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): transactions(new Transactions()) {} +/** + * \brief Destroys the database + */ LMDBAL::Base::~Base() { close(); @@ -40,6 +57,14 @@ LMDBAL::Base::~Base() { delete pair.second; } +/** + * \brief Closes the database + * + * Closes all lmdb handles, aborts all public transactions. + * This function will do nothing on closed database + * + * \exception LMDBAL::Unknown - thrown if something went wrong aborting transactions + */ void LMDBAL::Base::close() { if (opened) { for (LMDBAL::TransactionID id : *transactions) @@ -56,7 +81,13 @@ void LMDBAL::Base::close() { } /** - * Almost every LMDBAL::Base require it to be opened, this function opens it. It laso creates the directory for the database if it was an initial launch + * \brief Opens the database + * + * Almost every LMDBAL::Base require it to be opened, this function does it. + * It laso creates the directory for the database if it was an initial launch. + * This function will do nothing on opened database + * + * \exception LMDBAL::Unknown - thrown if something went wrong opening storages and caches */ void LMDBAL::Base::open() { if (!opened) { @@ -79,6 +110,13 @@ void LMDBAL::Base::open() { } } +/** + * \brief Removes database directory + * + * \returns true if removal was successfull of if no directory was created where it's expected to be, false otherwise + * + * \exception LMDBAL::Opened - thrown if this function was called on opened database + */ bool LMDBAL::Base::removeDirectory() { if (opened) throw Opened(name, "remove database directory"); @@ -93,6 +131,20 @@ bool LMDBAL::Base::removeDirectory() { return true; } +/** + * \brief Creates database directory + * + * Creates or opens existing directory with the given name in the location acquired with + * QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + * so, the file system destination of your data would depend on the + * QCoreApplication configuration of your app. + * This function does nothing if the directory was already created + * + * \returns the path of the created directory + * + * \exception LMDBAL::Opened - thrown if called on opened database + * \exception LMDBAL::Directory - if the database couldn't create the folder + */ QString LMDBAL::Base::createDirectory() { if (opened) throw Opened(name, "create database directory"); @@ -110,12 +162,31 @@ QString LMDBAL::Base::createDirectory() { return path; } +/** + * \brief Returns database name + * + * \returns database name + */ QString LMDBAL::Base::getName() const { return QString::fromStdString(name);} + +/** + * \brief Returns database state + * + * \returns true if the database is opened and ready for work, false otherwise + */ bool LMDBAL::Base::ready() const { return opened;} +/** + * \brief Drops the database + * + * Clears all caches and storages of the database + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if something unexpected happend + */ void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); @@ -131,18 +202,67 @@ void LMDBAL::Base::drop() { commitTransaction(txn); } +/** + * \brief Begins read-only transaction + * + * \returns read-only transaction ID + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { return beginReadOnlyTransaction(emptyName);} +/** + * \brief Begins writable transaction + * + * \returns writable transaction ID + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const { return beginTransaction(emptyName);} +/** + * \brief Aborts transaction + * + * Terminates transaction cancelling changes. + * This is an optimal way to terminate read-only transactions + * + * \param[in] id - transaction ID you want to abort + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + */ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { return abortTransaction(id, emptyName);} +/** + * \brief Commits transaction + * + * Terminates transaction applying changes. + * + * \param[in] id - transaction ID you want to commit + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + */ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) { return commitTransaction(id, emptyName);} +/** + * \brief Begins read-only transaction + * + * This function is intended to be called from subordinate storage or cache + * + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \returns read-only transaction ID + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginReadOnlyTransaction", name, storageName); @@ -155,6 +275,18 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& return txn; } +/** + * \brief Begins writable transaction + * + * This function is intended to be called from subordinate storage or cache + * + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \returns writable transaction ID + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageName) const { if (!opened) throw Closed("beginTransaction", name, storageName); @@ -167,6 +299,19 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN return txn; } +/** + * \brief Aborts transaction + * + * Terminates transaction cancelling changes. + * This is an optimal way to terminate read-only transactions. + * This function is intended to be called from subordinate storage or cache + * + * \param[in] id - transaction ID you want to abort + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + */ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); @@ -182,6 +327,18 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& transactions->erase(itr); } +/** + * \brief Commits transaction + * + * Terminates transaction applying changes. + * This function is intended to be called from subordinate storage or cache + * + * \param[in] id - transaction ID you want to commit + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \exception LMDBAL::Closed - thrown if the database is closed + * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + */ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) { if (!opened) throw Closed("abortTransaction", name, storageName); @@ -197,6 +354,18 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string transactions->erase(itr); } +/** + * \brief Begins read-only transaction + * + * This function is intended to be called from subordinate storage or cache, + * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \returns read-only transaction ID + * + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); @@ -207,6 +376,18 @@ LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::s return txn; } +/** + * \brief Begins writable transaction + * + * This function is intended to be called from subordinate storage or cache, + * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \returns writable transaction ID + * + * \exception LMDBAL::Unknown - thrown if something unexpected happened + */ LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, 0, &txn); @@ -217,11 +398,34 @@ LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& s return txn; } +/** + * \brief Aborts transaction + * + * Terminates transaction cancelling changes. + * This is an optimal way to terminate read-only transactions. + * This function is intended to be called from subordinate storage or cache, + * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * + * \param[in] id - transaction ID you want to abort + * \param[in] storageName - name of the storage/cache that you begin transaction from, unused here + */ void LMDBAL::Base::abortPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { UNUSED(storageName); mdb_txn_abort(id); } +/** + * \brief Commits transaction + * + * Terminates transaction applying changes. + * This function is intended to be called from subordinate storage or cache + * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * + * \param[in] id - transaction ID you want to commit + * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong + * + * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + */ void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std::string& storageName) { int rc = mdb_txn_commit(id); if (rc != MDB_SUCCESS) diff --git a/src/base.h b/src/base.h index 349aed4..1168b3b 100644 --- a/src/base.h +++ b/src/base.h @@ -109,7 +109,19 @@ private: #include "operators.hpp" /** - * Adds LMDBAL::Storage with the given name to the LMDBAL::Base, also returns it. The LMDBAL::Base must be closed + * \brief Adds LMDBAL::Storage to the database + * + * Defines that the database is going to have the following storage. + * The LMDBAL::Base must be closed + * + * \param[in] name - storage name + * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it + * + * \tparam K - key type of the storage + * \tparam V - value type of the storage + * + * \exception LMDBAL::Opened thrown if this method is called on the opened database + * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add storage with repeating name */ template LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { @@ -117,12 +129,27 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { throw Opened(name, "add storage " + p_name); } Storage* storage = new Storage(p_name, this); - storages.insert(std::make_pair(p_name, (iStorage*)storage)); + std::pair pair = storages.insert(std::make_pair(p_name, (iStorage*)storage)); + if (!pair.second) + throw StorageDuplicate(name, p_name); + return storage; } /** - * Adds LMDBAL::Cache with given the name to the LMDBAL::Base, also returns it. The LMDBAL::Base must be closed + * \brief Adds LMDBAL::Cache to the database + * + * Defines that the database is going to have the following cache. + * The LMDBAL::Base must be closed + * + * \param[in] name - cache name + * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it + * + * \tparam K - key type of the cache + * \tparam V - value type of the cahce + * + * \exception LMDBAL::Opened thrown if this method is called on the opened database + * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add cache with repeating name */ template LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { @@ -130,12 +157,29 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { throw Opened(name, "add cache " + p_name); } Cache* cache = new Cache(p_name, this); - storages.insert(std::make_pair(p_name, (iStorage*)cache)); + std::pair pair = storages.insert(std::make_pair(p_name, (iStorage*)cache)); + if (!pair.second) + throw StorageDuplicate(name, p_name); + return cache; } /** - * Returns LMDBAL::Storage with the given name + * \brief Returns LMDBAL::Storage handle + * + * Requested storage must have been added before opening database + * Note that template parameters is user responsibility zone! + * If user, for instance, had added storage but calling + * this method with template parameters + * on the same name of the previously added storage, or calling it on cache - the behaviour is undefined + * + * \param[in] name - storage name + * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it + * + * \tparam K - key type of the storage + * \tparam V - value type of the storage + * + * \exception std::out_of_range thrown if storage with the given name was not found */ template LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { @@ -143,7 +187,21 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { } /** - * Returns LMDBAL::Cache with the given name + * \brief Returns LMDBAL::Cache handle + * + * Requested cache must have been added before opening database + * Note that template parameters is user responsibility zone! + * If user, for instance, had added cache but calling + * this method with template parameters + * on the same name of the previously added cache, or calling it on storage - the behaviour is undefined + * + * \param[in] name - cache name + * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it + * + * \tparam K - key type of the cache + * \tparam V - value type of the cahce + * + * \exception std::out_of_range thrown if cache with the given name was not found */ template LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& p_name) { diff --git a/src/exceptions.cpp b/src/exceptions.cpp index e286ee0..c7491aa 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -82,6 +82,20 @@ std::string LMDBAL::NotFound::getMessage() const { + " in database " + dbName + " in table " + tableName;} +LMDBAL::StorageDuplicate::StorageDuplicate( + const std::string& p_dbName, + const std::string& p_tableName +): + dbName(p_dbName), + tableName(p_tableName) {} + +std::string LMDBAL::StorageDuplicate::getMessage() const { + return "An attempt to add a storage (or cache) " + tableName + + " to database " + dbName + + " but the database already has a storage with given name"; +} + + LMDBAL::Exist::Exist( const std::string& p_key, const std::string& p_dbName, diff --git a/src/exceptions.h b/src/exceptions.h index 87caecc..88eb510 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -76,6 +76,16 @@ private: std::string tableName; }; +class StorageDuplicate : public Exception { +public: + StorageDuplicate(const std::string& dbName, const std::string& tableName); + + std::string getMessage() const; +private: + std::string dbName; + std::string tableName; +}; + class Exist : public Exception { public: Exist(const std::string& key, const std::string& dbName, const std::string& tableName); diff --git a/src/storage.h b/src/storage.h index eb3a808..eb140c0 100644 --- a/src/storage.h +++ b/src/storage.h @@ -83,7 +83,7 @@ protected: protected: template - int makeTable(MDB_txn* transaction); + int makeStorage(MDB_txn* transaction); template static std::string toString(const T& value); diff --git a/src/storage.hpp b/src/storage.hpp index f46ebd8..3e9df5b 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -681,51 +681,51 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { */ template int LMDBAL::Storage::createStorage(MDB_txn* transaction) { - return makeTable(transaction); + return makeStorage(transaction); } template -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } template<> -inline int LMDBAL::iStorage::makeTable(MDB_txn* transaction) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } From 66df0da5f67afb1075ee41f58e8548d664325af8 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 11 Apr 2023 12:11:27 -0300 Subject: [PATCH 040/125] documented iStorage, hosted docs and added readme link --- README.md | 1 + src/base.cpp | 10 +-- src/base.h | 36 ++++---- src/storage.cpp | 213 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 236 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index fa4de83..7880758 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) [![AUR version](https://img.shields.io/aur/version/lmdbal?style=flat-square)](https://aur.archlinux.org/packages/lmdbal/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) +[![Documentation](https://img.shields.io/badge/Documentation-HTLM-green)](https://macaw.me/lmdbal/doc/html) ### Prerequisites diff --git a/src/base.cpp b/src/base.cpp index 8952652..01cd3c1 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -33,13 +33,13 @@ /** * \brief Creates the database * - * \param[in] name - name of the database, it is going to affect folder name that is created to store data - * \param[in] mapSize - LMDB map size (MBi), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() + * \param[in] _name - name of the database, it is going to affect folder name that is created to store data + * \param[in] _mapSize - LMDB map size (MBi), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() */ -LMDBAL::Base::Base(const QString& p_name, uint16_t mapSize): - name(p_name.toStdString()), +LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): + name(_name.toStdString()), opened(false), - size(mapSize), + size(_mapSize), environment(), storages(), transactions(new Transactions()) diff --git a/src/base.h b/src/base.h index 1168b3b..e02d06f 100644 --- a/src/base.h +++ b/src/base.h @@ -114,7 +114,7 @@ private: * Defines that the database is going to have the following storage. * The LMDBAL::Base must be closed * - * \param[in] name - storage name + * \param[in] _name - storage name * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it * * \tparam K - key type of the storage @@ -124,14 +124,14 @@ private: * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add storage with repeating name */ template -LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { +LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& _name) { if (opened) { - throw Opened(name, "add storage " + p_name); + throw Opened(name, "add storage " + _name); } - Storage* storage = new Storage(p_name, this); - std::pair pair = storages.insert(std::make_pair(p_name, (iStorage*)storage)); + Storage* storage = new Storage(_name, this); + std::pair pair = storages.insert(std::make_pair(_name, (iStorage*)storage)); if (!pair.second) - throw StorageDuplicate(name, p_name); + throw StorageDuplicate(name, _name); return storage; } @@ -142,7 +142,7 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { * Defines that the database is going to have the following cache. * The LMDBAL::Base must be closed * - * \param[in] name - cache name + * \param[in] _name - cache name * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it * * \tparam K - key type of the cache @@ -152,14 +152,14 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& p_name) { * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add cache with repeating name */ template -LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { +LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& _name) { if (opened) { - throw Opened(name, "add cache " + p_name); + throw Opened(name, "add cache " + _name); } - Cache* cache = new Cache(p_name, this); - std::pair pair = storages.insert(std::make_pair(p_name, (iStorage*)cache)); + Cache* cache = new Cache(_name, this); + std::pair pair = storages.insert(std::make_pair(_name, (iStorage*)cache)); if (!pair.second) - throw StorageDuplicate(name, p_name); + throw StorageDuplicate(name, _name); return cache; } @@ -173,7 +173,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { * this method with template parameters * on the same name of the previously added storage, or calling it on cache - the behaviour is undefined * - * \param[in] name - storage name + * \param[in] _name - storage name * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it * * \tparam K - key type of the storage @@ -182,8 +182,8 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& p_name) { * \exception std::out_of_range thrown if storage with the given name was not found */ template -LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { - return static_cast*>(storages.at(p_name)); +LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& _name) { + return static_cast*>(storages.at(_name)); } /** @@ -195,7 +195,7 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { * this method with template parameters * on the same name of the previously added cache, or calling it on storage - the behaviour is undefined * - * \param[in] name - cache name + * \param[in] _name - cache name * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it * * \tparam K - key type of the cache @@ -204,8 +204,8 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& p_name) { * \exception std::out_of_range thrown if cache with the given name was not found */ template -LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& p_name) { - return static_cast*>(storages.at(p_name)); +LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& _name) { + return static_cast*>(storages.at(_name)); } #endif //LMDBAL_BASE_H diff --git a/src/storage.cpp b/src/storage.cpp index 351ae9d..8737b6f 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -20,14 +20,36 @@ #define UNUSED(x) (void)(x) +/** + * \class LMDBAL::iStorage + * + * This is a interface-like class, it's designed to be an inner database interface to + * be used as a polymorphic entity, and provide protected interaction with the database + * from the heirs code + */ + +/** + * \brief Constructs a storage interface + */ LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): dbi(), db(parent), name(p_name) {} +/** + * \brief Destroys a storage interface + */ LMDBAL::iStorage::~iStorage() {} +/** + * \brief Drops content of a storage interface + * + * Designed to drop storage content + * + * \exception LMDBAL::Closed thrown if the database was closed + * \exception LMDBAL::Unknown thrown if something unexpected happened + */ void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); @@ -41,15 +63,38 @@ void LMDBAL::iStorage::drop() { db->commitTransaction(txn); } +/** + * \brief Drops content of a storage interface (transaction variant) + * + * Just performs content drop + * + * \param[in] transaction - transaction ID, must be writable transaction! + * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise + */ int LMDBAL::iStorage::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } +/** + * \brief Helper function, thows exception if the database is not opened + * + * \param[in] methodName - name of the method this function is called from, just for display in std::exception::what() message + * + * \exception LMDBAL::Closed thrown if the database was closed + */ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { - if (!db->opened) + if (!isDBOpened()) throw Closed(methodName, db->name, name); } +/** + * \brief Storage size + * + * \returns amount of records in the storage + * + * \exception LMDBAL::Closed thrown if the database was closed + * \exception LMDBAL::Unknown thrown if something unexpected happened + */ LMDBAL::SizeType LMDBAL::iStorage::count() const { ensureOpened(countMethodName); @@ -66,6 +111,14 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { return amount; } +/** + * \brief Storage size (transaction variant) + * + * \param[in] txn - transaction ID, can be read-only transaction + * \returns amount of records in the storage + * + * \exception LMDBAL::Unknown thrown if something unexpected happened + */ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { MDB_stat stat; int rc = mdb_stat(txn, dbi, &stat); @@ -75,16 +128,51 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { return stat.ms_entries; } +/** + * \brief Throws LMDBAL::Exist or LMDBAL::Unknown (transaction vairiant) + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * \param[in] txn - transaction ID to be aborted, any transaction + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST + * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST + */ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); throwDuplicateOrUnknown(rc, key); } +/** + * \brief Throws LMDBAL::NotFound or LMDBAL::Unknown (transaction vairiant) + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * \param[in] txn - transaction ID to be aborted, any transaction + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND + * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND + */ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { abortTransaction(txn); throwNotFoundOrUnknown(rc, key); } +/** + * \brief Throws LMDBAL::Exist or LMDBAL::Unknown + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST + * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST + */ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) const { if (rc == MDB_KEYEXIST) throwDuplicate(key); @@ -92,6 +180,17 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) c throwUnknown(rc); } +/** + * \brief Throws LMDBAL::NotFound or LMDBAL::Unknown (transaction variant) + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND + * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND + */ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) const { if (rc == MDB_NOTFOUND) throwNotFound(key); @@ -99,45 +198,157 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) co throwUnknown(rc); } +/** + * \brief Throws LMDBAL::Unknown (transaction vairiant) + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * \param[in] txn - transaction ID to be aborted, any transaction + * + * \exception LMDBAL::Unknown thrown everytime + */ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { abortTransaction(txn); throwUnknown(rc); } +/** + * \brief Database name + * + * Ment to be used in heirs, to provide some sort of interface to acces to some of the database information + * + * \returns database name + */ const std::string & LMDBAL::iStorage::dbName() const { return db->name;} +/** + * \brief Is database opened + * + * Ment to be used in heirs, to provide some sort of interface to acces to some of the database information + * + * \returns true if database is ipened, false otherwise + */ bool LMDBAL::iStorage::isDBOpened() const { return db->opened;} +/** + * \brief Throws LMDBAL::Unknown + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] rc - result of lmdb low level operation + * + * \exception LMDBAL::Unknown thrown everytime + */ void LMDBAL::iStorage::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} +/** + * \brief Throws LMDBAL::Exist + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::Exist thrown everytime + */ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { throw Exist(key, db->name, name);} +/** + * \brief Throws LMDBAL::NotFound + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] key - requested key string representation, just to show in std::exception::what() message + * + * \exception LMDBAL::NotFound thrown everytime + */ void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} +/** + * \brief Begins read-only transaction + * + * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message + * + * \returns read only transaction + */ LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { return db->beginPrivateReadOnlyTransaction(name);} +/** + * \brief Begins writable transaction + * + * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message + * + * \returns read only transaction + */ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { return db->beginPrivateTransaction(name);} +/** + * \brief Aborts transaction + * + * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message + */ void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { db->abortPrivateTransaction(id, name);} +/** + * \brief Commits transaction + * + * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message + * + * \exception LMDBAL::Unknown thrown if something unexpected happened + */ void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { db->commitPrivateTransaction(id, name);} +/** + * \brief called on beginning of public transaction + * + * This function is called on every storage of the database + * when user calls LMDBAL::Base::beginTransaction() or LMDBAL::Base::beginReadOnlyTransaction() + * + * This function is met to be reimplemented in heirs + * if the heir code requires some transaction custom handling + * + * \param[in] txn - ID of started transaction + * \param[in] readOnly - true if transaction is read-only, false otherwise + */ void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { UNUSED(txn); UNUSED(readOnly); } + +/** + * \brief called on commitment of public transaction + * + * This function is called on every storage of the database + * when user calls LMDBAL::Base::commitTransaction(LMDBAL::TransactionID) + * + * This function is met to be reimplemented in heirs + * if the heir code requires some transaction custom handling + * + * \param[in] txn - ID of started transaction + */ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { UNUSED(txn);} +/** + * \brief called on abortion of public transaction + * + * This function is called on every storage of the database + * when user calls LMDBAL::Base::abortTransaction(LMDBAL::TransactionID) + * + * This function is met to be reimplemented in heirs + * if the heir code requires some transaction custom handling + * + * \param[in] txn - ID of started transaction + */ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} From 2b4763b575781680a84323ef7355918660eefcc4 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 12 Apr 2023 12:36:33 -0300 Subject: [PATCH 041/125] 0.3.0 --- CMakeLists.txt | 2 +- README.md | 2 +- packaging/Archlinux/PKGBUILD | 4 +- src/base.cpp | 2 +- src/base.h | 20 +++++----- src/cache.h | 27 ++++++++----- src/cache.hpp | 27 ++++++++++++- src/storage.h | 32 ++++++++-------- src/storage.hpp | 74 ++++++++++++++++++++++++++++++++---- 9 files changed, 141 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c60a83e..4d719ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.2.0 + VERSION 0.3.0 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/README.md b/README.md index 7880758..9f841e6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) [![AUR version](https://img.shields.io/aur/version/lmdbal?style=flat-square)](https://aur.archlinux.org/packages/lmdbal/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -[![Documentation](https://img.shields.io/badge/Documentation-HTLM-green)](https://macaw.me/lmdbal/doc/html) +[![Documentation](https://img.shields.io/badge/Documentation-HTML-green)](https://macaw.me/lmdbal/doc/html) ### Prerequisites diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index c8451c9..f5d1431 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.2.0 +pkgver=0.3.0 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') @@ -19,5 +19,5 @@ build() { } package() { cd "$srcdir/$pkgname" - DESTDIR="$pkgdir/" cmake --install . + cmake --install . --prefix $pkgdir/ } diff --git a/src/base.cpp b/src/base.cpp index 01cd3c1..bb34bf1 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -34,7 +34,7 @@ * \brief Creates the database * * \param[in] _name - name of the database, it is going to affect folder name that is created to store data - * \param[in] _mapSize - LMDB map size (MBi), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() + * \param[in] _mapSize - LMDB map size (MiB), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() */ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): name(_name.toStdString()), diff --git a/src/base.h b/src/base.h index e02d06f..3cee862 100644 --- a/src/base.h +++ b/src/base.h @@ -46,7 +46,7 @@ class Storage; template class Cache; -typedef MDB_txn* TransactionID; +typedef MDB_txn* TransactionID; /*** getCache(const std::string& name); private: - typedef std::map Storages; - typedef std::set Transactions; + typedef std::map Storages; /** Transactions; /** class Cache : public Storage { friend class Base; - enum class Mode { //it's a cache state when we: - nothing, // - know nothing about records in database on disk - size, // - know just an amount of records - full // - shure that our cache is equal to the database on disk + enum class Mode { /**& data, TransactionID txn, bool overwrite = false) override; protected: + /** + * \brief Cache mode + * + * Sometimes we have a complete information about the content of the database, + * and we don't even need to refer to lmdb to fetch or check for data. + * This member actually shows what is the current state, more about them you can read in Enum descriptions + * + * It's done with pointer to comply some of the const method limitations which would modify this state eventually + */ Mode* mode; - std::map* cache; - std::set* abscent; - SizeType* sizeDifference; - TransactionCache* transactionCache; + std::map* cache; /**<\brief Cached data*/ + std::set* abscent; /**<\brief Set of keys that are definitely not in the cache*/ + SizeType* sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/ + TransactionCache* transactionCache; /**<\brief All changes made under under uncommited transactions*/ }; } diff --git a/src/cache.hpp b/src/cache.hpp index 93f19dd..18d8843 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -22,9 +22,29 @@ #include "cache.h" #include "exceptions.h" +/** + * \class LMDBAL::Cache + * \brief Storage with additional caching in std::map + * + * \tparam K type of the keys of the cache + * \tparam V type of the values of the cache + * + * You can receive an instance of this class calling LMDBAL::Base::addCache(const std::string&) + * if the database is yet closed and you're defining the storages you're going to need. + * Or you can call LMDBAL::Base::getCache(const std::string&) if you didn't save a pointer to the cache at first + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + +/** + * \brief Creates a cache + * + * \param[in] _name - name of the new cache + * \param[in] parent - parent database pointed (borrowed) + */ template -LMDBAL::Cache::Cache(const std::string& p_name, Base* parent): - Storage(p_name, parent), +LMDBAL::Cache::Cache(const std::string& _name, Base* parent): + Storage(_name, parent), mode(new Mode), cache(new std::map()), abscent(new std::set()), @@ -35,6 +55,9 @@ LMDBAL::Cache::Cache(const std::string& p_name, Base* parent): *sizeDifference = 0; } +/** + * \brief Destroys a cache + */ template LMDBAL::Cache::~Cache() { delete transactionCache; diff --git a/src/storage.h b/src/storage.h index eb140c0..8045d25 100644 --- a/src/storage.h +++ b/src/storage.h @@ -64,22 +64,22 @@ public: virtual SizeType count(TransactionID txn) const; protected: - MDB_dbi dbi; - Base* db; - const std::string name; + MDB_dbi dbi; /**<\brief lmdb storage handle*/ + Base* db; /**<\brief parent database pointer (borrowed)*/ + const std::string name; /**<\brief this storage name*/ - inline static const std::string dropMethodName = "drop"; - inline static const std::string countMethodName = "count"; + inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ + inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ - inline static const std::string addRecordMethodName = "addRecord"; - inline static const std::string forceRecordMethodName = "forceRecord"; - inline static const std::string changeRecordMethodName = "changeRecord"; - inline static const std::string removeRecordMethodName = "removeRecord"; - inline static const std::string checkRecordMethodName = "checkRecord"; - inline static const std::string getRecordMethodName = "getRecord"; - inline static const std::string readAllMethodName = "readAllRecord"; - inline static const std::string replaceAllMethodName = "replaceAll"; - inline static const std::string addRecordsMethodName = "addRecords"; + inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ + inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ protected: template @@ -122,8 +122,8 @@ public: virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); protected: - Serializer* keySerializer; - Serializer* valueSerializer; + Serializer* keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ + Serializer* valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ int createStorage(MDB_txn* transaction) override; }; diff --git a/src/storage.hpp b/src/storage.hpp index 3e9df5b..2dfdf1c 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -31,18 +31,27 @@ * * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&) * if the database is yet closed and you're defining the storages you're going to need. - * Or you can call LMDBAL::Base::getStorage(const std::string&) if the database is opened and you didn't save a pointer to the storage + * Or you can call LMDBAL::Base::getStorage(const std::string&) if you didn't save a pointer to the storage at first * * You are not supposed to instantiate or destory instances of this class yourself! */ +/** + * \brief Creates a storage + * + * \param[in] _name - name of the new storage + * \param[in] parent - parent database pointed (borrowed) + */ template -LMDBAL::Storage::Storage(const std::string& p_name, Base* parent): - iStorage(p_name, parent), +LMDBAL::Storage::Storage(const std::string& _name, Base* parent): + iStorage(_name, parent), keySerializer(new Serializer()), valueSerializer(new Serializer()) {} +/** + * \brief Destroys a storage + */ template LMDBAL::Storage::~Storage() { delete valueSerializer; @@ -673,57 +682,92 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { } /** - * \brief A private virtual function I need to open database + * \brief A private virtual function I need to open each storage in the database * - * This and the following collection of specializations are a way to optimise database using - * MDB_INTEGERKEY flag, when the key is actually kind of an integer - * This infrastructure also allowes us to customize mdb_dbi_open call in the future + * \param[in] transaction - lmdb transaction to call mdb_dbi_open + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code */ template int LMDBAL::Storage::createStorage(MDB_txn* transaction) { return makeStorage(transaction); } +/** + * \brief A functiion to actually open MDB_dbi storage + * + * \tparam K type of keys in opening storage + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + * + * This and the following collection of specializations are a way to optimise database using + * MDB_INTEGERKEY flag, when the key is actually kind of an integer + * This infrastructure also allowes us to customize mdb_dbi_open call in the future + */ template inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); } +/** + * \brief Opening database function specialization for uint64_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for uint32_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for uint16_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for uint8_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for int64_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for int32_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for int16_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); } +/** + * \brief Opening database function specialization for int8_t + */ template<> inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); @@ -743,11 +787,27 @@ inline std::string LMDBAL::iStorage::toString(const T& value) { return std::to_string(value); } +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * QString spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ template<> inline std::string LMDBAL::iStorage::toString(const QString& value) { return value.toStdString(); } +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * std::string spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ template<> inline std::string LMDBAL::iStorage::toString(const std::string& value) { return value; From 4975721a5cc360a223cdfff15853e5d3e6d9bb66 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 14 Apr 2023 11:44:46 -0300 Subject: [PATCH 042/125] 0.3.1 fix qt5 build, exception docs --- CHANGELOG.md | 18 +++++++++ CMakeLists.txt | 2 +- packaging/Archlinux/PKGBUILD | 6 +-- src/exceptions.h | 75 ++++++++++++++++++++++++++++++++++-- src/operators.hpp | 32 +++++++-------- 5 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a0c3db7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +## LMDBAL 0.3.1 (April 14, 2023) +### Bug fixes +- build with qt5 now is possible again + +### Improvements +- exception documentation + +## LMDBAL 0.3.0 (April 12, 2023) +### New features +- transaction functions + +### Improvements +- initial documentation +- cache unit testing +- transactions unit testing +- serialization unit testing diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d719ce..81c12f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.3.0 + VERSION 0.3.1 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index f5d1431..63457c6 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.3.0 +pkgver=0.3.1 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') @@ -11,7 +11,7 @@ makedepends=('cmake>=3.16') optdepends=() source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") -sha256sums=('SKIP') +sha256sums=('df1a9687d81d609d160754285f2613d7e07fc6deb781d0fb0084e4857ea82e95') build() { cd "$srcdir/$pkgname" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -D QT_VERSION_MAJOR=5 @@ -19,5 +19,5 @@ build() { } package() { cd "$srcdir/$pkgname" - cmake --install . --prefix $pkgdir/ + DESTDIR="$pkgdir/" cmake --install . } diff --git a/src/exceptions.h b/src/exceptions.h index 88eb510..c583c9b 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -25,18 +25,29 @@ namespace LMDBAL { +/** + * \brief Parent abstract class for all LMDBAL exceptions + */ class Exception : public std::exception { public: Exception(); virtual ~Exception(); - virtual std::string getMessage() const = 0; + virtual std::string getMessage() const = 0; /**<\brief returns exception message*/ - const char* what() const noexcept( true ); + const char* what() const noexcept( true ) override; }; +/** + * \brief Thrown if LMDBAL had issues creating or opening database directory + */ class Directory: public Exception { public: + /** + * \brief Creates exception + * + * \param path - path of the directory that was supposed to be used to store the database + */ Directory(const std::string& path); std::string getMessage() const; @@ -44,9 +55,19 @@ private: std::string path; }; +/** + * \brief Thrown if something in the database was called on closed state and it is not supported + */ class Closed : public Exception { public: - Closed(const std::string& p_operation, const std::string& dbName, const std::optional& tableName = std::nullopt); + /** + * \brief Creates exception + * + * \param operation - text name of the method that was called on closed database + * \param dbName - name of the database + * \param tableName - name of the storage which called that method, abscent if it's untracable or if it's thrown by the database + */ + Closed(const std::string& operation, const std::string& dbName, const std::optional& tableName = std::nullopt); std::string getMessage() const; private: @@ -55,8 +76,17 @@ private: std::optional tableName; }; +/** + * \brief Thrown if something in the database was called on opened state and it is not supported + */ class Opened : Exception { public: + /** + * \brief Creates exception + * + * \param action - text name of the method that was called on opened database + * \param dbName - name of the database + */ Opened(const std::string& dbName, const std::string& action); std::string getMessage() const; @@ -65,8 +95,18 @@ private: std::string action; }; +/** + * \brief Thrown if something in the database was not found + */ class NotFound : public Exception { public: + /** + * \brief Creates exception + * + * \param key - record key that was not found + * \param dbName - name of the database + * \param tableName - name of the storage that was looked for a record + */ NotFound(const std::string& key, const std::string& dbName, const std::string& tableName); std::string getMessage() const; @@ -76,8 +116,17 @@ private: std::string tableName; }; +/** + * \brief Thrown if there was attempt to define storages with conflicting names + */ class StorageDuplicate : public Exception { public: + /** + * \brief Creates exception + * + * \param dbName - name of the database + * \param tableName - that name that was conflicting + */ StorageDuplicate(const std::string& dbName, const std::string& tableName); std::string getMessage() const; @@ -86,8 +135,18 @@ private: std::string tableName; }; +/** + * \brief Thrown if there was a key conflict in one of the storages + */ class Exist : public Exception { public: + /** + * \brief Creates exception + * + * \param key - record key that caused the conflict + * \param dbName - name of the database + * \param tableName - name of the storage that was operated with + */ Exist(const std::string& key, const std::string& dbName, const std::string& tableName); std::string getMessage() const; @@ -97,8 +156,18 @@ private: std::string tableName; }; +/** + * \brief Thrown if something unexpected happened + */ class Unknown : public Exception { public: + /** + * \brief Creates exception + * + * \param message - text description of the error, most of the times contains the result of mdb_strerror + * \param dbName - name of the database + * \param tableName - name of the storage that was operated with, abscent if the operation was with the database itself + */ Unknown(const std::string& dbName, const std::string& message, const std::optional& tableName = std::nullopt); std::string getMessage() const; diff --git a/src/operators.hpp b/src/operators.hpp index 8adea66..80353ac 100644 --- a/src/operators.hpp +++ b/src/operators.hpp @@ -75,21 +75,23 @@ QDataStream& operator >> (QDataStream &in, std::multimap& container) { return in; } -// template -// QDataStream& operator << (QDataStream &out, const std::pair& pair) { -// out << pair.first; -// out << pair.second; -// -// return out; -// } -// -// template -// QDataStream& operator >> (QDataStream &in, std::pair& container) { -// in >> container.first; -// in >> container.second; -// -// return in; -// } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +template +QDataStream& operator << (QDataStream &out, const std::pair& pair) { + out << pair.first; + out << pair.second; + + return out; +} + +template +QDataStream& operator >> (QDataStream &in, std::pair& container) { + in >> container.first; + in >> container.second; + + return in; +} +#endif template QDataStream& operator << (QDataStream &out, const std::set& container) { From 69bf1fcc3d79840257dce14dcd641bcf7aebaee8 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 5 Aug 2023 17:13:43 -0300 Subject: [PATCH 043/125] replaced some pointers to mutables, some first thoughts about cursors --- src/CMakeLists.txt | 2 + src/base.cpp | 11 ++-- src/cache.h | 8 ++- src/cache.hpp | 123 +++++++++++++++++++++------------------------ src/cursor.h | 50 ++++++++++++++++++ src/cursor.hpp | 41 +++++++++++++++ src/storage.cpp | 8 +++ src/storage.h | 21 ++++++-- src/storage.hpp | 69 ++++++++++++++++--------- 9 files changed, 229 insertions(+), 104 deletions(-) create mode 100644 src/cursor.h create mode 100644 src/cursor.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b0d5ad..7d88adf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,8 @@ set(HEADERS exceptions.h storage.h storage.hpp + cursor.h + cursor.hpp cache.h cache.hpp serializer.h diff --git a/src/base.cpp b/src/base.cpp index bb34bf1..04040fc 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -67,13 +67,12 @@ LMDBAL::Base::~Base() { */ void LMDBAL::Base::close() { if (opened) { - for (LMDBAL::TransactionID id : *transactions) + for (const LMDBAL::TransactionID id : *transactions) abortTransaction(id, emptyName); - for (const std::pair& pair : storages) { - iStorage* storage = pair.second; - mdb_dbi_close(environment, storage->dbi); - } + for (const std::pair& pair : storages) + pair.second->close(); + mdb_env_close(environment); transactions->clear(); opened = false; @@ -101,7 +100,7 @@ void LMDBAL::Base::open() { TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { iStorage* storage = pair.second; - int rc = storage->createStorage(txn); + int rc = storage->open(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); } diff --git a/src/cache.h b/src/cache.h index 0f4f814..e6342cf 100644 --- a/src/cache.h +++ b/src/cache.h @@ -105,15 +105,13 @@ protected: * \brief Cache mode * * Sometimes we have a complete information about the content of the database, - * and we don't even need to refer to lmdb to fetch or check for data. + * and we don't even need to call lmdb to fetch or check for data. * This member actually shows what is the current state, more about them you can read in Enum descriptions - * - * It's done with pointer to comply some of the const method limitations which would modify this state eventually */ - Mode* mode; + mutable Mode mode; std::map* cache; /**<\brief Cached data*/ std::set* abscent; /**<\brief Set of keys that are definitely not in the cache*/ - SizeType* sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/ + mutable SizeType sizeDifference; /**<\brief Difference of size between cached data and amount of records in the lmdb storage*/ TransactionCache* transactionCache; /**<\brief All changes made under under uncommited transactions*/ }; diff --git a/src/cache.hpp b/src/cache.hpp index 18d8843..6563d60 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -45,15 +45,11 @@ template LMDBAL::Cache::Cache(const std::string& _name, Base* parent): Storage(_name, parent), - mode(new Mode), + mode(Mode::nothing), cache(new std::map()), abscent(new std::set()), - sizeDifference(new uint32_t), - transactionCache(new TransactionCache) -{ - *mode = Mode::nothing; - *sizeDifference = 0; -} + sizeDifference(0), + transactionCache(new TransactionCache) {} /** * \brief Destroys a cache @@ -61,8 +57,6 @@ LMDBAL::Cache::Cache(const std::string& _name, Base* parent): template LMDBAL::Cache::~Cache() { delete transactionCache; - delete sizeDifference; - delete mode; delete cache; delete abscent; } @@ -98,7 +92,7 @@ template void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { cache->insert(std::make_pair(key, value)); - if (*mode != Mode::full) + if (mode != Mode::full) abscent->erase(key); } @@ -129,7 +123,7 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value, TransactionI template void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool added) { - if (*mode == Mode::full) { + if (mode == Mode::full) { (*cache)[key] = value; } else { if (added) @@ -148,7 +142,7 @@ template void LMDBAL::Cache::changeRecord(const K& key, const V& value) { iStorage::ensureOpened(iStorage::changeRecordMethodName); - if (*mode == Mode::full) { + if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) iStorage::throwNotFound(iStorage::toString(key)); @@ -178,7 +172,7 @@ template void LMDBAL::Cache::changeRecord(const K& key, const V& value, TransactionID txn) { iStorage::ensureOpened(iStorage::changeRecordMethodName); - if (*mode == Mode::full) { + if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) iStorage::throwNotFound(iStorage::toString(key)); @@ -205,7 +199,7 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value, Transaction template void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { - if (*mode == Mode::full) { + if (mode == Mode::full) { cache->at(key) = value; } else { typename std::pair::iterator, bool> res = @@ -236,7 +230,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { return; } - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) iStorage::throwNotFound(iStorage::toString(key)); try { @@ -245,7 +239,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { handleMode(); return; } catch (const NotFound& error) { - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); throw error; @@ -338,7 +332,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con return; } - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) iStorage::throwNotFound(iStorage::toString(key)); try { @@ -349,7 +343,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } return; } catch (const NotFound& error) { - if (! currentTransaction && *mode != Mode::full) + if (!currentTransaction && mode != Mode::full) abscent->insert(key); throw error; @@ -364,7 +358,7 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { if (itr != cache->end()) return true; - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) return false; try { @@ -373,7 +367,7 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { handleMode(); return true; } catch (const NotFound& error) { - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); return false; @@ -439,7 +433,7 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { if (itr != cache->end()) return true; - if (*mode == Mode::full || abscent->count(key) != 0) + if (mode == Mode::full || abscent->count(key) != 0) return false; try { @@ -450,7 +444,7 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { } return true; } catch (const NotFound& error) { - if (!currentTransaction && *mode != Mode::full) + if (!currentTransaction && mode != Mode::full) abscent->insert(key); return false; @@ -461,11 +455,11 @@ template std::map LMDBAL::Cache::readAll() const { iStorage::ensureOpened(iStorage::readAllMethodName); - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values *cache = Storage::readAll(); //that are missing in the cache abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } return *cache; @@ -475,12 +469,12 @@ template void LMDBAL::Cache::readAll(std::map& out) const { iStorage::ensureOpened(iStorage::readAllMethodName); - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values Storage::readAll(out); //that are missing in the cache *cache = out; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -501,7 +495,7 @@ void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const typename TransactionCache::iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { Queue& queue = tc->second; - if (*mode != Mode::full) { + if (mode != Mode::full) { out = *cache; for (typename Queue::const_iterator i = queue.begin(), end = queue.end(); i != end; ++i) { @@ -552,12 +546,12 @@ void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const queue.emplace_back(Operation::replace, new std::map(out)); //I can as well erase all previous cache entries } } else { - if (*mode != Mode::full) { //there is a room for optimization - *mode = Mode::full; //I can read and deserialize only those values + if (mode != Mode::full) { //there is a room for optimization + mode = Mode::full; //I can read and deserialize only those values Storage::readAll(out); //that are missing in the cache *cache = out; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } } @@ -567,10 +561,10 @@ void LMDBAL::Cache::replaceAll(const std::map& data) { Storage::replaceAll(data); *cache = data; - if (*mode != Mode::full) { - *mode = Mode::full; + if (mode != Mode::full) { + mode = Mode::full; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -591,10 +585,10 @@ void LMDBAL::Cache::handleReplaceAll(std::map* data) { delete cache; cache = data; - if (*mode != Mode::full) { - *mode = Mode::full; + if (mode != Mode::full) { + mode = Mode::full; abscent->clear(); - *sizeDifference = 0; + sizeDifference = 0; } } @@ -622,9 +616,8 @@ LMDBAL::SizeType LMDBAL::Cache::addRecords(const std::map& data, Tra template void LMDBAL::Cache::handleAddRecords(const std::map& data, bool overwrite, SizeType newSize) { - Mode& m = *mode; - if (m == Mode::nothing) - m = Mode::size; + if (mode == Mode::nothing) + mode = Mode::size; std::map& c = *cache; std::set& a = *abscent; @@ -633,15 +626,15 @@ void LMDBAL::Cache::handleAddRecords(const std::map& data, bool over if (!res.second) { if (overwrite) res.first->second = pair.second; - } else if (m != Mode::full) { + } else if (mode != Mode::full) { a.erase(pair.first); } } - if (m != Mode::full) { - *sizeDifference = newSize - c.size(); - if (*sizeDifference == 0) { - *mode = Mode::full; + if (mode != Mode::full) { + sizeDifference = newSize - c.size(); + if (sizeDifference == 0) { + mode = Mode::full; abscent->clear(); } } @@ -652,7 +645,7 @@ void LMDBAL::Cache::removeRecord(const K& key) { iStorage::ensureOpened(iStorage::removeRecordMethodName); bool noKey = false; - if (*mode != Mode::full) + if (mode != Mode::full) noKey = cache->count(key) == 0; else noKey = abscent->count(key) > 0; @@ -669,7 +662,7 @@ void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { iStorage::ensureOpened(iStorage::removeRecordMethodName); bool noKey = false; - if (*mode != Mode::full) + if (mode != Mode::full) noKey = cache->count(key) == 0; else noKey = abscent->count(key) > 0; @@ -689,27 +682,27 @@ void LMDBAL::Cache::handleRemoveRecord(const K& key) { if (cache->erase(key) == 0) //if it was not cached and we are now in size mode then the sizeDifference would decrease handleMode(); - if (*mode != Mode::full) + if (mode != Mode::full) abscent->insert(key); } template uint32_t LMDBAL::Cache::count() const { - switch (*mode) { + switch (mode) { case Mode::nothing: { uint32_t sz = Storage::count(); - *sizeDifference = sz - cache->size(); + sizeDifference = sz - cache->size(); if (sz == 0) { - *mode = Mode::full; + mode = Mode::full; abscent->clear(); } else { - *mode = Mode::size; + mode = Mode::size; } return sz; } case Mode::size: - return cache->size() + *sizeDifference; + return cache->size() + sizeDifference; case Mode::full: return cache->size(); default: @@ -755,22 +748,22 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { } } - switch (*mode) { + switch (mode) { case Mode::nothing: { uint32_t sz = Storage::count(txn); if (!currentTransaction) { - *sizeDifference = sz - cache->size(); + sizeDifference = sz - cache->size(); if (sz == 0) { - *mode = Mode::full; + mode = Mode::full; abscent->clear(); } else { - *mode = Mode::size; + mode = Mode::size; } } return sz; } case Mode::size: - return cache->size() + *sizeDifference + diff; + return cache->size() + sizeDifference + diff; case Mode::full: return cache->size() + diff; default: @@ -780,10 +773,10 @@ uint32_t LMDBAL::Cache::count(TransactionID txn) const { template void LMDBAL::Cache::handleMode() const { - if (*mode == Mode::size) { - --(*sizeDifference); - if (*sizeDifference == 0) { - *mode = Mode::full; + if (mode == Mode::size) { + --sizeDifference; + if (sizeDifference == 0) { + mode = Mode::full; abscent->clear(); } } @@ -804,8 +797,8 @@ template void LMDBAL::Cache::handleDrop() { cache->clear(); abscent->clear(); - *mode = Mode::full; - *sizeDifference = 0; + mode = Mode::full; + sizeDifference = 0; } template diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 0000000..7322248 --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,50 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LMDBAL_CURSOR_H +#define LMDBAL_CURSOR_H + +#include "base.h" +#include "storage.h" + +namespace LMDBAL { + +template +class Cursor { + friend class Storage; +private: + Cursor(Storage* parent); + ~Cursor(); + +public: + void open(); + void close(); + +private: + void terminated(); + +private: + Storage* storage; +}; + +}; + + +#include "cursor.hpp" + +#endif //LMDBAL_CURSOR_H diff --git a/src/cursor.hpp b/src/cursor.hpp new file mode 100644 index 0000000..68f8497 --- /dev/null +++ b/src/cursor.hpp @@ -0,0 +1,41 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LMDBAL_CURSOR_HPP +#define LMDBAL_CURSOR_HPP + +#include "cursor.h" + +template +LMDBAL::Cursor::Cursor(Storage* parent): + storage(parent) +{ + +} + +template +LMDBAL::Cursor::~Cursor () { + +} + +template +void LMDBAL::Cursor::terminated () { + +} + +#endif //LMDBAL_CURSOR_HPP diff --git a/src/storage.cpp b/src/storage.cpp index 8737b6f..88a1f1f 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -42,6 +42,14 @@ LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): */ LMDBAL::iStorage::~iStorage() {} +/** + * \brief A private virtual function I need to close each storage in the database + */ +void LMDBAL::iStorage::close() { + mdb_dbi_close(db->environment, dbi); +} + + /** * \brief Drops content of a storage interface * diff --git a/src/storage.h b/src/storage.h index 8045d25..e52667b 100644 --- a/src/storage.h +++ b/src/storage.h @@ -21,6 +21,7 @@ #include "base.h" #include "serializer.h" +#include "cursor.h" namespace LMDBAL { @@ -34,7 +35,8 @@ protected: iStorage(const std::string& name, Base* parent); virtual ~iStorage(); - virtual int createStorage(MDB_txn * transaction) = 0; + virtual int open(MDB_txn * transaction) = 0; + virtual void close(); bool isDBOpened() const; const std::string& dbName() const; @@ -89,9 +91,13 @@ protected: static std::string toString(const T& value); }; +// template +// class Cursor; + template class Storage : public iStorage { friend class Base; + friend class Cursor; protected: Storage(const std::string& name, Base* parent); ~Storage() override; @@ -121,11 +127,16 @@ public: virtual uint32_t addRecords(const std::map& data, bool overwrite = false); virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); -protected: - Serializer* keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ - Serializer* valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ + Cursor* createCursor(); + void destroyCursor(Cursor* cursor); - int createStorage(MDB_txn* transaction) override; +protected: + mutable Serializer keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ + mutable Serializer valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ + std::set*> cursors; /**<\brief a set of cursors that has been created under this storage*/ + + int open(MDB_txn* transaction) override; + void close() override; }; } diff --git a/src/storage.hpp b/src/storage.hpp index 2dfdf1c..f14fa5e 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -45,18 +45,16 @@ template LMDBAL::Storage::Storage(const std::string& _name, Base* parent): iStorage(_name, parent), - keySerializer(new Serializer()), - valueSerializer(new Serializer()) + keySerializer(), + valueSerializer(), + cursors() {} /** * \brief Destroys a storage */ template -LMDBAL::Storage::~Storage() { - delete valueSerializer; - delete keySerializer; -} +LMDBAL::Storage::~Storage() {} /** * \brief Adds a key-value record to the storage @@ -104,8 +102,8 @@ template void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(addRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData = valueSerializer.setData(value); int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc != MDB_SUCCESS) @@ -158,7 +156,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio ensureOpened(forceRecordMethodName); bool added; - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); @@ -174,7 +172,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio throwUnknown(rc); } - lmdbData = valueSerializer->setData(value); + lmdbData = valueSerializer.setData(value); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); if (rc != MDB_SUCCESS) throwUnknown(rc); @@ -234,12 +232,12 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti if (rc != MDB_SUCCESS) throwUnknown(rc); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_val lmdbData = valueSerializer.setData(value); rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) @@ -339,14 +337,14 @@ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { ensureOpened(getRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); - valueSerializer->deserialize(lmdbData, value); + valueSerializer.deserialize(lmdbData, value); } /** @@ -392,7 +390,7 @@ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { ensureOpened(checkRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); @@ -495,9 +493,9 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); while (rc == MDB_SUCCESS) { K key; - keySerializer->deserialize(lmdbKey, key); + keySerializer.deserialize(lmdbKey, key); V& value = result[key]; - valueSerializer->deserialize(lmdbData, value); + valueSerializer.deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } mdb_cursor_close(cursor); @@ -553,8 +551,8 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID MDB_val lmdbKey, lmdbData; for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); + lmdbKey = keySerializer.setData(pair.first); + lmdbData = valueSerializer.setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here? if (rc != MDB_SUCCESS) @@ -612,8 +610,8 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti MDB_val lmdbKey, lmdbData; int rc; for (const std::pair& pair : data) { - lmdbKey = keySerializer->setData(pair.first); - lmdbData = valueSerializer->setData(pair.second); + lmdbKey = keySerializer.setData(pair.first); + lmdbData = valueSerializer.setData(pair.second); rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); if (rc == MDB_KEYEXIST) @@ -675,7 +673,7 @@ template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { ensureOpened(removeRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); + MDB_val lmdbKey = keySerializer.setData(key); int rc = mdb_del(txn, dbi, &lmdbKey, NULL); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); @@ -688,10 +686,35 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code */ template -int LMDBAL::Storage::createStorage(MDB_txn* transaction) { +int LMDBAL::Storage::open(MDB_txn* transaction) { return makeStorage(transaction); } +/** + * \brief A private virtual function I need to close each storage in the database + */ +template +void LMDBAL::Storage::close() { + for (Cursor* cursor : cursors) + cursor->terminated(); + + iStorage::close(); +} + +template +LMDBAL::Cursor* LMDBAL::Storage::createCursor() { + Cursor* cursor = new Cursor(this); + cursors.insert(cursor); + + return cursor; +} + +template +void LMDBAL::Storage::destroyCursor(Cursor* cursor) { + cursors.erase(cursor); + delete cursor; +} + /** * \brief A functiion to actually open MDB_dbi storage * From 5fba60f7f030b831f7a2c2062dca223f3c5cf7ad Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 7 Aug 2023 18:27:44 -0300 Subject: [PATCH 044/125] some thoughts about cursors, nothing special yet --- src/cursor.h | 36 ++++++++++++++-- src/cursor.hpp | 109 ++++++++++++++++++++++++++++++++++++++++++++++-- src/storage.h | 5 +-- src/storage.hpp | 5 ++- 4 files changed, 144 insertions(+), 11 deletions(-) diff --git a/src/cursor.h b/src/cursor.h index 7322248..36ea7fa 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -19,6 +19,9 @@ #ifndef LMDBAL_CURSOR_H #define LMDBAL_CURSOR_H +#include + +#include "lmdb.h" #include "base.h" #include "storage.h" @@ -28,18 +31,45 @@ template class Cursor { friend class Storage; private: + enum State { /*** parent); ~Cursor(); public: - void open(); - void close(); + void open() const; + void open(TransactionID txn) const; + void renew() const; + void renew(TransactionID txn) const; + void close() const; + + std::pair first() const; + std::pair last() const; + std::pair next() const; + std::pair prev() const; + std::pair current() const; + + void first(std::pair& out) const; + void last(std::pair& out) const; + void next(std::pair& out) const; + void prev(std::pair& out) const; + void current(std::pair& out) const; private: - void terminated(); + void terminated() const; private: Storage* storage; + mutable MDB_cursor* cursor; + mutable State state; + + inline static const std::string openRecordMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ + inline static const std::string closeRecordMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ + inline static const std::string renewRecordMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ }; }; diff --git a/src/cursor.hpp b/src/cursor.hpp index 68f8497..6a575ad 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -23,19 +23,122 @@ template LMDBAL::Cursor::Cursor(Storage* parent): - storage(parent) + storage(parent), + cursor(nullptr), + state(closed) { } template LMDBAL::Cursor::~Cursor () { - + close(); } template -void LMDBAL::Cursor::terminated () { +void LMDBAL::Cursor::terminated () const { + close(); //for now it's the same, but if I ever going to make writable cursor - here is where it's gonna be different +} +template +void LMDBAL::Cursor::open () const { + storage->ensureOpened(openRecordMethodName); + switch (state) { + case closed: { + TransactionID txn = storage->beginReadOnlyTransaction(); + int result = mdb_cursor_open(txn, storage->dbi, &cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result, txn); + + storage->transactionStarted(txn, true); + state = openedPrivate; + } break; + default: + break; + } +} + +template +void LMDBAL::Cursor::open (TransactionID txn) const { + storage->ensureOpened(openRecordMethodName); + switch (state) { + case closed: { + int result = mdb_cursor_open(txn, storage->dbi, &cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + state = openedPublic; + } break; + default: + break; + } +} + +template +void LMDBAL::Cursor::renew () const { + storage->ensureOpened(openRecordMethodName); + switch (state) { + case openedPrivate: { + TransactionID txn = mdb_cursor_txn(cursor); + storage->abortTransaction(txn); + storage->transactionAborted(txn); + [[fallthrough]]; + } + case openedPublic: { + TransactionID txn = storage->beginReadOnlyTransaction(); + int result = mdb_cursor_renew(txn, cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result, txn); + + storage->transactionStarted(txn, true); + state = openedPrivate; + } break; + default: + break; + } +} + +template +void LMDBAL::Cursor::renew (TransactionID txn) const { + storage->ensureOpened(openRecordMethodName); + switch (state) { + case openedPrivate: { + TransactionID txn = mdb_cursor_txn(cursor); + storage->abortTransaction(txn); + storage->transactionAborted(txn); + [[fallthrough]]; + } + case openedPublic: { + int result = mdb_cursor_renew(txn, cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + state = openedPublic; + } break; + default: + break; + } +} + +template +void LMDBAL::Cursor::close () const { + switch (state) { + case openedPublic: { + mdb_cursor_close(cursor); + + state = closed; + } break; + case openedPrivate: { + TransactionID txn = mdb_cursor_txn(cursor); + mdb_cursor_close(cursor); + storage->abortTransaction(txn); + storage->transactionAborted(txn); + + state = closed; + } break; + default: + break; + } } #endif //LMDBAL_CURSOR_HPP diff --git a/src/storage.h b/src/storage.h index e52667b..bfac49c 100644 --- a/src/storage.h +++ b/src/storage.h @@ -91,9 +91,6 @@ protected: static std::string toString(const T& value); }; -// template -// class Cursor; - template class Storage : public iStorage { friend class Base; @@ -133,7 +130,7 @@ public: protected: mutable Serializer keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ mutable Serializer valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ - std::set*> cursors; /**<\brief a set of cursors that has been created under this storage*/ + std::set*> cursors; /**<\brief a set of cursors that has been created under this storage*/ int open(MDB_txn* transaction) override; void close() override; diff --git a/src/storage.hpp b/src/storage.hpp index f14fa5e..58e3dee 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -54,7 +54,10 @@ LMDBAL::Storage::Storage(const std::string& _name, Base* parent): * \brief Destroys a storage */ template -LMDBAL::Storage::~Storage() {} +LMDBAL::Storage::~Storage() { + for (Cursor* cursor : cursors) + delete cursor; +} /** * \brief Adds a key-value record to the storage From 8cb1e97e3093858a66ff229ccb2054b2a43ea33c Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 9 Aug 2023 14:41:15 -0300 Subject: [PATCH 045/125] finally methods that actually do something, some testing of them --- src/cursor.h | 29 +++++-- src/cursor.hpp | 94 ++++++++++++++++++++++- src/exceptions.cpp | 17 +++++ src/exceptions.h | 21 ++++++ src/storage.cpp | 12 +++ src/storage.h | 4 + src/storage.hpp | 15 ++++ test/CMakeLists.txt | 1 + test/storagecursor.cpp | 167 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 348 insertions(+), 12 deletions(-) create mode 100644 test/storagecursor.cpp diff --git a/src/cursor.h b/src/cursor.h index 36ea7fa..c9f46aa 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -53,23 +53,36 @@ public: std::pair prev() const; std::pair current() const; - void first(std::pair& out) const; - void last(std::pair& out) const; - void next(std::pair& out) const; - void prev(std::pair& out) const; - void current(std::pair& out) const; + void first(K& key, V& value) const; + void last(K& key, V& value) const; + void next(K& key, V& value) const; + void prev(K& key, V& value) const; + void current(K& key, V& value) const; private: void terminated() const; + void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; private: Storage* storage; mutable MDB_cursor* cursor; mutable State state; - inline static const std::string openRecordMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ - inline static const std::string closeRecordMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ - inline static const std::string renewRecordMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ + inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ + inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ + inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions*/ + inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions*/ + inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/ + inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/ + inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/ + inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/ + inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions*/ + inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions*/ + inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions*/ }; }; diff --git a/src/cursor.hpp b/src/cursor.hpp index 6a575ad..57d3a42 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -42,7 +42,7 @@ void LMDBAL::Cursor::terminated () const { template void LMDBAL::Cursor::open () const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(openCursorMethodName); switch (state) { case closed: { TransactionID txn = storage->beginReadOnlyTransaction(); @@ -60,7 +60,7 @@ void LMDBAL::Cursor::open () const { template void LMDBAL::Cursor::open (TransactionID txn) const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(openCursorMethodName); switch (state) { case closed: { int result = mdb_cursor_open(txn, storage->dbi, &cursor); @@ -76,7 +76,7 @@ void LMDBAL::Cursor::open (TransactionID txn) const { template void LMDBAL::Cursor::renew () const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { TransactionID txn = mdb_cursor_txn(cursor); @@ -100,7 +100,7 @@ void LMDBAL::Cursor::renew () const { template void LMDBAL::Cursor::renew (TransactionID txn) const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { TransactionID txn = mdb_cursor_txn(cursor); @@ -141,4 +141,90 @@ void LMDBAL::Cursor::close () const { } } +template +void LMDBAL::Cursor::first (K& key, V& value) const { + operateCursorRead(key, value, MDB_FIRST, firstMethodName, firstOperationName); +} + +template +void LMDBAL::Cursor::last (K& key, V& value) const { + operateCursorRead(key, value, MDB_LAST, lastMethodName, lastOperationName); +} + +template +void LMDBAL::Cursor::next (K& key, V& value) const { + operateCursorRead(key, value, MDB_NEXT, nextMethodName, nextOperationName); +} + +template +void LMDBAL::Cursor::prev (K& key, V& value) const { + operateCursorRead(key, value, MDB_PREV, prevMethodName, prevOperationName); +} + +template +void LMDBAL::Cursor::current (K& key, V& value) const { + operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName); +} + +template +std::pair LMDBAL::Cursor::first () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_FIRST, firstMethodName, firstOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::last () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_LAST, lastMethodName, lastOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::next () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_NEXT, nextMethodName, nextOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::prev () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_PREV, prevMethodName, prevOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::current () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_GET_CURRENT, currentMethodName, currentOperationName); + return result; +} + + +template +void LMDBAL::Cursor::operateCursorRead( + K& key, + V& value, + MDB_cursor_op operation, + const std::string& methodName, + const std::string& operationName +) const { + if (state == closed) + storage->throwCursorNotReady(methodName); + + MDB_val mdbKey, mdbValue; + int result = mdb_cursor_get(cursor, &mdbKey, &mdbValue, operation); + if (result != MDB_SUCCESS) + storage->throwNotFoundOrUnknown(result, operationName); + + storage->keySerializer.deserialize(mdbKey, key); + storage->valueSerializer.deserialize(mdbValue, value); + + if (state == openedPrivate) + storage->discoveredRecord(key, value); + else + storage->discoveredRecord(key, value, mdb_cursor_txn(cursor)); +} + #endif //LMDBAL_CURSOR_HPP diff --git a/src/exceptions.cpp b/src/exceptions.cpp index c7491aa..b401511 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -55,6 +55,23 @@ std::string LMDBAL::Closed::getMessage() const { return msg; } +LMDBAL::CursorNotReady::CursorNotReady( + const std::string& p_operation, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + operation(p_operation), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string LMDBAL::CursorNotReady::getMessage() const { + std::string msg = "An attempt to perform operation " + operation + + " on closed cursor that belongs to the table " + tableName + + " within database " + dbName; + return msg; +} + LMDBAL::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), diff --git a/src/exceptions.h b/src/exceptions.h index c583c9b..8a90690 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -76,6 +76,27 @@ private: std::optional tableName; }; +/** + * \brief Thrown if the cursor was operated in closed state + */ +class CursorNotReady : public Exception { +public: + /** + * \brief Creates exception + * + * \param operation - text name of the method that was called on closed cursor + * \param dbName - name of the database + * \param tableName - name of the storage owning the cursor + */ + CursorNotReady(const std::string& operation, const std::string& dbName, const std::string& tableName); + + std::string getMessage() const; +private: + std::string operation; + std::string dbName; + std::string tableName; +}; + /** * \brief Thrown if something in the database was called on opened state and it is not supported */ diff --git a/src/storage.cpp b/src/storage.cpp index 88a1f1f..124dab8 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -277,6 +277,18 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} +/** + * \brief Throws LMDBAL::CursorNotReady + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] method - called cursor method name, just to show in std::exception::what() message + * + * \exception LMDBAL::CursorNotReady thrown everytime + */ +void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { + throw CursorNotReady(method, db->name, name);} + /** * \brief Begins read-only transaction * diff --git a/src/storage.h b/src/storage.h index bfac49c..0a7d645 100644 --- a/src/storage.h +++ b/src/storage.h @@ -50,6 +50,7 @@ protected: void throwUnknown(int rc) const; void throwDuplicate(const std::string& key) const; void throwNotFound(const std::string& key) const; + void throwCursorNotReady(const std::string& method) const; TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; @@ -99,6 +100,9 @@ protected: Storage(const std::string& name, Base* parent); ~Storage() override; + virtual void discoveredRecord(const K& key, const V& value) const; + virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const; + public: using iStorage::drop; virtual void addRecord(const K& key, const V& value); diff --git a/src/storage.hpp b/src/storage.hpp index 58e3dee..cd83f5a 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -22,6 +22,8 @@ #include "storage.h" #include "exceptions.h" +#define UNUSED(x) (void)(x) + /** * \class LMDBAL::Storage * \brief This is a basic key value storage. @@ -718,6 +720,19 @@ void LMDBAL::Storage::destroyCursor(Cursor* cursor) { delete cursor; } +template +void LMDBAL::Storage::discoveredRecord(const K& key, const V& value) const { + UNUSED(key); + UNUSED(value); +} + +template +void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, TransactionID txn) const { + UNUSED(key); + UNUSED(value); + UNUSED(txn); +} + /** * \brief A functiion to actually open MDB_dbi storage * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 43480dd..8e51951 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(runUnitTests serialization.cpp storagetransaction.cpp cachetransaction.cpp + storagecursor.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp new file mode 100644 index 0000000..e7b1f8d --- /dev/null +++ b/test/storagecursor.cpp @@ -0,0 +1,167 @@ +#include + +#include "base.h" +#include "storage.h" +#include "cursor.h" + +class StorageCursorTest : public ::testing::Test { +protected: + StorageCursorTest(): + ::testing::Test(), + table (db->getStorage("table1")) {} + + ~StorageCursorTest() {} + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("testBase"); + db->addStorage("table1"); + db->open(); + } + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static LMDBAL::Base* db; + static LMDBAL::Cursor* cursor; + + LMDBAL::Storage* table; +}; + +LMDBAL::Base* StorageCursorTest::db = nullptr; +LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; + +static const std::map data({ + {245665783, "bothering nerds"}, + {3458, "resilent pick forefront"}, + {105190, "apportunity legal bat"}, + {6510, "outside"}, + {7438537, "damocles plush apparently rusty"}, + {19373572, "local guidence"}, + {138842, "forgetting tusks prepare"}, + {981874, "butchered soaking pawn"}, + {19302, "tanned inmate"}, + {178239, "custody speaks neurotic"}, +}); + +TEST_F(StorageCursorTest, PopulatingTheTable) { + + uint32_t amount = table->addRecords(data); + EXPECT_EQ(amount, data.size()); +} + +TEST_F(StorageCursorTest, Creation) { + cursor = table->createCursor(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + + cursor->open(); +} + +TEST_F(StorageCursorTest, FirstPrivate) { + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, NextPrivate) { + std::map::const_iterator reference = data.begin(); + + reference++; + for (; reference != data.end(); ++reference) { + std::pair element = cursor->next(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + + std::pair element = cursor->first(); + reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, LastPrivate) { + std::pair element = cursor->last(); + std::map::const_reverse_iterator reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, PrevPrivate) { + std::map::const_reverse_iterator reference = data.rbegin(); + + reference++; + for (; reference != data.rend(); ++reference) { + std::pair element = cursor->prev(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + + std::pair element = cursor->last(); + reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, Destruction) { + cursor->close(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); +} + +TEST_F(StorageCursorTest, CurrentPrivate) { + cursor->open(); + + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + element = cursor->current(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + element = cursor->current(); + ++reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + cursor->next(); + cursor->prev(); + element = cursor->current(); + ++reference; + ++reference; + --reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} From 8ff5672655dd82d228c439bf55a6f9418f511da2 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 10 Aug 2023 20:07:12 -0300 Subject: [PATCH 046/125] mostly documentation mostly for Cursor, some corner cases testing --- src/cursor.h | 1 + src/cursor.hpp | 273 ++++++++++++++++++++++++++++++++++++++++- src/storage.cpp | 12 ++ src/storage.h | 1 + src/storage.hpp | 35 +++++- test/storagecursor.cpp | 68 +++++++--- 6 files changed, 368 insertions(+), 22 deletions(-) diff --git a/src/cursor.h b/src/cursor.h index c9f46aa..36d46ba 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -46,6 +46,7 @@ public: void renew() const; void renew(TransactionID txn) const; void close() const; + bool opened() const; std::pair first() const; std::pair last() const; diff --git a/src/cursor.hpp b/src/cursor.hpp index 57d3a42..a3bd252 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -21,25 +21,67 @@ #include "cursor.h" +/** + * \class LMDBAL::Cursor + * \brief An object to iterate storages. + * + * \tparam K type of the keys in the storage that this cursor would iterate + * \tparam V type of the values in the storage that this cursor would iterate + * + * Cursor allowes you to perform sequential querries, navigate from one element to another at reduced operation price. + * For now Cursors are read only but in the future you might also be able to write to the storage with them. + * + * Cursors are owned by the storage, they die with the storage. + * They also get closed if the storage is closed (if you close by the database for example) + * + * You can obtain an instance of this class calling LMDBAL::Storage::createCursor() + * and destory it calling LMDBAL::Storage::destoryCursor() at any time, LMDBAL::Base doesn't necessarily need to be opened. + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + +/** + * \brief Creates a cursor + * + * \param[in] parent a storage that created this cursor + */ template LMDBAL::Cursor::Cursor(Storage* parent): storage(parent), cursor(nullptr), state(closed) -{ - -} +{} +/** + * \brief Destroys a cursor + * + * If the cursor wasn't properly closed - it's going to be upon destruction + */ template LMDBAL::Cursor::~Cursor () { close(); } +/** + * \brief A private function the storage owning this cursor will call to inform this cursor that the thansaction needs to be aborted + */ template void LMDBAL::Cursor::terminated () const { close(); //for now it's the same, but if I ever going to make writable cursor - here is where it's gonna be different } +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function creates a read only transaction just for this cursor + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \throws LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \throws LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + */ template void LMDBAL::Cursor::open () const { storage->ensureOpened(openCursorMethodName); @@ -58,6 +100,18 @@ void LMDBAL::Cursor::open () const { } } +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function uses for queries a transaction you have obtained somewhere else. + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \throws LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \throws LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb + */ template void LMDBAL::Cursor::open (TransactionID txn) const { storage->ensureOpened(openCursorMethodName); @@ -74,6 +128,24 @@ void LMDBAL::Cursor::open (TransactionID txn) const { } } +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * creates new private transaction and rebinds this cursor to it. + * + * Theoretically you could call this method if your public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to a new private transaction. + * + * This function does nothing if the cursor is closed + * + * \param[in] txn a transaction you wish this cursor to be bound to + * + * \throws LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \throws LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + */ template void LMDBAL::Cursor::renew () const { storage->ensureOpened(renewCursorMethodName); @@ -98,6 +170,24 @@ void LMDBAL::Cursor::renew () const { } } +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * and rebinds this cursor to a passed new transaction. + * + * Theoretically you could call this method if your previous public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to another public transaction. + * + * This function does nothing if the cursor is closed + * + * \param[in] txn a transaction you wish this cursor to be bound to + * + * \throws LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \throws LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb + */ template void LMDBAL::Cursor::renew (TransactionID txn) const { storage->ensureOpened(renewCursorMethodName); @@ -120,6 +210,16 @@ void LMDBAL::Cursor::renew (TransactionID txn) const { } } +/** + * \brief Termiates a sequence of operations with the cursor + * + * This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries. + * The state of the cursor is lost after calling this method, some inner resorce is freed. + * + * If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction. + * + * This function does nothing on a closed cursor. + */ template void LMDBAL::Cursor::close () const { switch (state) { @@ -141,31 +241,124 @@ void LMDBAL::Cursor::close () const { } } +/** + * \brief Tells if the cursor is open + */ +template +bool LMDBAL::Cursor::opened () const { + return state != closed; +} + +/** + * \brief Queries the first element in the storage + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::first (K& key, V& value) const { operateCursorRead(key, value, MDB_FIRST, firstMethodName, firstOperationName); } +/** + * \brief Queries the last element in the storage + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::last (K& key, V& value) const { operateCursorRead(key, value, MDB_LAST, lastMethodName, lastOperationName); } +/** + * \brief Queries the next element from the storage + * + * If there was no operation before this method positions the cursor on the first element and returns it + * so, it's basically doing the same as LMDBAL::Cursor::first(K key, V value). + * + * It will also throw LMDBAL::NotFound if you call this method being on the last element + * or if there are no elements in the database. + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::next (K& key, V& value) const { operateCursorRead(key, value, MDB_NEXT, nextMethodName, nextOperationName); } +/** + * \brief Queries the previous element from the storage + * + * If there was no operation before this method positions the cursor on the last element and returns it + * so, it's basically doing the same as LMDBAL::Cursor::last(K key, V value). + * + * It will also throw LMDBAL::NotFound if you call this method being on the first element + * or if there are no elements in the database. + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::prev (K& key, V& value) const { operateCursorRead(key, value, MDB_PREV, prevMethodName, prevOperationName); } +/** + * \brief Returns current cursor element from the storage + * + * If there was no operation before this method throws LMDBAL::Unknown for some reason + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::current (K& key, V& value) const { operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName); } +/** + * \brief Queries the first element in the storage + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \returns std::pair where first is element key and second is element value + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template std::pair LMDBAL::Cursor::first () const { std::pair result; @@ -173,6 +366,17 @@ std::pair LMDBAL::Cursor::first () const { return result; } +/** + * \brief Queries the last element in the storage + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \returns std::pair where first is element key and second is element value + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template std::pair LMDBAL::Cursor::last () const { std::pair result; @@ -180,6 +384,23 @@ std::pair LMDBAL::Cursor::last () const { return result; } +/** + * \brief Queries the next element from the storage + * + * If there was no operation before this method positions the cursor on the first element and returns it + * so, it's basically doing the same as LMDBAL::Cursor::first(). + * + * It will also throw LMDBAL::NotFound if you call this method being on the last element + * or if there are no elements in the database. + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \returns std::pair where first is element key and second is element value + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template std::pair LMDBAL::Cursor::next () const { std::pair result; @@ -187,6 +408,23 @@ std::pair LMDBAL::Cursor::next () const { return result; } +/** + * \brief Queries the previous element from the storage + * + * If there was no operation before this method positions the cursor on the last element and returns it + * so, it's basically doing the same as LMDBAL::Cursor::last(). + * + * It will also throw LMDBAL::NotFound if you call this method being on the first element + * or if there are no elements in the database. + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \returns std::pair where first is element key and second is element value + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage + * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + */ template std::pair LMDBAL::Cursor::prev () const { std::pair result; @@ -194,6 +432,19 @@ std::pair LMDBAL::Cursor::prev () const { return result; } +/** + * \brief Returns current cursor element from the storage + * + * If there was no operation before this method throws LMDBAL::Unknown for some reason + * + * Notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \returns std::pair where first is element key and second is element value + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about + * \throws LMDBAL::Unknown thrown if there was no positioning operation before of if there was some unexpected problem with lmdb + */ template std::pair LMDBAL::Cursor::current () const { std::pair result; @@ -201,7 +452,21 @@ std::pair LMDBAL::Cursor::current () const { return result; } - +/** + * \brief a private mothod that actually doing all the reading + * + * Queries the storage, deserializes the output, notifies the storage of the queried element calling LMDBAL::Storage::discoveredRecord() + * + * \param[out] key a reference to an object the key of queried element is going to be assigned + * \param[out] value a reference to an object the value of queried element is going to be assigned + * \param[in] operation LMDB cursor operation code + * \param[in] methodName a name of the method you called it from, just for the exception message if something goes not as expected + * \param[in] operationName a name of the opeartion, just for the exception message if something goes not as expected + * + * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \throws LMDBAL::NotFound mostly thrown if the query wasn't found + * \throws LMDBAL::Unknown mostly thrown if there was some unexpected problem with lmdb + */ template void LMDBAL::Cursor::operateCursorRead( K& key, diff --git a/src/storage.cpp b/src/storage.cpp index 124dab8..d65923c 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -253,6 +253,18 @@ bool LMDBAL::iStorage::isDBOpened() const { void LMDBAL::iStorage::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} +/** + * \brief Throws LMDBAL::Unknown + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] message - a message you wish to appear in the exception reason + * + * \exception LMDBAL::Unknown thrown everytime + */ +void LMDBAL::iStorage::throwUnknown(const std::string& message) const { + throw Unknown(db->name, message, name);} + /** * \brief Throws LMDBAL::Exist * diff --git a/src/storage.h b/src/storage.h index 0a7d645..bf58222 100644 --- a/src/storage.h +++ b/src/storage.h @@ -48,6 +48,7 @@ protected: void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; void throwUnknown(int rc, TransactionID txn) const; void throwUnknown(int rc) const; + void throwUnknown(const std::string& message) const; void throwDuplicate(const std::string& key) const; void throwNotFound(const std::string& key) const; void throwCursorNotReady(const std::string& method) const; diff --git a/src/storage.hpp b/src/storage.hpp index cd83f5a..5e33f08 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -667,7 +667,7 @@ void LMDBAL::Storage::removeRecord(const K& key) { * This function schedules a record removal, but doesn't immidiately execute it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * - * \param[in] key key of the record you wish to be removed + * \param[in] key key of the record you wish to be removed * \param[in] txn transaction ID, needs to be a writable transaction! * * \exception LMDBAL::Closed thrown if the database was not opened @@ -706,6 +706,11 @@ void LMDBAL::Storage::close() { iStorage::close(); } +/** + * \brief Creates cursor + * + * \returns LMDBAL::Cursor for this storage and returs you a pointer to a created cursor + */ template LMDBAL::Cursor* LMDBAL::Storage::createCursor() { Cursor* cursor = new Cursor(this); @@ -714,18 +719,44 @@ LMDBAL::Cursor* LMDBAL::Storage::createCursor() { return cursor; } +/** + * \brief Destroys cursor + * + * This a normal way to discard a cursor you don't need anymore + * + * \param[in] cursor a pointer to a cursor you want to destroy + * + * \throws LMDBAL::Unknown thrown if you try to destroy something that this storage didn't create + */ template void LMDBAL::Storage::destroyCursor(Cursor* cursor) { - cursors.erase(cursor); + typename std::set*>::const_iterator itr = cursors.find(cursor); + if (itr == cursors.end()) + throwUnknown("An attempt to destroy a cursor the storage doesn't own"); + + cursors.erase(itr); delete cursor; } +/** + * \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache + * + * \param[in] key a key of discovered record + * \param[in] value a value of discovered record + */ template void LMDBAL::Storage::discoveredRecord(const K& key, const V& value) const { UNUSED(key); UNUSED(value); } +/** + * \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache + * + * \param[in] key a key of discovered record + * \param[in] value a value of discovered record + * \param[in] txn TransactionID under which the dicovery happened, to avoid not commited changes collisions + */ template void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, TransactionID txn) const { UNUSED(key); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index e7b1f8d..8a08724 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -8,7 +8,8 @@ class StorageCursorTest : public ::testing::Test { protected: StorageCursorTest(): ::testing::Test(), - table (db->getStorage("table1")) {} + table (db->getStorage("table1")), + emptyTable (db->getStorage("empty")) {} ~StorageCursorTest() {} @@ -16,6 +17,7 @@ protected: if (db == nullptr) { db = new LMDBAL::Base("testBase"); db->addStorage("table1"); + db->addStorage("empty"); db->open(); } } @@ -25,16 +27,21 @@ protected: db->removeDirectory(); delete db; db = nullptr; + cursor = nullptr; + emptyCursor = nullptr; } static LMDBAL::Base* db; static LMDBAL::Cursor* cursor; + static LMDBAL::Cursor* emptyCursor; LMDBAL::Storage* table; + LMDBAL::Storage* emptyTable; }; LMDBAL::Base* StorageCursorTest::db = nullptr; LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; +LMDBAL::Cursor* StorageCursorTest::emptyCursor = nullptr; static const std::map data({ {245665783, "bothering nerds"}, @@ -50,13 +57,13 @@ static const std::map data({ }); TEST_F(StorageCursorTest, PopulatingTheTable) { - uint32_t amount = table->addRecords(data); EXPECT_EQ(amount, data.size()); } TEST_F(StorageCursorTest, Creation) { cursor = table->createCursor(); + emptyCursor = emptyTable->createCursor(); EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); @@ -121,21 +128,7 @@ TEST_F(StorageCursorTest, PrevPrivate) { EXPECT_EQ(element.second, reference->second); } -TEST_F(StorageCursorTest, Destruction) { - cursor->close(); - - EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); -} - TEST_F(StorageCursorTest, CurrentPrivate) { - cursor->open(); - - EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc - std::pair element = cursor->first(); std::map::const_iterator reference = data.begin(); @@ -165,3 +158,46 @@ TEST_F(StorageCursorTest, CurrentPrivate) { EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); } + +TEST_F(StorageCursorTest, Destruction) { + cursor->close(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); +} + +TEST_F(StorageCursorTest, CornerCases) { + emptyCursor->open(); + EXPECT_THROW(emptyCursor->first(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->last(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->next(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->current(), LMDBAL::Unknown); + emptyCursor->close(); + + cursor->open(); + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + + std::map::const_reverse_iterator breference = data.rbegin(); + std::pair element(cursor->prev()); + EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again! + EXPECT_EQ(element.second, breference->second); + element = cursor->current(); + EXPECT_EQ(element.first, breference->first); + EXPECT_EQ(element.second, breference->second); + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + cursor->close(); + + cursor->open(); + element = cursor->next(); + std::map::const_iterator reference = data.begin(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + element = cursor->current(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); +} From d57d27f952f9f84b8fe8e15b85d1c24e25c301c7 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 12 Aug 2023 18:28:49 -0300 Subject: [PATCH 047/125] some more tests, one subtle but important bugfix with cache --- CMakeLists.txt | 2 +- src/cache.h | 4 ++ src/cache.hpp | 40 ++++++++++++----- test/storagecursor.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 81c12f8..21199ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.3.1 + VERSION 0.4.0 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/src/cache.h b/src/cache.h index e6342cf..41b3e06 100644 --- a/src/cache.h +++ b/src/cache.h @@ -57,6 +57,9 @@ protected: virtual void transactionStarted(TransactionID txn, bool readOnly) const override; virtual void transactionCommited(TransactionID txn) override; virtual void transactionAborted(TransactionID txn) const override; + + virtual void discoveredRecord(const K& key, const V& value) const override; + virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const override; private: void handleMode() const; @@ -70,6 +73,7 @@ private: void handleReplaceAll(std::map* data); void handleAddRecords(const std::map& data, bool overwrite, SizeType newSize); void handleDrop(); + void appendToCache(const K& key, const V& value) const; public: using Storage::drop; diff --git a/src/cache.hpp b/src/cache.hpp index 6563d60..c82820d 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -235,8 +235,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { try { Storage::getRecord(key, out); - cache->insert(std::make_pair(key, out)); - handleMode(); + appendToCache(key, out); return; } catch (const NotFound& error) { if (mode != Mode::full) @@ -337,10 +336,9 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con try { Storage::getRecord(key, out, txn); - if (!currentTransaction) { - cache->insert(std::make_pair(key, out)); - handleMode(); - } + if (!currentTransaction) + appendToCache(key, out); + return; } catch (const NotFound& error) { if (!currentTransaction && mode != Mode::full) @@ -350,6 +348,18 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } } +template +void LMDBAL::Cache::discoveredRecord(const K& key, const V& value) const { + appendToCache(key, value); +} + +template +void LMDBAL::Cache::discoveredRecord(const K& key, const V& value, TransactionID txn) const { + typename TransactionCache::const_iterator tc = transactionCache->find(txn); + if (tc == transactionCache->end()) //there is a way to look though all the records in transaction log and cache the new pair + discoveredRecord(key, value); //if there is nothing in transaction log about it, but it seems like too much for a small gain +} + template bool LMDBAL::Cache::checkRecord(const K& key) const { iStorage::ensureOpened(iStorage::checkRecordMethodName); @@ -363,8 +373,8 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { try { V value = Storage::getRecord(key); - cache->insert(std::make_pair(key, value)); - handleMode(); + appendToCache(key, value); + return true; } catch (const NotFound& error) { if (mode != Mode::full) @@ -438,10 +448,9 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { try { V value = Storage::getRecord(key, txn); - if (!currentTransaction) { - cache->insert(std::make_pair(key, value)); - handleMode(); - } + if (!currentTransaction) + appendToCache(key, value); + return true; } catch (const NotFound& error) { if (!currentTransaction && mode != Mode::full) @@ -451,6 +460,13 @@ bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { } } +template +void LMDBAL::Cache::appendToCache(const K& key, const V& value) const { + typename std::pair::const_iterator, bool> pair = cache->insert(std::make_pair(key, value)); + if (pair.second) + handleMode(); +} + template std::map LMDBAL::Cache::readAll() const { iStorage::ensureOpened(iStorage::readAllMethodName); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index 8a08724..513c9e0 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -34,6 +34,7 @@ protected: static LMDBAL::Base* db; static LMDBAL::Cursor* cursor; static LMDBAL::Cursor* emptyCursor; + static LMDBAL::TransactionID transaction; LMDBAL::Storage* table; LMDBAL::Storage* emptyTable; @@ -42,6 +43,7 @@ protected: LMDBAL::Base* StorageCursorTest::db = nullptr; LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; LMDBAL::Cursor* StorageCursorTest::emptyCursor = nullptr; +LMDBAL::TransactionID StorageCursorTest::transaction = nullptr; static const std::map data({ {245665783, "bothering nerds"}, @@ -167,9 +169,106 @@ TEST_F(StorageCursorTest, Destruction) { EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + + EXPECT_THROW(emptyTable->destroyCursor(cursor), LMDBAL::Unknown); + table->destroyCursor(cursor); + + cursor = table->createCursor(); +} + +TEST_F(StorageCursorTest, FirstPublic) { + transaction = db->beginReadOnlyTransaction(); + + cursor->open(transaction); + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, NextPublic) { + std::map::const_iterator reference = data.begin(); + + reference++; + for (; reference != data.end(); ++reference) { + std::pair element = cursor->next(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + + std::pair element = cursor->first(); + reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, LastPublic) { + std::pair element = cursor->last(); + std::map::const_reverse_iterator reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, PrevPublic) { + std::map::const_reverse_iterator reference = data.rbegin(); + + reference++; + for (; reference != data.rend(); ++reference) { + std::pair element = cursor->prev(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + + std::pair element = cursor->last(); + reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, CurrentPublic) { + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + element = cursor->current(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + element = cursor->current(); + ++reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + cursor->next(); + cursor->prev(); + element = cursor->current(); + ++reference; + ++reference; + --reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); } TEST_F(StorageCursorTest, CornerCases) { + db->abortTransaction(transaction); + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); + cursor->close(); + emptyCursor->open(); EXPECT_THROW(emptyCursor->first(), LMDBAL::NotFound); EXPECT_THROW(emptyCursor->last(), LMDBAL::NotFound); From 161a8500884d3bcae1630c945388b4517f3d5b24 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 13 Aug 2023 13:30:17 -0300 Subject: [PATCH 048/125] Some more tests over cache cursor operations --- CHANGELOG.md | 9 ++ test/CMakeLists.txt | 1 + test/basic.cpp | 15 ++ test/cachecursor.cpp | 326 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 test/cachecursor.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c3db7..cf30d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## LMDBAL 0.4.0 (August 13, 2023) +### Bug fixes +- possible cache unsync + +### Improvements +- read only cursors +- some more documentation +- more tests + ## LMDBAL 0.3.1 (April 14, 2023) ### Bug fixes - build with qt5 now is possible again diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e51951..b2b1eba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(runUnitTests storagetransaction.cpp cachetransaction.cpp storagecursor.cpp + cachecursor.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/basic.cpp b/test/basic.cpp index 143e2c7..7369de6 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -106,6 +106,10 @@ TEST_F(BaseTest, GettingNotExistingKeys) { TEST_F(BaseTest, Persistence) { EXPECT_EQ(db->ready(), true); + uint32_t t1Size = t1->count(); + uint32_t t2Size = t2->count(); + uint32_t c1Size = c1->count(); + db->close(); delete db; @@ -115,20 +119,31 @@ TEST_F(BaseTest, Persistence) { c1 = db->addCache("cache1"); db->open(); + EXPECT_EQ(t1->count(), t1Size); EXPECT_EQ(t1->getRecord(3), 15); EXPECT_EQ(t1->getRecord(1), 2); EXPECT_EQ(t1->getRecord(2), 2); + EXPECT_EQ(t1->count(), t1Size); + + EXPECT_EQ(t2->count(), t2Size); EXPECT_EQ(t2->getRecord("hello"), "world"); EXPECT_EQ(t2->getRecord("aaa"), "gagdfsdf"); EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); EXPECT_EQ(t2->getRecord("sdfsda"), "shgsdgfa"); + EXPECT_EQ(t2->count(), t2Size); + EXPECT_EQ(c1->count(), c1Size); + EXPECT_EQ(c1->checkRecord(-116), true); EXPECT_EQ(c1->getRecord(-116), "whatever"); + EXPECT_EQ(c1->checkRecord(-4), true); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); + EXPECT_EQ(c1->checkRecord(-4), true); + EXPECT_EQ(c1->count(), c1Size); EXPECT_EQ(c1->getRecord(-37), "aaaaa tss tsss tsss tsss aaaaaaa"); EXPECT_EQ(c1->getRecord(2), "blah balah"); + EXPECT_EQ(c1->count(), c1Size); EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound); EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound); diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp new file mode 100644 index 0000000..d1afa3e --- /dev/null +++ b/test/cachecursor.cpp @@ -0,0 +1,326 @@ +#include + +#include "base.h" +#include "storage.h" +#include "cache.h" +#include "cursor.h" + +class CacheCursorTest : public ::testing::Test { +protected: + CacheCursorTest(): + ::testing::Test(), + cache (db->getCache("table1")), + emptyCache (db->getCache("empty")) {} + + ~CacheCursorTest() {} + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("testBase"); + db->addCache("table1"); + db->addCache("empty"); + db->open(); + } + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + cursor = nullptr; + emptyCursor = nullptr; + } + + static LMDBAL::Base* db; + static LMDBAL::Cursor* cursor; + static LMDBAL::Cursor* emptyCursor; + static LMDBAL::TransactionID transaction; + + LMDBAL::Cache* cache; + LMDBAL::Cache* emptyCache; +}; + +LMDBAL::Base* CacheCursorTest::db = nullptr; +LMDBAL::Cursor* CacheCursorTest::cursor = nullptr; +LMDBAL::Cursor* CacheCursorTest::emptyCursor = nullptr; +LMDBAL::TransactionID CacheCursorTest::transaction = nullptr; + +static const std::map data({ + {245665783, "bothering nerds"}, + {3458, "resilent pick forefront"}, + {105190, "apportunity legal bat"}, + {6510, "outside"}, + {7438537, "damocles plush apparently rusty"}, + {19373572, "local guidence"}, + {138842, "forgetting tusks prepare"}, + {981874, "butchered soaking pawn"}, + {19302, "tanned inmate"}, + {178239, "custody speaks neurotic"}, +}); + +TEST_F(CacheCursorTest, PopulatingTheTable) { + uint32_t amount = cache->addRecords(data); + EXPECT_EQ(amount, data.size()); +} + +TEST_F(CacheCursorTest, Creation) { + cursor = cache->createCursor(); + emptyCursor = emptyCache->createCursor(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + + cursor->open(); +} + +TEST_F(CacheCursorTest, FirstPrivate) { + EXPECT_EQ(cache->count(), data.size()); + + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, NextPrivate) { + std::map::const_iterator reference = data.begin(); + + reference++; + for (; reference != data.end(); ++reference) { + std::pair element = cursor->next(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); + } + + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_EQ(cache->count(), data.size()); + + std::pair element = cursor->first(); + reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, LastPrivate) { + EXPECT_EQ(cache->count(), data.size()); + + std::pair element = cursor->last(); + std::map::const_reverse_iterator reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, PrevPrivate) { + std::map::const_reverse_iterator reference = data.rbegin(); + + reference++; + for (; reference != data.rend(); ++reference) { + std::pair element = cursor->prev(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); + } + + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_EQ(cache->count(), data.size()); + + std::pair element = cursor->last(); + reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, CurrentPrivate) { + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + element = cursor->current(); + EXPECT_EQ(cache->count(), data.size()); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + element = cursor->current(); + ++reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + cursor->next(); + cursor->prev(); + element = cursor->current(); + ++reference; + ++reference; + --reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, Destruction) { + cursor->close(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + + EXPECT_THROW(emptyCache->destroyCursor(cursor), LMDBAL::Unknown); + cache->destroyCursor(cursor); + + cursor = cache->createCursor(); +} + +TEST_F(CacheCursorTest, FirstPublic) { + transaction = db->beginTransaction(); + + cursor->open(transaction); + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, NextPublic) { + std::map::const_iterator reference = data.begin(); + + reference++; + for (; reference != data.end(); ++reference) { + std::pair element = cursor->next(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); + } + + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_EQ(cache->count(), data.size()); + + std::pair element = cursor->first(); + reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(CacheCursorTest, LastPublic) { + std::pair element = cursor->last(); + std::map::const_reverse_iterator reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, PrevPublic) { + std::map::const_reverse_iterator reference = data.rbegin(); + + reference++; + for (; reference != data.rend(); ++reference) { + std::pair element = cursor->prev(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); + } + + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + + std::pair element = cursor->last(); + reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, CurrentPublic) { + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + element = cursor->current(); + EXPECT_EQ(cache->count(), data.size()); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + element = cursor->current(); + ++reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + cursor->next(); + cursor->prev(); + element = cursor->current(); + ++reference; + ++reference; + --reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_EQ(cache->count(), data.size()); +} + +TEST_F(CacheCursorTest, CornerCases) { + db->abortTransaction(transaction); + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); + cursor->close(); + + emptyCursor->open(); + EXPECT_THROW(emptyCursor->first(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->last(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->next(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor->current(), LMDBAL::Unknown); + emptyCursor->close(); + + cursor->open(); + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + + std::map::const_reverse_iterator breference = data.rbegin(); + std::pair element(cursor->prev()); + EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again! + EXPECT_EQ(element.second, breference->second); + element = cursor->current(); + EXPECT_EQ(element.first, breference->first); + EXPECT_EQ(element.second, breference->second); + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + cursor->close(); + + cursor->open(); + element = cursor->next(); + std::map::const_iterator reference = data.begin(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + element = cursor->current(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); +} + From 7b26d57ab6346cf17ebb42ef8d069dd94ecd1d80 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 13 Aug 2023 14:53:07 -0300 Subject: [PATCH 049/125] some more docs, doc build fix --- CHANGELOG.md | 1 + doc/CMakeLists.txt | 12 ++++---- src/cursor.hpp | 2 -- src/exceptions.h | 4 +-- src/serializer.hpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++ src/storage.h | 6 ++++ 6 files changed, 85 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf30d98..5c27023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - read only cursors - some more documentation - more tests +- doxygen-awesome build bix ## LMDBAL 0.3.1 (April 14, 2023) ### Bug fixes diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 24ee33b..48e365d 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -6,23 +6,23 @@ if (BUILD_DOXYGEN_AWESOME) include(ExternalProject) ExternalProject_Add(doxygen-awesome-css GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css.git - GIT_TAG "v2.2.0" + GIT_TAG "v2.2.1" CONFIGURE_COMMAND "" BUILD_COMMAND make BUILD_IN_SOURCE TRUE - INSTALL_COMMAND make DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css install + INSTALL_COMMAND make PREFIX=${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css install ) set (DOXYGEN_GENERATE_TREEVIEW YES) set (DOXYGEN_DISABLE_INDEX NO) set (DOXYGEN_FULL_SIDEBAR NO) set (DOXYGEN_HTML_EXTRA_STYLESHEET - ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome.css - ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only.css - ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome.css + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-sidebar-only.css + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css custom.css ) set (DOXYGEN_HTML_EXTRA_FILES - ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js + ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css/share/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js ) set (DOXYGEN_FULL_SIDEBAR NO) set (DOXYGEN_HTML_COLORSTYLE "LIGHT") diff --git a/src/cursor.hpp b/src/cursor.hpp index a3bd252..1430833 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -141,8 +141,6 @@ void LMDBAL::Cursor::open (TransactionID txn) const { * * This function does nothing if the cursor is closed * - * \param[in] txn a transaction you wish this cursor to be bound to - * * \throws LMDBAL::Closed thrown if you try to renew the cursor on a closed database * \throws LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb */ diff --git a/src/exceptions.h b/src/exceptions.h index 8a90690..fbc1f63 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -33,9 +33,9 @@ public: Exception(); virtual ~Exception(); - virtual std::string getMessage() const = 0; /**<\brief returns exception message*/ + virtual std::string getMessage() const = 0; /**<\brief returns exception message*/ - const char* what() const noexcept( true ) override; + const char* what() const noexcept( true ) override; /**<\brief system exception method that is actually called to show the message*/ }; /** diff --git a/src/serializer.hpp b/src/serializer.hpp index bbece55..9b10cbd 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -21,6 +21,21 @@ #include "serializer.h" +/** + * \class LMDBAL::Serializer + * + * A class that is constructed in every LMDBAL::Storage + * to serialize or deserialize keys and values. + * + * It serializes to and deserializes from MDB_valMDB_val + * + * \tparam K type of the keys of the storage + * \tparam V type of the values of the storage + */ + +/** + * \brief Creates an empty Serializer + */ template LMDBAL::Serializer::Serializer() : bytes(), @@ -30,6 +45,13 @@ LMDBAL::Serializer::Serializer() : buffer.open(QIODevice::ReadWrite); } +/** + * \brief Creates a Serializer with some data in it + * + * The data automatically gets serialized + * + * \param[in] value - a value that is assigned to the serializer + */ template LMDBAL::Serializer::Serializer(const T& value) : bytes(), @@ -40,11 +62,23 @@ LMDBAL::Serializer::Serializer(const T& value) : _setData(value); } +/** + * \brief Destoys the serializer + */ template LMDBAL::Serializer::~Serializer() { buffer.close(); } +/** + * \brief Sets the data to the seriazer + * + * This is a normal way to seriaze value + * + * \param[in] value - a value you want to serialize + * + * \returns serialized value + */ template MDB_val LMDBAL::Serializer::setData(const T& value) { clear(); @@ -52,6 +86,15 @@ MDB_val LMDBAL::Serializer::setData(const T& value) { return getData(); } +/** + * \brief Deserializes value + * + * This is a normal way to deseriaze value + * + * \param[in] value - a value you want to deserialize + * + * \returns deserialized value + */ template T LMDBAL::Serializer::deserialize(const MDB_val& value) { T result; @@ -60,6 +103,14 @@ T LMDBAL::Serializer::deserialize(const MDB_val& value) { return result; } +/** + * \brief Deserializes value + * + * This is a normal way to deseriaze value + * + * \param[in] value - a value you want to deserialize + * \param[out] result - deserialized value + */ template void LMDBAL::Serializer::deserialize(const MDB_val& value, T& result) { clear(); @@ -67,11 +118,21 @@ void LMDBAL::Serializer::deserialize(const MDB_val& value, T& result) { stream >> result; } +/** + * \brief Private function that handles serialization + * + * \param[in] value - a value you want to serialize + */ template void LMDBAL::Serializer::_setData(const T& value) { stream << value; } +/** + * \brief Clears the state of serializer + * + * Normally you don't need to call this function + */ template void LMDBAL::Serializer::clear() { if (buffer.pos() > 0) { @@ -79,6 +140,15 @@ void LMDBAL::Serializer::clear() { } } +/** + * \brief Returns the data if it already was serialized + * + * Normally you don't need to call this function + * + * This may be usefull if you called LMDBAL::Serilizer::setData() but lost the result + * + * \returns Serialized data + */ template MDB_val LMDBAL::Serializer::getData() { MDB_val val; diff --git a/src/storage.h b/src/storage.h index bf58222..fa9516c 100644 --- a/src/storage.h +++ b/src/storage.h @@ -35,6 +35,12 @@ protected: iStorage(const std::string& name, Base* parent); virtual ~iStorage(); + /** + * \brief A private virtual function I need to open each storage in the database + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + */ virtual int open(MDB_txn * transaction) = 0; virtual void close(); From f0727aa73d38f7122cfd002522e1629b30974c21 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 15 Aug 2023 15:48:19 -0300 Subject: [PATCH 050/125] started to work on duplicates support --- CHANGELOG.md | 13 ++++- src/base.h | 45 ++++++++--------- src/cache.h | 2 +- src/cache.hpp | 9 ++-- src/serializer.hpp | 3 +- src/storage.cpp | 9 +++- src/storage.h | 16 ++++-- src/storage.hpp | 118 ++++++++++++++++++--------------------------- test/basic.cpp | 25 ++++++++++ 9 files changed, 132 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c27023..035262c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,22 @@ # Changelog +## LMDBAL 0.5.0 (UNRELEASED, 2023) +### New Features +- duplicates support + + ## LMDBAL 0.4.0 (August 13, 2023) +### New Features +- read only cursors + ### Bug fixes - possible cache unsync +- doxygen-awesome build bix ### Improvements -- read only cursors - some more documentation - more tests -- doxygen-awesome build bix + ## LMDBAL 0.3.1 (April 14, 2023) ### Bug fixes @@ -17,6 +25,7 @@ ### Improvements - exception documentation + ## LMDBAL 0.3.0 (April 12, 2023) ### New features - transaction functions diff --git a/src/base.h b/src/base.h index 3cee862..85f75c4 100644 --- a/src/base.h +++ b/src/base.h @@ -69,16 +69,16 @@ public: void abortTransaction(TransactionID id) const; template - LMDBAL::Storage* addStorage(const std::string& name); + LMDBAL::Storage* addStorage(const std::string& storageName, bool duplicates = false); template - LMDBAL::Cache* addCache(const std::string& name); + LMDBAL::Cache* addCache(const std::string& storageName); template - LMDBAL::Storage* getStorage(const std::string& name); + LMDBAL::Storage* getStorage(const std::string& storageName); template - LMDBAL::Cache* getCache(const std::string& name); + LMDBAL::Cache* getCache(const std::string& storageName); private: typedef std::map Storages; /** -LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& _name) { +LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, bool duplicates) { if (opened) { - throw Opened(name, "add storage " + _name); + throw Opened(name, "add storage " + storageName); } - Storage* storage = new Storage(_name, this); - std::pair pair = storages.insert(std::make_pair(_name, (iStorage*)storage)); + Storage* storage = new Storage(this, storageName, duplicates); + std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)storage)); if (!pair.second) - throw StorageDuplicate(name, _name); + throw StorageDuplicate(name, storageName); return storage; } @@ -142,7 +143,7 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& _name) { * Defines that the database is going to have the following cache. * The LMDBAL::Base must be closed * - * \param[in] _name - cache name + * \param[in] storageName - cache name * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it * * \tparam K - key type of the cache @@ -152,14 +153,14 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& _name) { * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add cache with repeating name */ template -LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& _name) { +LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { if (opened) { - throw Opened(name, "add cache " + _name); + throw Opened(name, "add cache " + storageName); } - Cache* cache = new Cache(_name, this); - std::pair pair = storages.insert(std::make_pair(_name, (iStorage*)cache)); + Cache* cache = new Cache(this, storageName, false); + std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)cache)); if (!pair.second) - throw StorageDuplicate(name, _name); + throw StorageDuplicate(name, storageName); return cache; } @@ -173,7 +174,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& _name) { * this method with template parameters * on the same name of the previously added storage, or calling it on cache - the behaviour is undefined * - * \param[in] _name - storage name + * \param[in] storageName - storage name * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it * * \tparam K - key type of the storage @@ -182,8 +183,8 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& _name) { * \exception std::out_of_range thrown if storage with the given name was not found */ template -LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& _name) { - return static_cast*>(storages.at(_name)); +LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& storageName) { + return static_cast*>(storages.at(storageName)); } /** @@ -195,7 +196,7 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& _name) { * this method with template parameters * on the same name of the previously added cache, or calling it on storage - the behaviour is undefined * - * \param[in] _name - cache name + * \param[in] storageName - cache name * \returns cache pointer. LMDBAL::Base keeps the ownership of the added cache, you don't need to destoroy it * * \tparam K - key type of the cache @@ -204,8 +205,8 @@ LMDBAL::Storage* LMDBAL::Base::getStorage(const std::string& _name) { * \exception std::out_of_range thrown if cache with the given name was not found */ template -LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& _name) { - return static_cast*>(storages.at(_name)); +LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& storageName) { + return static_cast*>(storages.at(storageName)); } #endif //LMDBAL_BASE_H diff --git a/src/cache.h b/src/cache.h index 41b3e06..ff0d996 100644 --- a/src/cache.h +++ b/src/cache.h @@ -51,7 +51,7 @@ class Cache : public Storage { typedef std::map TransactionCache; protected: - Cache(const std::string& name, Base* parent); + Cache(Base* parent, const std::string& name, bool duplicates = false); ~Cache() override; virtual void transactionStarted(TransactionID txn, bool readOnly) const override; diff --git a/src/cache.hpp b/src/cache.hpp index c82820d..063b1bd 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -39,12 +39,13 @@ /** * \brief Creates a cache * - * \param[in] _name - name of the new cache - * \param[in] parent - parent database pointed (borrowed) + * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) + * \param[in] name - the name of the storage + * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) */ template -LMDBAL::Cache::Cache(const std::string& _name, Base* parent): - Storage(_name, parent), +LMDBAL::Cache::Cache(Base* parent, const std::string& name, bool duplicates): + Storage(parent, name, duplicates), mode(Mode::nothing), cache(new std::map()), abscent(new std::set()), diff --git a/src/serializer.hpp b/src/serializer.hpp index 9b10cbd..80f4d90 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -23,11 +23,12 @@ /** * \class LMDBAL::Serializer + * \brief A class handling serialization/deserialization * * A class that is constructed in every LMDBAL::Storage * to serialize or deserialize keys and values. * - * It serializes to and deserializes from MDB_valMDB_val + * It serializes to and deserializes from MDB_val * * \tparam K type of the keys of the storage * \tparam V type of the values of the storage diff --git a/src/storage.cpp b/src/storage.cpp index d65923c..0f09886 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -30,11 +30,16 @@ /** * \brief Constructs a storage interface + * + * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) + * \param[in] name - the name of the storage + * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) */ -LMDBAL::iStorage::iStorage(const std::string& p_name, Base* parent): +LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates): dbi(), db(parent), - name(p_name) + name(name), + duplicates(duplicates) {} /** diff --git a/src/storage.h b/src/storage.h index fa9516c..bd1229a 100644 --- a/src/storage.h +++ b/src/storage.h @@ -19,10 +19,14 @@ #ifndef LMDBAL_STORAGE_H #define LMDBAL_STORAGE_H +#include + #include "base.h" #include "serializer.h" #include "cursor.h" +class BaseTest; + namespace LMDBAL { typedef uint32_t SizeType; @@ -32,7 +36,7 @@ class iStorage { public: protected: - iStorage(const std::string& name, Base* parent); + iStorage(Base* parent, const std::string& name, bool duplicates = false); virtual ~iStorage(); /** @@ -77,9 +81,11 @@ protected: MDB_dbi dbi; /**<\brief lmdb storage handle*/ Base* db; /**<\brief parent database pointer (borrowed)*/ const std::string name; /**<\brief this storage name*/ + const bool duplicates; /**<\brief true if storage supports duplicates*/ inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ + inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ @@ -92,8 +98,8 @@ protected: inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ protected: - template - int makeStorage(MDB_txn* transaction); + template + int makeStorage(MDB_txn* transaction, bool duplicates); template static std::string toString(const T& value); @@ -101,14 +107,16 @@ protected: template class Storage : public iStorage { + friend class ::BaseTest; friend class Base; friend class Cursor; protected: - Storage(const std::string& name, Base* parent); + Storage(Base* parent, const std::string& name, bool duplicates = false); ~Storage() override; virtual void discoveredRecord(const K& key, const V& value) const; virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const; + uint32_t flags() const; public: using iStorage::drop; diff --git a/src/storage.hpp b/src/storage.hpp index 5e33f08..d1dcbae 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -41,12 +41,13 @@ /** * \brief Creates a storage * - * \param[in] _name - name of the new storage - * \param[in] parent - parent database pointed (borrowed) + * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) + * \param[in] name - the name of the storage + * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) */ template -LMDBAL::Storage::Storage(const std::string& _name, Base* parent): - iStorage(_name, parent), +LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool duplicates): + iStorage(parent, name, duplicates), keySerializer(), valueSerializer(), cursors() @@ -692,7 +693,7 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { */ template int LMDBAL::Storage::open(MDB_txn* transaction) { - return makeStorage(transaction); + return makeStorage(transaction, duplicates); } /** @@ -719,6 +720,30 @@ LMDBAL::Cursor* LMDBAL::Storage::createCursor() { return cursor; } +/** + * \brief Reads current storage flags it was opened with + * + * This function exists mostly for testing purposes + * + * \returns Third out parameter of mdb_dbi_flags function + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown if the result of mdb_dbi_flags was not successfull + */ +template +uint32_t LMDBAL::Storage::flags() const { + ensureOpened(flagsMethodName); + uint32_t result; + TransactionID txn = beginReadOnlyTransaction(); + + int res = mdb_dbi_flags(txn, dbi, &result); + abortTransaction(txn); + if (res != MDB_SUCCESS) + throwUnknown(res); + + return result; +} + /** * \brief Destroys cursor * @@ -726,7 +751,7 @@ LMDBAL::Cursor* LMDBAL::Storage::createCursor() { * * \param[in] cursor a pointer to a cursor you want to destroy * - * \throws LMDBAL::Unknown thrown if you try to destroy something that this storage didn't create + * \exception LMDBAL::Unknown thrown if you try to destroy something that this storage didn't create */ template void LMDBAL::Storage::destroyCursor(Cursor* cursor) { @@ -770,79 +795,28 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans * \tparam K type of keys in opening storage * * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! + * \param[in] duplicates - true if you wish to enable duplicates support for the storage + * * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code * - * This and the following collection of specializations are a way to optimise database using - * MDB_INTEGERKEY flag, when the key is actually kind of an integer + * This is a way to optimise database using MDB_INTEGERKEY flag, + * when the key is actually kind of an integer * This infrastructure also allowes us to customize mdb_dbi_open call in the future */ -template -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE, &dbi); -} +template +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) { + unsigned int flags = MDB_CREATE; + if constexpr (std::is_integral::value) + flags |= MDB_INTEGERKEY; -/** - * \brief Opening database function specialization for uint64_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} + if (duplicates) { + flags |= MDB_DUPSORT; -/** - * \brief Opening database function specialization for uint32_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} + if constexpr (std::is_integral::value) + flags |= MDB_INTEGERDUP | MDB_DUPFIXED; + } -/** - * \brief Opening database function specialization for uint16_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -/** - * \brief Opening database function specialization for uint8_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -/** - * \brief Opening database function specialization for int64_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -/** - * \brief Opening database function specialization for int32_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -/** - * \brief Opening database function specialization for int16_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); -} - -/** - * \brief Opening database function specialization for int8_t - */ -template<> -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction) { - return mdb_dbi_open(transaction, name.c_str(), MDB_CREATE | MDB_INTEGERKEY, &dbi); + return mdb_dbi_open(transaction, name.c_str(), flags, &dbi); } /** diff --git a/test/basic.cpp b/test/basic.cpp index 7369de6..460ce36 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -16,6 +16,10 @@ protected: ~BaseTest() {} + uint32_t getT1Flags() const {return t1->flags();} + uint32_t getT2Flags() const {return t2->flags();} + uint32_t getC1Flags() const {return c1->flags();} + static void SetUpTestSuite() { if (db == nullptr) { db = new LMDBAL::Base("testBase"); @@ -56,6 +60,27 @@ TEST_F(BaseTest, OpeningClosingDatabase) { EXPECT_EQ(db->ready(), true); } +TEST_F(BaseTest, Flags) { + uint32_t t1Flags = getT1Flags(); + uint32_t t2Flags = getT2Flags(); + uint32_t c1Flags = getC1Flags(); + + EXPECT_TRUE(t1Flags & MDB_INTEGERKEY); + EXPECT_FALSE(t1Flags & MDB_DUPSORT); + EXPECT_FALSE(t1Flags & MDB_DUPFIXED); + EXPECT_FALSE(t1Flags & MDB_INTEGERDUP); + + EXPECT_FALSE(t2Flags & MDB_INTEGERKEY); + EXPECT_FALSE(t2Flags & MDB_DUPSORT); + EXPECT_FALSE(t2Flags & MDB_DUPFIXED); + EXPECT_FALSE(t2Flags & MDB_INTEGERDUP); + + EXPECT_TRUE(c1Flags & MDB_INTEGERKEY); + EXPECT_FALSE(c1Flags & MDB_DUPSORT); + EXPECT_FALSE(c1Flags & MDB_DUPFIXED); + EXPECT_FALSE(c1Flags & MDB_INTEGERDUP); +} + TEST_F(BaseTest, AddingIntegerKey) { EXPECT_EQ(db->ready(), true); t1->addRecord(1, 2); From 06e1aca45a35c425c0b4bdf44dff73e3906b3824 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 17 Aug 2023 11:45:11 -0300 Subject: [PATCH 051/125] some more ideas about duplicates --- src/base.h | 21 +++++++---- src/cache.h | 2 +- src/cache.hpp | 4 +- src/storage.cpp | 4 +- src/storage.h | 11 +++--- src/storage.hpp | 30 +++++++++++---- test/CMakeLists.txt | 1 + test/duplicates.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 test/duplicates.cpp diff --git a/src/base.h b/src/base.h index 85f75c4..f09330e 100644 --- a/src/base.h +++ b/src/base.h @@ -48,6 +48,12 @@ class Cache; typedef MDB_txn* TransactionID; /**AND value pairs allowed*/ + full /** - any configuration of duplicates goes*/ +}; + class Base { friend class iStorage; public: @@ -69,7 +75,7 @@ public: void abortTransaction(TransactionID id) const; template - LMDBAL::Storage* addStorage(const std::string& storageName, bool duplicates = false); + LMDBAL::Storage* addStorage(const std::string& storageName, Duplicates duplicates = uniqueKey); template LMDBAL::Cache* addCache(const std::string& storageName); @@ -115,6 +121,7 @@ private: * The LMDBAL::Base must be closed * * \param[in] storageName - storage name + * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) * * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it * @@ -125,10 +132,10 @@ private: * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add storage with repeating name */ template -LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, bool duplicates) { - if (opened) { +LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, Duplicates duplicates) { + if (opened) throw Opened(name, "add storage " + storageName); - } + Storage* storage = new Storage(this, storageName, duplicates); std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)storage)); if (!pair.second) @@ -154,10 +161,10 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, */ template LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { - if (opened) { + if (opened) throw Opened(name, "add cache " + storageName); - } - Cache* cache = new Cache(this, storageName, false); + + Cache* cache = new Cache(this, storageName, uniqueKey); std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)cache)); if (!pair.second) throw StorageDuplicate(name, storageName); diff --git a/src/cache.h b/src/cache.h index ff0d996..7a6bef2 100644 --- a/src/cache.h +++ b/src/cache.h @@ -51,7 +51,7 @@ class Cache : public Storage { typedef std::map TransactionCache; protected: - Cache(Base* parent, const std::string& name, bool duplicates = false); + Cache(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); ~Cache() override; virtual void transactionStarted(TransactionID txn, bool readOnly) const override; diff --git a/src/cache.hpp b/src/cache.hpp index 063b1bd..5f5edb9 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -41,10 +41,10 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) + * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) */ template -LMDBAL::Cache::Cache(Base* parent, const std::string& name, bool duplicates): +LMDBAL::Cache::Cache(Base* parent, const std::string& name, Duplicates duplicates): Storage(parent, name, duplicates), mode(Mode::nothing), cache(new std::map()), diff --git a/src/storage.cpp b/src/storage.cpp index 0f09886..28cd319 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -33,9 +33,9 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) + * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) */ -LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates): +LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, Duplicates duplicates): dbi(), db(parent), name(name), diff --git a/src/storage.h b/src/storage.h index bd1229a..adcc0f0 100644 --- a/src/storage.h +++ b/src/storage.h @@ -26,6 +26,7 @@ #include "cursor.h" class BaseTest; +class DuplicatesTest; namespace LMDBAL { @@ -34,9 +35,8 @@ typedef uint32_t SizeType; class iStorage { friend class Base; public: - protected: - iStorage(Base* parent, const std::string& name, bool duplicates = false); + iStorage(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); virtual ~iStorage(); /** @@ -81,7 +81,7 @@ protected: MDB_dbi dbi; /**<\brief lmdb storage handle*/ Base* db; /**<\brief parent database pointer (borrowed)*/ const std::string name; /**<\brief this storage name*/ - const bool duplicates; /**<\brief true if storage supports duplicates*/ + const Duplicates duplicates; /**<\brief true if storage supports duplicates*/ inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ @@ -99,7 +99,7 @@ protected: protected: template - int makeStorage(MDB_txn* transaction, bool duplicates); + int makeStorage(MDB_txn* transaction, Duplicates duplicates = uniqueKey); template static std::string toString(const T& value); @@ -108,10 +108,11 @@ protected: template class Storage : public iStorage { friend class ::BaseTest; + friend class ::DuplicatesTest; friend class Base; friend class Cursor; protected: - Storage(Base* parent, const std::string& name, bool duplicates = false); + Storage(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); ~Storage() override; virtual void discoveredRecord(const K& key, const V& value) const; diff --git a/src/storage.hpp b/src/storage.hpp index d1dcbae..3f5296f 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -43,10 +43,10 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - true if storage supports duplicates, false otherwise (false by default) + * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) */ template -LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool duplicates): +LMDBAL::Storage::Storage(Base* parent, const std::string& name, Duplicates duplicates): iStorage(parent, name, duplicates), keySerializer(), valueSerializer(), @@ -111,7 +111,20 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData = valueSerializer.setData(value); - int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + unsigned int flags; + switch (duplicates) { + case uniqueKey: + flags = MDB_NOOVERWRITE; + break; + case uniquePair: + flags = MDB_NODUPDATA; + break; + case full: + flags = 0; + break; + } + + int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, flags); if (rc != MDB_SUCCESS) throwDuplicateOrUnknown(rc, toString(key)); } @@ -804,16 +817,19 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans * This infrastructure also allowes us to customize mdb_dbi_open call in the future */ template -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, Duplicates duplicates) { unsigned int flags = MDB_CREATE; if constexpr (std::is_integral::value) flags |= MDB_INTEGERKEY; - if (duplicates) { + if (duplicates != uniqueKey) { flags |= MDB_DUPSORT; - if constexpr (std::is_integral::value) - flags |= MDB_INTEGERDUP | MDB_DUPFIXED; + if constexpr (std::is_scalar::value && std::is_scalar::value) + flags |= MDB_DUPFIXED; + else if constexpr (std::is_integral::value) + flags |= MDB_INTEGERDUP; + } return mdb_dbi_open(transaction, name.c_str(), flags, &dbi); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b2b1eba..ab59a88 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(runUnitTests cachetransaction.cpp storagecursor.cpp cachecursor.cpp + duplicates.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/duplicates.cpp b/test/duplicates.cpp new file mode 100644 index 0000000..f25feba --- /dev/null +++ b/test/duplicates.cpp @@ -0,0 +1,90 @@ +#include + +#include "base.h" +#include "storage.h" + +class DuplicatesTest : public ::testing::Test { +protected: + DuplicatesTest(): + ::testing::Test(), + t1(db->getStorage("sameSizeInts")), + t2(db->getStorage("stringInt")), + t3(db->getStorage("differentSizeInts")), + t4(db->getStorage("intDouble")) {} + + ~DuplicatesTest() {} + + uint32_t getT1Flags() const {return t1->flags();} + uint32_t getT2Flags() const {return t2->flags();} + uint32_t getT3Flags() const {return t3->flags();} + uint32_t getT4Flags() const {return t4->flags();} + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("testBase"); + db->addStorage("sameSizeInts", LMDBAL::uniquePair); + db->addStorage("stringInt", LMDBAL::uniquePair); + db->addStorage("differentSizeInts", LMDBAL::uniquePair); + db->addStorage("intDouble", LMDBAL::uniquePair); + + db->open(); + } + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static LMDBAL::Base* db; + + LMDBAL::Storage* t1; + LMDBAL::Storage* t2; + LMDBAL::Storage* t3; + LMDBAL::Storage* t4; +}; + +LMDBAL::Base* DuplicatesTest::db = nullptr; + +TEST_F(DuplicatesTest, Flags) { + uint32_t t1Flags = getT1Flags(); + uint32_t t2Flags = getT2Flags(); + uint32_t t3Flags = getT3Flags(); + uint32_t t4Flags = getT4Flags(); + + EXPECT_TRUE(t1Flags & MDB_INTEGERKEY); + EXPECT_TRUE(t1Flags & MDB_DUPSORT); + EXPECT_TRUE(t1Flags & MDB_DUPFIXED); + EXPECT_FALSE(t1Flags & MDB_INTEGERDUP); + + EXPECT_FALSE(t2Flags & MDB_INTEGERKEY); + EXPECT_TRUE(t2Flags & MDB_DUPSORT); + EXPECT_FALSE(t2Flags & MDB_DUPFIXED); + EXPECT_TRUE(t2Flags & MDB_INTEGERDUP); + + EXPECT_TRUE(t3Flags & MDB_INTEGERKEY); + EXPECT_TRUE(t3Flags & MDB_DUPSORT); + EXPECT_TRUE(t3Flags & MDB_DUPFIXED); + EXPECT_FALSE(t3Flags & MDB_INTEGERDUP); + + EXPECT_TRUE(t4Flags & MDB_INTEGERKEY); + EXPECT_TRUE(t4Flags & MDB_DUPSORT); + EXPECT_TRUE(t4Flags & MDB_DUPFIXED); + EXPECT_FALSE(t4Flags & MDB_INTEGERDUP); +} + +TEST_F(DuplicatesTest, Adding) { + t1->addRecord(1, 1); + t1->addRecord(2, 2); + t1->addRecord(2, 1); + t1->addRecord(1, 2); + EXPECT_THROW(t1->addRecord(1, 1), LMDBAL::Exist); + EXPECT_THROW(t1->addRecord(1, 2), LMDBAL::Exist); + EXPECT_THROW(t1->addRecord(2, 2), LMDBAL::Exist); + + EXPECT_EQ(t1->count(), 4); + EXPECT_EQ(t1->getRecord(1), 1); + EXPECT_EQ(t1->getRecord(2), 1); +} From 180c40370cdd3a7765325a28baff3d2617dde17f Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 18 Aug 2023 10:31:30 -0300 Subject: [PATCH 052/125] more duplicates test, misinterpreted something about duplicates, had to fallback --- src/base.h | 14 ++--- src/cache.h | 2 +- src/cache.hpp | 4 +- src/storage.cpp | 4 +- src/storage.h | 8 +-- src/storage.hpp | 27 +++----- test/duplicates.cpp | 140 +++++++++++++++++++++++++++-------------- test/serialization.cpp | 16 +++-- 8 files changed, 126 insertions(+), 89 deletions(-) diff --git a/src/base.h b/src/base.h index f09330e..2b7353e 100644 --- a/src/base.h +++ b/src/base.h @@ -48,12 +48,6 @@ class Cache; typedef MDB_txn* TransactionID; /**AND value pairs allowed*/ - full /** - any configuration of duplicates goes*/ -}; - class Base { friend class iStorage; public: @@ -75,7 +69,7 @@ public: void abortTransaction(TransactionID id) const; template - LMDBAL::Storage* addStorage(const std::string& storageName, Duplicates duplicates = uniqueKey); + LMDBAL::Storage* addStorage(const std::string& storageName, bool duplicates = false); template LMDBAL::Cache* addCache(const std::string& storageName); @@ -121,7 +115,7 @@ private: * The LMDBAL::Base must be closed * * \param[in] storageName - storage name - * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) + * \param[in] duplicates - true if key duplicates are allowed (false by default) * * \returns storage pointer. LMDBAL::Base keeps the ownership of the added storage, you don't need to destoroy it * @@ -132,7 +126,7 @@ private: * \exception LMDBAL::StorageDuplicate thrown if somebody tries to add storage with repeating name */ template -LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, Duplicates duplicates) { +LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, bool duplicates) { if (opened) throw Opened(name, "add storage " + storageName); @@ -164,7 +158,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { if (opened) throw Opened(name, "add cache " + storageName); - Cache* cache = new Cache(this, storageName, uniqueKey); + Cache* cache = new Cache(this, storageName, false); std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)cache)); if (!pair.second) throw StorageDuplicate(name, storageName); diff --git a/src/cache.h b/src/cache.h index 7a6bef2..ff0d996 100644 --- a/src/cache.h +++ b/src/cache.h @@ -51,7 +51,7 @@ class Cache : public Storage { typedef std::map TransactionCache; protected: - Cache(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); + Cache(Base* parent, const std::string& name, bool duplicates = false); ~Cache() override; virtual void transactionStarted(TransactionID txn, bool readOnly) const override; diff --git a/src/cache.hpp b/src/cache.hpp index 5f5edb9..0f0470b 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -41,10 +41,10 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) + * \param[in] duplicates - true if key duplicates are allowed (false by default) */ template -LMDBAL::Cache::Cache(Base* parent, const std::string& name, Duplicates duplicates): +LMDBAL::Cache::Cache(Base* parent, const std::string& name, bool duplicates): Storage(parent, name, duplicates), mode(Mode::nothing), cache(new std::map()), diff --git a/src/storage.cpp b/src/storage.cpp index 28cd319..2f50cd4 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -33,9 +33,9 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) + * \param[in] duplicates - true if key duplicates are allowed (false by default) */ -LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, Duplicates duplicates): +LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates): dbi(), db(parent), name(name), diff --git a/src/storage.h b/src/storage.h index adcc0f0..46372e2 100644 --- a/src/storage.h +++ b/src/storage.h @@ -36,7 +36,7 @@ class iStorage { friend class Base; public: protected: - iStorage(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); + iStorage(Base* parent, const std::string& name, bool duplicates = false); virtual ~iStorage(); /** @@ -81,7 +81,7 @@ protected: MDB_dbi dbi; /**<\brief lmdb storage handle*/ Base* db; /**<\brief parent database pointer (borrowed)*/ const std::string name; /**<\brief this storage name*/ - const Duplicates duplicates; /**<\brief true if storage supports duplicates*/ + const bool duplicates; /**<\brief true if storage supports duplicates*/ inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ @@ -99,7 +99,7 @@ protected: protected: template - int makeStorage(MDB_txn* transaction, Duplicates duplicates = uniqueKey); + int makeStorage(MDB_txn* transaction, bool duplicates = false); template static std::string toString(const T& value); @@ -112,7 +112,7 @@ class Storage : public iStorage { friend class Base; friend class Cursor; protected: - Storage(Base* parent, const std::string& name, Duplicates duplicates = uniqueKey); + Storage(Base* parent, const std::string& name, bool duplicates = false); ~Storage() override; virtual void discoveredRecord(const K& key, const V& value) const; diff --git a/src/storage.hpp b/src/storage.hpp index 3f5296f..8e63d61 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -43,10 +43,10 @@ * * \param[in] parent - LMDBAL::Base pointer for the owning database (borrowed) * \param[in] name - the name of the storage - * \param[in] duplicates - LMDBAL::Duplicates duplicates mode (uniqueKey by default) + * \param[in] duplicates - true if key duplicates are allowed (false by default) */ template -LMDBAL::Storage::Storage(Base* parent, const std::string& name, Duplicates duplicates): +LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool duplicates): iStorage(parent, name, duplicates), keySerializer(), valueSerializer(), @@ -111,18 +111,11 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData = valueSerializer.setData(value); - unsigned int flags; - switch (duplicates) { - case uniqueKey: - flags = MDB_NOOVERWRITE; - break; - case uniquePair: - flags = MDB_NODUPDATA; - break; - case full: - flags = 0; - break; - } + unsigned int flags = 0; + if (duplicates) + flags |= MDB_NODUPDATA; + else + flags |= MDB_NOOVERWRITE; int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, flags); if (rc != MDB_SUCCESS) @@ -808,7 +801,7 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans * \tparam K type of keys in opening storage * * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! - * \param[in] duplicates - true if you wish to enable duplicates support for the storage + * \param[in] duplicates - true if key duplicates are allowed (false by default) * * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code * @@ -817,12 +810,12 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans * This infrastructure also allowes us to customize mdb_dbi_open call in the future */ template -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, Duplicates duplicates) { +inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) { unsigned int flags = MDB_CREATE; if constexpr (std::is_integral::value) flags |= MDB_INTEGERKEY; - if (duplicates != uniqueKey) { + if (duplicates) { flags |= MDB_DUPSORT; if constexpr (std::is_scalar::value && std::is_scalar::value) diff --git a/test/duplicates.cpp b/test/duplicates.cpp index f25feba..b65acba 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -7,25 +7,25 @@ class DuplicatesTest : public ::testing::Test { protected: DuplicatesTest(): ::testing::Test(), - t1(db->getStorage("sameSizeInts")), - t2(db->getStorage("stringInt")), - t3(db->getStorage("differentSizeInts")), - t4(db->getStorage("intDouble")) {} + tu1(db->getStorage("sameSizeInts")), + tu2(db->getStorage("stringInt")), + tu3(db->getStorage("floatFloat")), + tu4(db->getStorage("intDouble")) {} ~DuplicatesTest() {} - uint32_t getT1Flags() const {return t1->flags();} - uint32_t getT2Flags() const {return t2->flags();} - uint32_t getT3Flags() const {return t3->flags();} - uint32_t getT4Flags() const {return t4->flags();} + uint32_t getTU1Flags() const {return tu1->flags();} + uint32_t getTU2Flags() const {return tu2->flags();} + uint32_t getTU3Flags() const {return tu3->flags();} + uint32_t getTU4Flags() const {return tu4->flags();} static void SetUpTestSuite() { if (db == nullptr) { db = new LMDBAL::Base("testBase"); - db->addStorage("sameSizeInts", LMDBAL::uniquePair); - db->addStorage("stringInt", LMDBAL::uniquePair); - db->addStorage("differentSizeInts", LMDBAL::uniquePair); - db->addStorage("intDouble", LMDBAL::uniquePair); + db->addStorage("sameSizeInts", true); + db->addStorage("stringInt", true); + db->addStorage("floatFloat", true); + db->addStorage("intDouble", true); db->open(); } @@ -40,51 +40,95 @@ protected: static LMDBAL::Base* db; - LMDBAL::Storage* t1; - LMDBAL::Storage* t2; - LMDBAL::Storage* t3; - LMDBAL::Storage* t4; + LMDBAL::Storage* tu1; + LMDBAL::Storage* tu2; + LMDBAL::Storage* tu3; + LMDBAL::Storage* tu4; }; LMDBAL::Base* DuplicatesTest::db = nullptr; -TEST_F(DuplicatesTest, Flags) { - uint32_t t1Flags = getT1Flags(); - uint32_t t2Flags = getT2Flags(); - uint32_t t3Flags = getT3Flags(); - uint32_t t4Flags = getT4Flags(); +TEST_F(DuplicatesTest, FlagsUnique) { + uint32_t tu1Flags = getTU1Flags(); + uint32_t tu2Flags = getTU2Flags(); + uint32_t tu3Flags = getTU3Flags(); + uint32_t tu4Flags = getTU4Flags(); - EXPECT_TRUE(t1Flags & MDB_INTEGERKEY); - EXPECT_TRUE(t1Flags & MDB_DUPSORT); - EXPECT_TRUE(t1Flags & MDB_DUPFIXED); - EXPECT_FALSE(t1Flags & MDB_INTEGERDUP); + EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY); + EXPECT_TRUE(tu1Flags & MDB_DUPSORT); + EXPECT_TRUE(tu1Flags & MDB_DUPFIXED); + EXPECT_FALSE(tu1Flags & MDB_INTEGERDUP); - EXPECT_FALSE(t2Flags & MDB_INTEGERKEY); - EXPECT_TRUE(t2Flags & MDB_DUPSORT); - EXPECT_FALSE(t2Flags & MDB_DUPFIXED); - EXPECT_TRUE(t2Flags & MDB_INTEGERDUP); + EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY); + EXPECT_TRUE(tu2Flags & MDB_DUPSORT); + EXPECT_FALSE(tu2Flags & MDB_DUPFIXED); + EXPECT_TRUE(tu2Flags & MDB_INTEGERDUP); - EXPECT_TRUE(t3Flags & MDB_INTEGERKEY); - EXPECT_TRUE(t3Flags & MDB_DUPSORT); - EXPECT_TRUE(t3Flags & MDB_DUPFIXED); - EXPECT_FALSE(t3Flags & MDB_INTEGERDUP); + EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY); + EXPECT_TRUE(tu3Flags & MDB_DUPSORT); + EXPECT_TRUE(tu3Flags & MDB_DUPFIXED); + EXPECT_FALSE(tu3Flags & MDB_INTEGERDUP); - EXPECT_TRUE(t4Flags & MDB_INTEGERKEY); - EXPECT_TRUE(t4Flags & MDB_DUPSORT); - EXPECT_TRUE(t4Flags & MDB_DUPFIXED); - EXPECT_FALSE(t4Flags & MDB_INTEGERDUP); + EXPECT_TRUE(tu4Flags & MDB_INTEGERKEY); + EXPECT_TRUE(tu4Flags & MDB_DUPSORT); + EXPECT_TRUE(tu4Flags & MDB_DUPFIXED); + EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP); } -TEST_F(DuplicatesTest, Adding) { - t1->addRecord(1, 1); - t1->addRecord(2, 2); - t1->addRecord(2, 1); - t1->addRecord(1, 2); - EXPECT_THROW(t1->addRecord(1, 1), LMDBAL::Exist); - EXPECT_THROW(t1->addRecord(1, 2), LMDBAL::Exist); - EXPECT_THROW(t1->addRecord(2, 2), LMDBAL::Exist); +TEST_F(DuplicatesTest, AddingPairUnique) { + tu1->addRecord(1, 1); + tu1->addRecord(2, 2); + tu1->addRecord(2, 1); + tu1->addRecord(1, 2); + EXPECT_THROW(tu1->addRecord(1, 1), LMDBAL::Exist); + EXPECT_THROW(tu1->addRecord(1, 2), LMDBAL::Exist); + EXPECT_THROW(tu1->addRecord(2, 2), LMDBAL::Exist); - EXPECT_EQ(t1->count(), 4); - EXPECT_EQ(t1->getRecord(1), 1); - EXPECT_EQ(t1->getRecord(2), 1); + EXPECT_EQ(tu1->count(), 4); + EXPECT_EQ(tu1->getRecord(1), 1); + EXPECT_EQ(tu1->getRecord(2), 1); + + tu2->addRecord("brass boulers", -54); + tu2->addRecord("grief ", 61); + tu2->addRecord("grief ", 19); + tu2->addRecord("grief ", 32); + tu2->addRecord("miracles of a lunch", 44); + tu2->addRecord("miracles of a lunch", 102); + tu2->addRecord("miracles of a lunch", -72); + + EXPECT_THROW(tu2->addRecord("grief ", 19), LMDBAL::Exist); + EXPECT_THROW(tu2->addRecord("brass boulers", -54), LMDBAL::Exist); + EXPECT_EQ(tu2->count(), 7); + EXPECT_EQ(tu2->getRecord("grief "), 19); + EXPECT_EQ(tu2->getRecord("miracles of a lunch"), 44); //apparently ints are compared as uints + + tu3->addRecord(7.2, 697); + tu3->addRecord(5119, -998.53); + tu3->addRecord(7.2001, 4); + tu3->addRecord(7.2, -113); + tu3->addRecord(7.2, -53.5478); + float tu3ds = 0.432924; + tu3->addRecord(5119, tu3ds); + + EXPECT_THROW(tu3->addRecord(5119, -998.53), LMDBAL::Exist); + EXPECT_THROW(tu3->addRecord(7.2001, 4), LMDBAL::Exist); + tu3->addRecord(7.20001, 4.00000001); //not sure how exactly, but it works + + EXPECT_EQ(tu3->count(), 7); + EXPECT_EQ(tu3->getRecord(7.2), -113); + float tu3dd = tu3->getRecord(5119); + EXPECT_TRUE(tu3ds == tu3dd); + EXPECT_EQ(tu3ds, tu3dd); + + tu4->addRecord(327, 463.28348); + tu4->addRecord(327, 79.624923); + tu4->addRecord(172, 0.00001); + tu4->addRecord(172, 0.00000001); + EXPECT_THROW(tu4->addRecord(172, 0.00000001), LMDBAL::Exist); + EXPECT_THROW(tu4->addRecord(172, 0.00001), LMDBAL::Exist); + EXPECT_THROW(tu4->addRecord(327, 79.624923), LMDBAL::Exist); + + EXPECT_EQ(tu4->count(), 4); + EXPECT_EQ(tu4->getRecord(172), 0.00000001); + EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically } diff --git a/test/serialization.cpp b/test/serialization.cpp index b413d42..9fe162e 100644 --- a/test/serialization.cpp +++ b/test/serialization.cpp @@ -4,12 +4,13 @@ #include TEST(Serialization, Double) { - double source = 5344.6542; + double source1 = 5344.6542; + double source2 = 0.4329248; LMDBAL::Serializer serializer; - LMDBAL::Serializer serializer2(source); + LMDBAL::Serializer serializer2(source1); LMDBAL::Serializer deserializer; - serializer.setData(source); + serializer.setData(source1); MDB_val data = serializer.getData(); MDB_val data2 = serializer2.getData(); @@ -19,11 +20,16 @@ TEST(Serialization, Double) { double destination; serializer.deserialize(data, destination); - EXPECT_DOUBLE_EQ(source, destination); + EXPECT_DOUBLE_EQ(source1, destination); double dest2 = serializer.deserialize(data); - EXPECT_DOUBLE_EQ(source, dest2); + EXPECT_DOUBLE_EQ(source1, dest2); + + data = serializer.setData(source2); + serializer.deserialize(data, destination); + + EXPECT_DOUBLE_EQ(source2, destination); } TEST(Serialization, Float) { From f00f017b16b57f84174aedd7c558a9efa27e6b50 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 18 Aug 2023 12:04:37 -0300 Subject: [PATCH 053/125] got down to the MDB_DUP... flags, some more tests, some docs --- src/storage.hpp | 47 +++++++++++++++++++++++++++++++++++---------- test/duplicates.cpp | 39 ++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/storage.hpp b/src/storage.hpp index 8e63d61..601449c 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -65,7 +65,9 @@ LMDBAL::Storage::~Storage() { /** * \brief Adds a key-value record to the storage * - * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown. + * If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database. + * If your storage supports duplicates LMDBAL::Exist is thrown only if the record with the same key AND already exists in the database. * * \param[in] key key of the record * \param[in] value value of the record @@ -94,7 +96,9 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { * This function schedules an addition of a key-value record, but doesn't immidiately adds it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * - * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown. + * If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database. + * If your storage supports duplicates LMDBAL::Exist is thrown only if the record with the same key AND already exists in the database. * * \param[in] key key of the record * \param[in] value value of the record @@ -259,7 +263,7 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti /** * \brief Gets the record from the database - * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * * \param[in] key key of the record you look for * \returns the value from the storage @@ -279,8 +283,14 @@ V LMDBAL::Storage::getRecord(const K& key) const { /** * \brief Gets the record from the database (reference variant) - - * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown + * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for * \param[out] value the value from the storage @@ -310,7 +320,13 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * - * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown + * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for * \param[in] txn transaction ID, can be read only transaction @@ -335,7 +351,13 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * - * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown + * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for * \param[out] value the value from the storage @@ -818,11 +840,16 @@ inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) if (duplicates) { flags |= MDB_DUPSORT; - if constexpr (std::is_scalar::value && std::is_scalar::value) + if constexpr (std::is_scalar::value) flags |= MDB_DUPFIXED; - else if constexpr (std::is_integral::value) - flags |= MDB_INTEGERDUP; + if constexpr ( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode + flags |= MDB_INTEGERDUP; } return mdb_dbi_open(transaction, name.c_str(), flags, &dbi); diff --git a/test/duplicates.cpp b/test/duplicates.cpp index b65acba..7ffa2fa 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -1,5 +1,7 @@ #include +#include + #include "base.h" #include "storage.h" @@ -10,7 +12,8 @@ protected: tu1(db->getStorage("sameSizeInts")), tu2(db->getStorage("stringInt")), tu3(db->getStorage("floatFloat")), - tu4(db->getStorage("intDouble")) {} + tu4(db->getStorage("intDouble")), + tu5(db->getStorage("floatLong")) {} ~DuplicatesTest() {} @@ -18,6 +21,7 @@ protected: uint32_t getTU2Flags() const {return tu2->flags();} uint32_t getTU3Flags() const {return tu3->flags();} uint32_t getTU4Flags() const {return tu4->flags();} + uint32_t getTU5Flags() const {return tu5->flags();} static void SetUpTestSuite() { if (db == nullptr) { @@ -26,6 +30,7 @@ protected: db->addStorage("stringInt", true); db->addStorage("floatFloat", true); db->addStorage("intDouble", true); + db->addStorage("floatLong", true); db->open(); } @@ -44,6 +49,7 @@ protected: LMDBAL::Storage* tu2; LMDBAL::Storage* tu3; LMDBAL::Storage* tu4; + LMDBAL::Storage* tu5; }; LMDBAL::Base* DuplicatesTest::db = nullptr; @@ -53,6 +59,7 @@ TEST_F(DuplicatesTest, FlagsUnique) { uint32_t tu2Flags = getTU2Flags(); uint32_t tu3Flags = getTU3Flags(); uint32_t tu4Flags = getTU4Flags(); + uint32_t tu5Flags = getTU5Flags(); EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY); EXPECT_TRUE(tu1Flags & MDB_DUPSORT); @@ -61,8 +68,8 @@ TEST_F(DuplicatesTest, FlagsUnique) { EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY); EXPECT_TRUE(tu2Flags & MDB_DUPSORT); - EXPECT_FALSE(tu2Flags & MDB_DUPFIXED); - EXPECT_TRUE(tu2Flags & MDB_INTEGERDUP); + EXPECT_TRUE(tu2Flags & MDB_DUPFIXED); + EXPECT_FALSE(tu2Flags & MDB_INTEGERDUP); EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY); EXPECT_TRUE(tu3Flags & MDB_DUPSORT); @@ -73,6 +80,11 @@ TEST_F(DuplicatesTest, FlagsUnique) { EXPECT_TRUE(tu4Flags & MDB_DUPSORT); EXPECT_TRUE(tu4Flags & MDB_DUPFIXED); EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP); + + EXPECT_FALSE(tu5Flags & MDB_INTEGERKEY); + EXPECT_TRUE(tu5Flags & MDB_DUPSORT); + EXPECT_TRUE(tu5Flags & MDB_DUPFIXED); + EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP); } TEST_F(DuplicatesTest, AddingPairUnique) { @@ -131,4 +143,25 @@ TEST_F(DuplicatesTest, AddingPairUnique) { EXPECT_EQ(tu4->count(), 4); EXPECT_EQ(tu4->getRecord(172), 0.00000001); EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically + + tu5->addRecord(-84.7, 45656753); + EXPECT_THROW(tu5->addRecord(-84.7, 45656753), LMDBAL::Exist); + tu5->addRecord(-84.7, 45656754); + int64_t intMax = std::numeric_limits::max(); + int64_t intMin = std::numeric_limits::min(); + int64_t longMax = std::numeric_limits::max(); + int64_t longMin = std::numeric_limits::min(); + + tu5->addRecord(52.87, intMax); + EXPECT_THROW(tu5->addRecord(52.87, intMax), LMDBAL::Exist); + tu5->addRecord(52.87, intMin); + EXPECT_THROW(tu5->addRecord(52.87, intMin), LMDBAL::Exist); + tu5->addRecord(52.87, longMax); + EXPECT_THROW(tu5->addRecord(52.87, longMax), LMDBAL::Exist); + tu5->addRecord(52.87, longMin); + EXPECT_THROW(tu5->addRecord(52.87, longMin), LMDBAL::Exist); + + EXPECT_EQ(tu5->count(), 6); + EXPECT_EQ(tu5->getRecord(-84.7), 45656753); + EXPECT_EQ(tu5->getRecord(52.87), intMax); } From 2d4069256067541dfad13e0b991b7ee8d14b3780 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 16:25:52 -0300 Subject: [PATCH 054/125] duplicates handling for forceRecord and changeRecord methods, tests and docs --- src/base.h | 1 + src/storage.h | 3 +- src/storage.hpp | 137 +++++++++++++++++++++++++----------- test/duplicates.cpp | 165 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 263 insertions(+), 43 deletions(-) diff --git a/src/base.h b/src/base.h index 2b7353e..fd5ba18 100644 --- a/src/base.h +++ b/src/base.h @@ -47,6 +47,7 @@ template class Cache; typedef MDB_txn* TransactionID; /** +#include #include "base.h" #include "serializer.h" @@ -30,8 +31,6 @@ class DuplicatesTest; namespace LMDBAL { -typedef uint32_t SizeType; - class iStorage { friend class Base; public: diff --git a/src/storage.hpp b/src/storage.hpp index 601449c..8a8ef31 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -93,7 +93,7 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { /** * \brief Adds a key-value record to the storage (transaction variant) * - * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * This method schedules an addition of a key-value record, but doesn't immidiately adds it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown. @@ -128,8 +128,15 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI /** * \brief Adds a key-value record to the storage, overwrites if it already exists + * + * This method is mostly useless in duplicates mode. + * In this mode it basically does the same thing LMDBAL::Storage::addRecord() does, + * but suppresses LMDBAL::Exist exception if the record with the same key-value pair existed in the storage. + * In this case just false is returned from the method. + * * \param[in] key key of the record * \param[in] value value of the record + * * \returns true if the record was added, false otherwise * * \exception LMDBAL::Closed thrown if the database was not opened @@ -155,10 +162,15 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { /** * \brief Adds a key-value record to the storage, overwrites if it already exists (transaction variant) * - * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * This method schedules an addition of a key-value record, but doesn't immidiately add it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::Base::commitTransaction(). * + * This method is mostly useless in duplicates mode. + * In this mode it basically does the same thing LMDBAL::Storage::addRecord() does, + * but suppresses LMDBAL::Exist exception if the record with the same key-value pair existed in the storage. + * In this case just false is returned from the method. + * * \param[in] key key of the record * \param[in] value value of the record * \param[in] txn transaction ID, needs to be a writable transaction! @@ -172,27 +184,35 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio ensureOpened(forceRecordMethodName); bool added; - MDB_val lmdbKey = keySerializer.setData(key); - MDB_val lmdbData; - - int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); - switch (rc) { - case MDB_SUCCESS: - added = false; - break; - case MDB_NOTFOUND: + if (duplicates) { + try { + addRecord(key, value, txn); added = true; - break; - default: + } catch (const LMDBAL::Exist& e) { added = false; + } + } else { + MDB_val lmdbKey = keySerializer.setData(key); + MDB_val lmdbData; + + int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + switch (rc) { + case MDB_SUCCESS: + added = false; + break; + case MDB_NOTFOUND: + added = true; + break; + default: + added = false; + throwUnknown(rc); + } + + lmdbData = valueSerializer.setData(value); + rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + if (rc != MDB_SUCCESS) throwUnknown(rc); } - - lmdbData = valueSerializer.setData(value); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); - if (rc != MDB_SUCCESS) - throwUnknown(rc); - return added; } @@ -201,10 +221,17 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * + * If duplicates mode is enabled this function will find the first entry of the key + * (which is pretty couterintuitive, see LMDBAL::Storage::getRecord() description) + * and change it's value to the given one. + * If the given value matches some of the other values for given key the method will throw LMDBAL::Exist, + * if no key was found it will still throw LMDBAL::NotFound. + * * \param[in] key key of the record * \param[in] value new value of the record * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Exist thrown in duplicates mode when the given value matches some of existing values for the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ @@ -226,16 +253,23 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { /** * \brief Changes key-value record to the storage (transaction variant) * - * This function schedules a modification of a key-value record, but doesn't immidiately changes it. + * This method schedules a modification of a key-value record, but doesn't immidiately changes it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * + * If duplicates mode is enabled this function will find the first entry of the key + * (which is pretty couterintuitive, see LMDBAL::Storage::getRecord() description) + * and change it's value to the given one. + * If the given value matches some of the other values for given key the method will throw LMDBAL::Exist, + * if no key was found it will still throw LMDBAL::NotFound. + * * \param[in] key key of the record * \param[in] value new value of the record * \param[in] txn transaction ID, needs to be a writable transaction! * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Exist thrown in duplicates mode when the given value matches some of existing values for the given key * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ @@ -249,15 +283,34 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti throwUnknown(rc); MDB_val lmdbKey = keySerializer.setData(key); - rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET); + MDB_val lmdbData; + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); - MDB_val lmdbData = valueSerializer.setData(value); - rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); + MDB_val lmdbNewData = valueSerializer.setData(value); + bool sameSize = lmdbData.mv_size == lmdbNewData.mv_size; + int firstDifferentByte = 0; + if (sameSize) { //can compare only if they are the same size + firstDifferentByte = memcmp(lmdbData.mv_data, lmdbNewData.mv_data, lmdbData.mv_size); + if (firstDifferentByte == 0) { //old and new is the same, nothing to do + mdb_cursor_close(cursor); + return; + } + } + + unsigned int flags = MDB_CURRENT; + if (duplicates && (!sameSize || firstDifferentByte < 0)) { //if new value is greater than the old one + rc = mdb_cursor_del(cursor, 0); //we need to initiate duplicates sort, for it to be in the correct place + flags = MDB_NODUPDATA; + } + + if (rc == MDB_SUCCESS) + rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbNewData, flags); + mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) - throwUnknown(rc); + throwDuplicateOrUnknown(rc, toString(key)); } /** @@ -265,6 +318,12 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" + * * \param[in] key key of the record you look for * \returns the value from the storage * @@ -287,7 +346,7 @@ V LMDBAL::Storage::getRecord(const K& key) const { * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. - * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" @@ -323,7 +382,7 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. - * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" @@ -354,7 +413,7 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. - * It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" @@ -540,7 +599,7 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c /** * \brief Replaces the content of the whole storage with the given * - * Basically this function drops the database and adds all the records from the given map + * Basically this method drops the database and adds all the records from the given map * * \param[in] data new data of the storage * @@ -565,8 +624,8 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { /** * \brief Replaces the content of the whole storage with the given (transaction variant) * - * Basically this function drops the database and adds all the records from the given map - * This function schedules a data replacement, but doesn't immidiately execute it. + * Basically this method drops the database and adds all the records from the given map + * This method schedules a data replacement, but doesn't immidiately execute it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] data new data of the storage @@ -598,7 +657,7 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID * \brief Adds records in bulk * * \param[in] data the data to be added - * \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it + * \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it * \returns new actual amount of records in the storage * * \exception LMDBAL::Closed thrown if the database was not opened @@ -625,12 +684,12 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool over /** * \brief Adds records in bulk (transaction variant) * - * This function schedules a data addition, but doesn't immidiately execute it. + * This method schedules a data addition, but doesn't immidiately execute it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] data the data to be added * \param[in] txn transaction ID, needs to be a writable transaction! - * \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it + * \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it * \returns new actual amount of records in the storage * * \exception LMDBAL::Closed thrown if the database was not opened @@ -693,7 +752,7 @@ void LMDBAL::Storage::removeRecord(const K& key) { * \brief Removes one of the records (transaction variant) * * Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown - * This function schedules a record removal, but doesn't immidiately execute it. + * This method schedules a record removal, but doesn't immidiately execute it. * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] key key of the record you wish to be removed @@ -714,7 +773,7 @@ void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { } /** - * \brief A private virtual function I need to open each storage in the database + * \brief A private virtual method I need to open each storage in the database * * \param[in] transaction - lmdb transaction to call mdb_dbi_open * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code @@ -725,7 +784,7 @@ int LMDBAL::Storage::open(MDB_txn* transaction) { } /** - * \brief A private virtual function I need to close each storage in the database + * \brief A private virtual method I need to close each storage in the database */ template void LMDBAL::Storage::close() { @@ -751,7 +810,7 @@ LMDBAL::Cursor* LMDBAL::Storage::createCursor() { /** * \brief Reads current storage flags it was opened with * - * This function exists mostly for testing purposes + * This method exists mostly for testing purposes * * \returns Third out parameter of mdb_dbi_flags function * @@ -792,7 +851,7 @@ void LMDBAL::Storage::destroyCursor(Cursor* cursor) { } /** - * \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache + * \brief A private virtual method that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache * * \param[in] key a key of discovered record * \param[in] value a value of discovered record @@ -804,7 +863,7 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value) const } /** - * \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache + * \brief A private virtual method that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache * * \param[in] key a key of discovered record * \param[in] value a value of discovered record diff --git a/test/duplicates.cpp b/test/duplicates.cpp index 7ffa2fa..d84ba2b 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -54,7 +54,7 @@ protected: LMDBAL::Base* DuplicatesTest::db = nullptr; -TEST_F(DuplicatesTest, FlagsUnique) { +TEST_F(DuplicatesTest, Flags) { uint32_t tu1Flags = getTU1Flags(); uint32_t tu2Flags = getTU2Flags(); uint32_t tu3Flags = getTU3Flags(); @@ -87,7 +87,7 @@ TEST_F(DuplicatesTest, FlagsUnique) { EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP); } -TEST_F(DuplicatesTest, AddingPairUnique) { +TEST_F(DuplicatesTest, Adding) { tu1->addRecord(1, 1); tu1->addRecord(2, 2); tu1->addRecord(2, 1); @@ -165,3 +165,164 @@ TEST_F(DuplicatesTest, AddingPairUnique) { EXPECT_EQ(tu5->getRecord(-84.7), 45656753); EXPECT_EQ(tu5->getRecord(52.87), intMax); } + +TEST_F(DuplicatesTest, Forcing) { + LMDBAL::SizeType tu1Size = tu1->count(); + tu1->addRecord(-56, 71); + tu1->addRecord(-56, 274); + tu1->addRecord(-56, 732); + EXPECT_EQ(tu1->count(), tu1Size += 3); + EXPECT_TRUE(tu1->forceRecord(-56, 322)); + EXPECT_EQ(tu1->count(), tu1Size += 1); + EXPECT_EQ(tu1->getRecord(-56), 274); //like yeah, it's really counterintuitive, since it's compared byte by byte + EXPECT_TRUE(tu1->forceRecord(-56, 14)); + EXPECT_EQ(tu1->count(), tu1Size += 1); + EXPECT_EQ(tu1->getRecord(-56), 14); + EXPECT_FALSE(tu1->forceRecord(-56, 274)); + EXPECT_EQ(tu1->count(), tu1Size); + + LMDBAL::SizeType tu2Size = tu2->count(); + tu2->addRecord("printable", -2); + tu2->addRecord("printable", 4); + EXPECT_EQ(tu2->count(), tu2Size += 2); + EXPECT_TRUE(tu2->forceRecord("printable", 18)); + EXPECT_EQ(tu2->count(), tu2Size += 1); + EXPECT_EQ(tu2->getRecord("printable"), 4); + EXPECT_TRUE(tu2->forceRecord("printable", 3)); + EXPECT_EQ(tu2->count(), tu2Size += 1); + EXPECT_EQ(tu2->getRecord("printable"), 3); + EXPECT_FALSE(tu2->forceRecord("printable", 4)); + EXPECT_EQ(tu2->count(), tu2Size); + + LMDBAL::SizeType tu3Size = tu3->count(); + tu3->addRecord(17.3, 93.21); + tu3->addRecord(17.3, 6.6); + tu3->addRecord(17.3, 105.1); + EXPECT_EQ(tu3->count(), tu3Size += 3); + EXPECT_TRUE(tu3->forceRecord(17.3, 74.9)); + EXPECT_EQ(tu3->count(), tu3Size += 1); + EXPECT_EQ(tu3->getRecord(17.3), 105.1f); //here too, really one should not use this function with duplicates, + EXPECT_TRUE(tu3->forceRecord(17.3, 5.1)); //unless he wishes for kinda randomish result + EXPECT_EQ(tu3->count(), tu3Size += 1); + EXPECT_EQ(tu3->getRecord(17.3), 5.1f); + EXPECT_FALSE(tu3->forceRecord(17.3, 93.21)); + EXPECT_EQ(tu3->count(), tu3Size); + + LMDBAL::SizeType tu4Size = tu4->count(); + tu4->addRecord(84, -359.109); + tu4->addRecord(84, 2879.654); + EXPECT_EQ(tu4->count(), tu4Size += 2); + EXPECT_TRUE(tu4->forceRecord(84, 72.9)); + EXPECT_EQ(tu4->count(), tu4Size += 1); + EXPECT_EQ(tu4->getRecord(84), 2879.654); + EXPECT_TRUE(tu4->forceRecord(84, 2679.5)); + EXPECT_EQ(tu4->count(), tu4Size += 1); + EXPECT_EQ(tu4->getRecord(84), 2679.5); + EXPECT_FALSE(tu4->forceRecord(84, -359.109)); + EXPECT_EQ(tu4->count(), tu4Size); + + LMDBAL::SizeType tu5Size = tu5->count(); + tu5->addRecord(0.45, -85645); + tu5->addRecord(0.45, 10573); + tu5->addRecord(0.45, 573); + tu5->addRecord(0.45, 73285); + EXPECT_EQ(tu5->count(), tu5Size += 4); + EXPECT_TRUE(tu5->forceRecord(0.45, -473)); + EXPECT_EQ(tu5->count(), tu5Size += 1); + EXPECT_EQ(tu5->getRecord(0.45), 573); + EXPECT_TRUE(tu5->forceRecord(0.45, 394)); + EXPECT_EQ(tu5->count(), tu5Size += 1); + EXPECT_EQ(tu5->getRecord(0.45), 394); + EXPECT_FALSE(tu5->forceRecord(0.45, 10573)); + EXPECT_EQ(tu5->count(), tu5Size); +} + +TEST_F(DuplicatesTest, Changing) { + LMDBAL::SizeType tu1Size = tu1->count(); + EXPECT_THROW(tu1->changeRecord(-31, 53), LMDBAL::NotFound); + EXPECT_EQ(tu1->count(), tu1Size); + tu1->addRecord(-31, 53); + EXPECT_EQ(tu1->count(), tu1Size += 1); + EXPECT_EQ(tu1->getRecord(-31), 53); + tu1->changeRecord(-31, 53); //should just do nothing usefull, but work normally + EXPECT_EQ(tu1->getRecord(-31), 53); + tu1->changeRecord(-31, 19); + EXPECT_EQ(tu1->count(), tu1Size); + EXPECT_EQ(tu1->getRecord(-31), 19); + tu1->addRecord(-31, 60); + EXPECT_EQ(tu1->count(), tu1Size += 1); + EXPECT_EQ(tu1->getRecord(-31), 19); + tu1->changeRecord(-31, 16); + EXPECT_EQ(tu1->count(), tu1Size); + EXPECT_EQ(tu1->getRecord(-31), 16); + tu1->changeRecord(-31, 203); + EXPECT_EQ(tu1->count(), tu1Size); + EXPECT_EQ(tu1->getRecord(-31), 60); + EXPECT_THROW(tu1->changeRecord(-31, 203), LMDBAL::Exist); + + LMDBAL::SizeType tu2Size = tu2->count(); + EXPECT_THROW(tu2->changeRecord("jeremy spins", -5), LMDBAL::NotFound); + EXPECT_EQ(tu2->count(), tu2Size); + tu2->addRecord("jeremy spins", -5); + EXPECT_EQ(tu2->count(), tu2Size += 1); + EXPECT_EQ(tu2->getRecord("jeremy spins"), -5); + tu2->changeRecord("jeremy spins", -5); //should just do nothing usefull, but work normally + EXPECT_EQ(tu2->getRecord("jeremy spins"), -5); + tu2->changeRecord("jeremy spins", 11); + EXPECT_EQ(tu2->count(), tu2Size); + EXPECT_EQ(tu2->getRecord("jeremy spins"), 11); + tu2->addRecord("jeremy spins", 24); + EXPECT_EQ(tu2->count(), tu2Size += 1); + EXPECT_EQ(tu2->getRecord("jeremy spins"), 11); + tu2->changeRecord("jeremy spins", 4); + EXPECT_EQ(tu2->count(), tu2Size); + EXPECT_EQ(tu2->getRecord("jeremy spins"), 4); + tu2->changeRecord("jeremy spins", -7); + EXPECT_EQ(tu2->count(), tu2Size); + EXPECT_EQ(tu2->getRecord("jeremy spins"), 24); //cuz it's compared as usigned down there + EXPECT_THROW(tu2->changeRecord("jeremy spins", -7), LMDBAL::Exist); + + LMDBAL::SizeType tu3Size = tu3->count(); + EXPECT_THROW(tu3->changeRecord(26.7, 68.22), LMDBAL::NotFound); + EXPECT_EQ(tu3->count(), tu3Size); + tu3->addRecord(26.7, 68.22); + EXPECT_EQ(tu3->count(), tu3Size += 1); + EXPECT_EQ(tu3->getRecord(26.7), 68.22f); + tu3->changeRecord(26.7, 68.22); //should just do nothing usefull, but work normally + EXPECT_EQ(tu3->getRecord(26.7), 68.22f); + tu3->changeRecord(26.7, 23.18); + EXPECT_EQ(tu3->count(), tu3Size); + EXPECT_EQ(tu3->getRecord(26.7), 23.18f); + tu3->addRecord(26.7, 22.16); + EXPECT_EQ(tu3->count(), tu3Size += 1); + EXPECT_EQ(tu3->getRecord(26.7), 23.18f); + tu3->changeRecord(26.7, 21.7); + EXPECT_EQ(tu3->count(), tu3Size); + EXPECT_EQ(tu3->getRecord(26.7), 21.7f); + tu3->changeRecord(26.7, 54.33); + EXPECT_EQ(tu3->count(), tu3Size); + EXPECT_EQ(tu3->getRecord(26.7), 22.16f); + EXPECT_THROW(tu3->changeRecord(26.7, 54.33), LMDBAL::Exist); + + LMDBAL::SizeType tu4Size = tu4->count(); + EXPECT_THROW(tu4->changeRecord(852, 6795.349), LMDBAL::NotFound); + EXPECT_EQ(tu4->count(), tu4Size); + tu4->addRecord(852, 6795.349); + EXPECT_EQ(tu4->count(), tu4Size += 1); + EXPECT_EQ(tu4->getRecord(852), 6795.349); + tu4->changeRecord(852, 6795.349); //should just do nothing usefull, but work normally + EXPECT_EQ(tu4->getRecord(852), 6795.349); + tu4->changeRecord(852, 13.54); + EXPECT_EQ(tu4->count(), tu4Size); + EXPECT_EQ(tu4->getRecord(852), 13.54); + tu4->addRecord(852, 213.85); + EXPECT_EQ(tu4->count(), tu4Size += 1); + EXPECT_EQ(tu4->getRecord(852), 13.54); + tu4->changeRecord(852, 236.21); + EXPECT_EQ(tu4->count(), tu4Size); + EXPECT_EQ(tu4->getRecord(852), 236.21); + tu4->changeRecord(852, 46324.1135); + EXPECT_EQ(tu4->count(), tu4Size); + EXPECT_EQ(tu4->getRecord(852), 213.85); + EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist); +} From a32c35910b8127cddd049c727979705d86998c5b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 18:57:25 -0300 Subject: [PATCH 055/125] act_runner demo --- .gitea/workflows/demo.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .gitea/workflows/demo.yml diff --git a/.gitea/workflows/demo.yml b/.gitea/workflows/demo.yml new file mode 100644 index 0000000..1029150 --- /dev/null +++ b/.gitea/workflows/demo.yml @@ -0,0 +1,19 @@ +name: Gitea Actions Demo +run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +on: [push] + +jobs: + Explore-Gitea-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" + - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." + - name: Check out repository code + uses: actions/checkout@v3 + - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - run: echo "🍏 This job's status is ${{ job.status }}." From f3a82acdbd7d356133c53375f2a4789067fbd133 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 19:31:19 -0300 Subject: [PATCH 056/125] first attempt to compile in CI --- .gitea/workflows/demo.yml | 19 ------------------- .gitea/workflows/main.yml | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 .gitea/workflows/demo.yml create mode 100644 .gitea/workflows/main.yml diff --git a/.gitea/workflows/demo.yml b/.gitea/workflows/demo.yml deleted file mode 100644 index 1029150..0000000 --- a/.gitea/workflows/demo.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Gitea Actions Demo -run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 -on: [push] - -jobs: - Explore-Gitea-Actions: - runs-on: ubuntu-latest - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" - - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." - - name: Check out repository code - uses: actions/checkout@v3 - - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ gitea.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml new file mode 100644 index 0000000..392b967 --- /dev/null +++ b/.gitea/workflows/main.yml @@ -0,0 +1,20 @@ +name: Main LMDBAL workfow +run-name: ${{ gitea.actor }} is running LMDBAL main workflow +on: [push] + +jobs: + Compile: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Make a build directory + run: mkdir build + - name: Change into build directory + run: cd build + - name: Configure + run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + - name: Build + run: cmake --build . + - name: Status + run: echo "${{ job.status }}" From beab78a8634c61bd1f6901d48466d085f3c2fd46 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 21:16:13 -0300 Subject: [PATCH 057/125] second attempt to perform build --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 392b967..5cdbff5 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -4,7 +4,7 @@ on: [push] jobs: Compile: - runs-on: ubuntu-latest + runs-on: archlinux steps: - name: Check out repository code uses: actions/checkout@v3 From 0d39a613c60085c750f7d5f71986cad89b61c2e6 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 22:10:09 -0300 Subject: [PATCH 058/125] third attempt to perform build --- .gitea/workflows/main.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 5cdbff5..c71986a 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -10,11 +10,9 @@ jobs: uses: actions/checkout@v3 - name: Make a build directory run: mkdir build - - name: Change into build directory - run: cd build - name: Configure - run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + run: cd build & cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 - name: Build - run: cmake --build . + run: cd build & cmake --build . - name: Status run: echo "${{ job.status }}" From c36230b252a5fc8abbabb0f9ff55d79fb3130fc4 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 22:15:34 -0300 Subject: [PATCH 059/125] forth attempt to perform build --- .gitea/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index c71986a..7c32dd1 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -11,8 +11,10 @@ jobs: - name: Make a build directory run: mkdir build - name: Configure - run: cd build & cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + working-directory: ./build + run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 - name: Build - run: cd build & cmake --build . + working-directory: ./build + run: cmake --build . - name: Status run: echo "${{ job.status }}" From 437b65df2ff387672de2b2f31c5054eda7ead1f4 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 22:37:21 -0300 Subject: [PATCH 060/125] an attempt to run tests --- .gitea/workflows/main.yml | 6 ++++++ README.md | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 7c32dd1..3816bc3 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -16,5 +16,11 @@ jobs: - name: Build working-directory: ./build run: cmake --build . + + UnitTests: + runs-on: archlinux + - name: Run tests + working-directory: ./build/test + run: ./runUnitTests - name: Status run: echo "${{ job.status }}" diff --git a/README.md b/README.md index 9f841e6..2c73873 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,12 @@ ### Prerequisites -- Qt 5 or higher +- a compiler (c++ would do) +- Qt 5 or higher (qt5-base would do) - lmdb - CMake 3.16 or higher - Doxygen (optional, for documentation) +- gtest (optional, for tests) ### Using with CMake From 9611431295e4f0d29c622673eca1834a1f18734b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 22:38:23 -0300 Subject: [PATCH 061/125] a type in the workflow --- .gitea/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 3816bc3..1c9309e 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -19,6 +19,7 @@ jobs: UnitTests: runs-on: archlinux + steps: - name: Run tests working-directory: ./build/test run: ./runUnitTests From f3242e266516d4d402b82ad60c1946931956e406 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Aug 2023 22:41:18 -0300 Subject: [PATCH 062/125] okay, getting to the bottom of how it works, now should test --- .gitea/workflows/.main.yml.kate-swp | Bin 0 -> 705 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitea/workflows/.main.yml.kate-swp diff --git a/.gitea/workflows/.main.yml.kate-swp b/.gitea/workflows/.main.yml.kate-swp new file mode 100644 index 0000000000000000000000000000000000000000..849c0001ca81e4b56f6b00c89ad1d9bac5fb555b GIT binary patch literal 705 zcmY+A!A``C4OyOQ%@UvdE)OD=+6&1ASH z-C-R}+DNYSFMxUm20+iC3#etFkLl7xXdu)PlDAsgyGjfzLZl^E5(6q396(Y-s3K?x z6@;>B-P2JbZ(&Vka#)^amge!CM_x44({zvd_Oml&qu6KMiFkx&sL@d@M(K)X%OHGm z{m0(S4V@_u!nYyo4<@ne%?40I3{4l}Dmly$;xP76PRH1H`K|aG>I?|PwzSRBEN=5S vnF|FPD#emJ8?Bw`JYemk)ru{GNk2+tY`t!$JN{b?8VFF)U^B0pR=U?; Date: Sat, 19 Aug 2023 22:41:27 -0300 Subject: [PATCH 063/125] okay, getting to the bottom of how it works, now should test --- .gitea/workflows/.main.yml.kate-swp | Bin 705 -> 0 bytes .gitea/workflows/main.yml | 6 +----- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .gitea/workflows/.main.yml.kate-swp diff --git a/.gitea/workflows/.main.yml.kate-swp b/.gitea/workflows/.main.yml.kate-swp deleted file mode 100644 index 849c0001ca81e4b56f6b00c89ad1d9bac5fb555b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 705 zcmY+A!A``C4OyOQ%@UvdE)OD=+6&1ASH z-C-R}+DNYSFMxUm20+iC3#etFkLl7xXdu)PlDAsgyGjfzLZl^E5(6q396(Y-s3K?x z6@;>B-P2JbZ(&Vka#)^amge!CM_x44({zvd_Oml&qu6KMiFkx&sL@d@M(K)X%OHGm z{m0(S4V@_u!nYyo4<@ne%?40I3{4l}Dmly$;xP76PRH1H`K|aG>I?|PwzSRBEN=5S vnF|FPD#emJ8?Bw`JYemk)ru{GNk2+tY`t!$JN{b?8VFF)U^B0pR=U?; Date: Sun, 20 Aug 2023 11:56:39 -0300 Subject: [PATCH 064/125] an attempt to actually deploy docs --- .gitea/workflows/main.yml | 29 ++++++++++++++++++++++++++--- packaging/Archlinux/PKGBUILD | 4 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index e6b2718..a77b715 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -1,6 +1,9 @@ name: Main LMDBAL workfow run-name: ${{ gitea.actor }} is running LMDBAL main workflow -on: [push] +on: + push: + branches: + - main jobs: Archlinux: @@ -19,5 +22,25 @@ jobs: - name: Run tests working-directory: ./build/test run: ./runUnitTests - - name: Status - run: echo "${{ job.status }}" + - name: Compres Docs + working-directory: ./build/doc + run: tar -cvzf doc.tar.gz man xml html + - name: Copy docs via scp + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{ secrets.DOMAIN_ROOT }} + username: ${{ secrets.DEPLOY_USER_NAME }} + key: ${{ secrets.DEPLOY_PRIVATE_KEY }} + source: "./build/doc/doc.tar.gz" + target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} + - name: Unpack the archive + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.DOMAIN_ROOT }} + username: ${{ secrets.DEPLOY_USER_NAME }} + key: ${{ secrets.DEPLOY_PRIVATE_KEY }} + script_stop: true + script: | + cd ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} + tar -xvzf doc.tar.gz + rm doc.tar.gz diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 63457c6..7b58b00 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,13 +1,13 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.3.1 +pkgver=0.5.0 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/lmdbal" license=('GPL3') depends=( 'lmdb' 'qt5-base') -makedepends=('cmake>=3.16') +makedepends=('cmake>=3.16' 'gcc') optdepends=() source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") From a9be6be54f922f55a3d98a3d9288562ae4fb2dbf Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 11:58:30 -0300 Subject: [PATCH 065/125] an attempt to actually deploy docs, try 2 --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index a77b715..3cb5054 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} is running LMDBAL main workflow on: push: branches: - - main + - master jobs: Archlinux: From af07ca279527620fbdecffec3fbabe3fc9b4e5a5 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 12:04:21 -0300 Subject: [PATCH 066/125] an attempt to actually deploy docs, try 3 --- .gitea/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 3cb5054..12e0713 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -27,11 +27,12 @@ jobs: run: tar -cvzf doc.tar.gz man xml html - name: Copy docs via scp uses: appleboy/scp-action@v0.1.4 + working-directory: ./build/doc with: host: ${{ secrets.DOMAIN_ROOT }} username: ${{ secrets.DEPLOY_USER_NAME }} key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - source: "./build/doc/doc.tar.gz" + source: doc.tar.gz target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} - name: Unpack the archive uses: appleboy/ssh-action@v1.0.0 @@ -42,5 +43,6 @@ jobs: script_stop: true script: | cd ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} + pwd tar -xvzf doc.tar.gz rm doc.tar.gz From a19a141611a09a305c2c583744744a9ae23a2fb7 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 12:10:32 -0300 Subject: [PATCH 067/125] an attempt to actually deploy docs, try 4 --- .gitea/workflows/main.yml | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 12e0713..e440ab8 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -11,20 +11,22 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v3 + - name: Make a build directory run: mkdir build + - name: Configure working-directory: ./build run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + - name: Build working-directory: ./build run: cmake --build . + - name: Run tests working-directory: ./build/test run: ./runUnitTests - - name: Compres Docs - working-directory: ./build/doc - run: tar -cvzf doc.tar.gz man xml html + - name: Copy docs via scp uses: appleboy/scp-action@v0.1.4 working-directory: ./build/doc @@ -32,17 +34,5 @@ jobs: host: ${{ secrets.DOMAIN_ROOT }} username: ${{ secrets.DEPLOY_USER_NAME }} key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - source: doc.tar.gz + source: "html,xml,man" target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} - - name: Unpack the archive - uses: appleboy/ssh-action@v1.0.0 - with: - host: ${{ secrets.DOMAIN_ROOT }} - username: ${{ secrets.DEPLOY_USER_NAME }} - key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - script_stop: true - script: | - cd ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} - pwd - tar -xvzf doc.tar.gz - rm doc.tar.gz From a0288727e1db2ba9bc6d388ba303ef6f6dd8b619 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 12:21:08 -0300 Subject: [PATCH 068/125] an attempt to actually deploy docs, try 5 --- .gitea/workflows/main.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index e440ab8..1c0e577 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -28,11 +28,12 @@ jobs: run: ./runUnitTests - name: Copy docs via scp - uses: appleboy/scp-action@v0.1.4 - working-directory: ./build/doc + uses: appleboy/scp-action@master + # working-directory: ./build/doc //doesn't work with: host: ${{ secrets.DOMAIN_ROOT }} username: ${{ secrets.DEPLOY_USER_NAME }} key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - source: "html,xml,man" + source: "/build/doc/html,/build/doc/xml,/build/doc/man" target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} + strip_components: 2 From dbbc46e7c958eef959eb9cdc1cfe5f8620258367 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 12:23:58 -0300 Subject: [PATCH 069/125] an attempt to actually deploy docs, try 6 --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 1c0e577..b6fab4e 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -34,6 +34,6 @@ jobs: host: ${{ secrets.DOMAIN_ROOT }} username: ${{ secrets.DEPLOY_USER_NAME }} key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - source: "/build/doc/html,/build/doc/xml,/build/doc/man" + source: "build/doc/html/*,build/doc/xml/*,build/doc/man/*" target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} strip_components: 2 From de210b44f5985d0bc8028b9ca1692ffb076ceab9 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Aug 2023 13:38:29 -0300 Subject: [PATCH 070/125] readAll method now works correctly for duplicates mode, testing, some doc fixes --- doc/mainpage.dox | 2 +- src/base.h | 8 +-- src/storage.cpp | 2 + src/storage.hpp | 20 ++++++- test/duplicates.cpp | 139 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 8 deletions(-) diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 7cd5015..2192aa4 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -15,7 +15,7 @@ * std::map * to speed up the access. * - * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&) or LMDBAL::Base::addCache(const std::string& name). + * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&, bool) or LMDBAL::Base::addCache(const std::string& name). * Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them. * You are not obliged to save those handlers, * you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&) diff --git a/src/base.h b/src/base.h index fd5ba18..8b21c77 100644 --- a/src/base.h +++ b/src/base.h @@ -46,8 +46,8 @@ class Storage; template class Cache; -typedef MDB_txn* TransactionID; /*** getCache(const std::string& storageName); private: - typedef std::map Storages; /** Transactions; /** Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ + typedef std::set Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ TransactionID beginReadOnlyTransaction(const std::string& storageName) const; TransactionID beginTransaction(const std::string& storageName) const; diff --git a/src/storage.cpp b/src/storage.cpp index 2f50cd4..b96ad4c 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -23,6 +23,8 @@ /** * \class LMDBAL::iStorage * + * \brief Storage interface + * * This is a interface-like class, it's designed to be an inner database interface to * be used as a polymorphic entity, and provide protected interaction with the database * from the heirs code diff --git a/src/storage.hpp b/src/storage.hpp index 8a8ef31..b3ef621 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -31,7 +31,7 @@ * \tparam K type of the keys of the storage * \tparam V type of the values of the storage * - * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&) + * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&, bool) * if the database is yet closed and you're defining the storages you're going to need. * Or you can call LMDBAL::Base::getStorage(const std::string&) if you didn't save a pointer to the storage at first * @@ -501,6 +501,9 @@ bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { * * Basically just reads all database in an std::map, usefull when you store small storages * + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ @@ -518,6 +521,9 @@ std::map LMDBAL::Storage::readAll() const { * * Basically just reads all database in an std::map, usefull when you store small storages * + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * * \param[out] result a map that is going to contain all data * * \exception LMDBAL::Closed thrown if the database was not opened @@ -545,6 +551,9 @@ void LMDBAL::Storage::readAll(std::map& result) const { * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * * \param[in] txn transaction ID, can be read only transaction * * \exception LMDBAL::Closed thrown if the database was not opened @@ -566,6 +575,9 @@ std::map LMDBAL::Storage::readAll(TransactionID txn) const { * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * * \param[out] result a map that is going to contain all data * \param[in] txn transaction ID, can be read only transaction * @@ -587,8 +599,10 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c while (rc == MDB_SUCCESS) { K key; keySerializer.deserialize(lmdbKey, key); - V& value = result[key]; - valueSerializer.deserialize(lmdbData, value); + std::pair::iterator, bool> probe = result.emplace(key, V{}); + if (probe.second) //I do this to avoid overwrites in case duplicates are enabled + valueSerializer.deserialize(lmdbData, probe.first->second); + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } mdb_cursor_close(cursor); diff --git a/test/duplicates.cpp b/test/duplicates.cpp index d84ba2b..56b63fd 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -1,9 +1,12 @@ #include #include +#include +#include #include "base.h" #include "storage.h" +#include "cursor.h" class DuplicatesTest : public ::testing::Test { protected: @@ -326,3 +329,139 @@ TEST_F(DuplicatesTest, Changing) { EXPECT_EQ(tu4->getRecord(852), 213.85); EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist); } + +TEST_F(DuplicatesTest, GettingAllRecords) { + LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + bool cycle; + LMDBAL::SizeType iterations; + + std::map m1; + std::set k1; + LMDBAL::Cursor* c1 = tu1->createCursor(); + tu1->readAll(m1, txn); + c1->open(txn); + + cycle = false; + iterations = 0; + do { + try { + std::pair pair = c1->next(); + cycle = true; + std::pair::const_iterator, bool> probe = k1.insert(pair.first); + if (probe.second) { + uint16_t valueAll = m1.at(pair.first); + uint16_t valueGet; + EXPECT_NO_THROW(tu1->getRecord(pair.first, valueGet, txn)); + EXPECT_EQ(valueAll, valueGet); + } + ++iterations; + } catch (const LMDBAL::NotFound& e) { + cycle = false; + } + } while (cycle); + tu1->destroyCursor(c1); + + EXPECT_EQ(iterations, tu1->count(txn)); + EXPECT_EQ(k1.size(), m1.size()); + EXPECT_NE(iterations, 0); + EXPECT_NE(k1.size(), 0); + + + std::map m2; + std::set k2; + LMDBAL::Cursor* c2 = tu2->createCursor(); + tu2->readAll(m2, txn); + c2->open(txn); + + cycle = false; + iterations = 0; + do { + try { + std::pair pair = c2->next(); + cycle = true; + std::pair::const_iterator, bool> probe = k2.insert(pair.first); + if (probe.second) { + int8_t valueAll = m2.at(pair.first); + int8_t valueGet; + EXPECT_NO_THROW(tu2->getRecord(pair.first, valueGet, txn)); + EXPECT_EQ(valueAll, valueGet); + } + ++iterations; + } catch (const LMDBAL::NotFound& e) { + cycle = false; + } + } while (cycle); + tu2->destroyCursor(c2); + + EXPECT_EQ(iterations, tu2->count(txn)); + EXPECT_EQ(k2.size(), m2.size()); + EXPECT_NE(iterations, 0); + EXPECT_NE(k2.size(), 0); + + + std::map m3; + std::set k3; + LMDBAL::Cursor* c3 = tu3->createCursor(); + tu3->readAll(m3, txn); + c3->open(txn); + + cycle = false; + iterations = 0; + do { + try { + std::pair pair = c3->next(); + cycle = true; + std::pair::const_iterator, bool> probe = k3.insert(pair.first); + if (probe.second) { + float valueAll = m3.at(pair.first); + float valueGet; + EXPECT_NO_THROW(tu3->getRecord(pair.first, valueGet, txn)); + EXPECT_EQ(valueAll, valueGet); + } + ++iterations; + } catch (const LMDBAL::NotFound& e) { + cycle = false; + } + } while (cycle); + tu3->destroyCursor(c3); + + EXPECT_EQ(iterations, tu3->count(txn)); + EXPECT_EQ(k3.size(), m3.size()); + EXPECT_NE(iterations, 0); + EXPECT_NE(k3.size(), 0); + + + std::map m4; + std::set k4; + LMDBAL::Cursor* c4 = tu4->createCursor(); + tu4->readAll(m4, txn); + c4->open(txn); + + cycle = false; + iterations = 0; + do { + try { + std::pair pair = c4->next(); + cycle = true; + std::pair::const_iterator, bool> probe = k4.insert(pair.first); + if (probe.second) { + double valueAll = m4.at(pair.first); + double valueGet; + EXPECT_NO_THROW(tu4->getRecord(pair.first, valueGet, txn)); + EXPECT_EQ(valueAll, valueGet); + } + ++iterations; + } catch (const LMDBAL::NotFound& e) { + cycle = false; + } + } while (cycle); + tu4->destroyCursor(c4); + + EXPECT_EQ(iterations, tu4->count(txn)); + EXPECT_EQ(k4.size(), m4.size()); + EXPECT_NE(iterations, 0); + EXPECT_NE(k4.size(), 0); + + + db->abortTransaction(txn); +} From 275406df61c8d901e7afbb443cff0270f1f619cf Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 14 Oct 2023 15:09:50 -0300 Subject: [PATCH 071/125] Release CI Workflow --- .gitea/workflows/release.yml | 46 ++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 2 +- packaging/Archlinux/PKGBUILD | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/release.yml diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e3772ff --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,46 @@ +name: LMDBAL Release workflow +run-name: ${{ gitea.actor }} is running LMDBAL Release workflow on release ${{ gitea.event.release.tag_name }} +on: + release: + types: [published] + +jobs: + Archlinux: + runs-on: archlinux + steps: + - name: Download the release tarball + run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz + + - name: Calculate SHA256 for the tarball + run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV + + - name: Unarchive tarball + run: tar -xvzf tarball.tar.gz + + - name: Clone the AUR repository + run: | + echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key + chmod 600 key + GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/lmdbal.git aur + chmod 777 -R aur + cd aur + git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} + git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }} + + + - name: Copy PKGBUILD to the directory + run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/ + + - name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO + working-directory: aur + run: | + sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD + sudo -u build makepkg --printsrcinfo > .SRCINFO + + - name: Commit package to aur + working-directory: aur + run: | + git add PKGBUILD .SRCINFO + git commit -m "${{ gitea.event.release.body }}" + GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push + diff --git a/CMakeLists.txt b/CMakeLists.txt index 21199ec..4bfee90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.4.0 + VERSION 0.5.0 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 7b58b00..d51131b 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -10,7 +10,7 @@ depends=( 'lmdb' 'qt5-base') makedepends=('cmake>=3.16' 'gcc') optdepends=() -source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") +source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz") sha256sums=('df1a9687d81d609d160754285f2613d7e07fc6deb781d0fb0084e4857ea82e95') build() { cd "$srcdir/$pkgname" From fbcf94d1c2befa6b7cab9053cc8c5767ae3c4395 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 15 Oct 2023 11:10:27 -0300 Subject: [PATCH 072/125] Doc fixes, testing for QVariant --- CHANGELOG.md | 8 ++-- src/storage.hpp | 8 ++-- test/basic.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 035262c..1cd74a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Changelog -## LMDBAL 0.5.0 (UNRELEASED, 2023) +## LMDBAL 0.5.0 (October 15, 2023) ### New Features -- duplicates support +- duplicates support (only for table) +### Improvements +- some more documentation +- more tests ## LMDBAL 0.4.0 (August 13, 2023) ### New Features @@ -17,7 +20,6 @@ - some more documentation - more tests - ## LMDBAL 0.3.1 (April 14, 2023) ### Bug fixes - build with qt5 now is possible again diff --git a/src/storage.hpp b/src/storage.hpp index b3ef621..8c52bf6 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -321,7 +321,7 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: - * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for @@ -348,7 +348,7 @@ V LMDBAL::Storage::getRecord(const K& key) const { * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: - * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for @@ -384,7 +384,7 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: - * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for @@ -415,7 +415,7 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. * Anyway: - * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for diff --git a/test/basic.cpp b/test/basic.cpp index 460ce36..559993d 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -12,13 +12,15 @@ protected: ::testing::Test(), t1(db->getStorage("table1")), t2(db->getStorage("table2")), - c1(db->getCache("cache1")) {} + c1(db->getCache("cache1")), + c2(db->getCache("cache2")) {} ~BaseTest() {} uint32_t getT1Flags() const {return t1->flags();} uint32_t getT2Flags() const {return t2->flags();} uint32_t getC1Flags() const {return c1->flags();} + uint32_t getC2Flags() const {return c2->flags();} static void SetUpTestSuite() { if (db == nullptr) { @@ -26,6 +28,7 @@ protected: db->addStorage("table1"); db->addStorage("table2"); db->addCache("cache1"); + db->addCache("cache2"); } } @@ -41,6 +44,7 @@ protected: LMDBAL::Storage* t1; LMDBAL::Storage* t2; LMDBAL::Cache* c1; + LMDBAL::Cache* c2; }; @@ -64,6 +68,7 @@ TEST_F(BaseTest, Flags) { uint32_t t1Flags = getT1Flags(); uint32_t t2Flags = getT2Flags(); uint32_t c1Flags = getC1Flags(); + uint32_t c2Flags = getC2Flags(); EXPECT_TRUE(t1Flags & MDB_INTEGERKEY); EXPECT_FALSE(t1Flags & MDB_DUPSORT); @@ -79,6 +84,11 @@ TEST_F(BaseTest, Flags) { EXPECT_FALSE(c1Flags & MDB_DUPSORT); EXPECT_FALSE(c1Flags & MDB_DUPFIXED); EXPECT_FALSE(c1Flags & MDB_INTEGERDUP); + + EXPECT_FALSE(c2Flags & MDB_INTEGERKEY); + EXPECT_FALSE(c2Flags & MDB_DUPSORT); + EXPECT_FALSE(c2Flags & MDB_DUPFIXED); + EXPECT_FALSE(c2Flags & MDB_INTEGERDUP); } TEST_F(BaseTest, AddingIntegerKey) { @@ -108,6 +118,19 @@ TEST_F(BaseTest, AddingKeysToCache) { EXPECT_EQ(c1->getRecord(-116), "whatever"); } +TEST_F(BaseTest, AddingKeysToVariableCache) { + EXPECT_EQ(db->ready(), true); + c2->addRecord("regrets", "blah balah"); + c2->addRecord("fossil fingers", 842); + c2->addRecord("preloaded cut", 539.75); + c2->addRecord("dihotomy", false); + + EXPECT_EQ(c2->getRecord("regrets"), "blah balah"); + EXPECT_EQ(c2->getRecord("fossil fingers"), 842); + EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75); + EXPECT_EQ(c2->getRecord("dihotomy"), false); +} + TEST_F(BaseTest, AddingRepeatingKey) { EXPECT_EQ(db->ready(), true); @@ -119,6 +142,9 @@ TEST_F(BaseTest, AddingRepeatingKey) { EXPECT_THROW(c1->addRecord(-4, "world"), LMDBAL::Exist); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); + + EXPECT_THROW(c2->addRecord("dihotomy", "pired"), LMDBAL::Exist); + EXPECT_EQ(c2->getRecord("dihotomy"), false); } TEST_F(BaseTest, GettingNotExistingKeys) { @@ -127,6 +153,7 @@ TEST_F(BaseTest, GettingNotExistingKeys) { EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound); EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound); EXPECT_THROW(c1->getRecord(21), LMDBAL::NotFound); + EXPECT_THROW(c2->getRecord("praising"), LMDBAL::NotFound); } TEST_F(BaseTest, Persistence) { @@ -134,6 +161,7 @@ TEST_F(BaseTest, Persistence) { uint32_t t1Size = t1->count(); uint32_t t2Size = t2->count(); uint32_t c1Size = c1->count(); + uint32_t c2Size = c2->count(); db->close(); delete db; @@ -142,6 +170,7 @@ TEST_F(BaseTest, Persistence) { t1 = db->addStorage("table1"); t2 = db->addStorage("table2"); c1 = db->addCache("cache1"); + c2 = db->addCache("cache2"); db->open(); EXPECT_EQ(t1->count(), t1Size); @@ -170,9 +199,16 @@ TEST_F(BaseTest, Persistence) { EXPECT_EQ(c1->getRecord(2), "blah balah"); EXPECT_EQ(c1->count(), c1Size); + EXPECT_EQ(c2->count(), c2Size); + EXPECT_EQ(c2->getRecord("regrets"), "blah balah"); + EXPECT_EQ(c2->getRecord("fossil fingers"), 842); + EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75); + EXPECT_EQ(c2->getRecord("dihotomy"), false); + EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound); EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound); EXPECT_THROW(c1->getRecord(89), LMDBAL::NotFound); + EXPECT_THROW(c2->getRecord("marathons"), LMDBAL::NotFound); } TEST_F(BaseTest, CountAndDrop) { @@ -180,21 +216,24 @@ TEST_F(BaseTest, CountAndDrop) { EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t2->count(), 4); EXPECT_EQ(c1->count(), 4); - + EXPECT_EQ(c2->count(), 4); db->drop(); EXPECT_EQ(t1->count(), 0); EXPECT_EQ(t2->count(), 0); EXPECT_EQ(c1->count(), 0); + EXPECT_EQ(c2->count(), 0); t1->addRecord(2, 2); t2->addRecord("sdfhga", "world"); c1->addRecord(15, "world"); c1->addRecord(12, "grr grr"); + c2->addRecord("blues", -749); EXPECT_EQ(t1->count(), 1); EXPECT_EQ(t2->count(), 1); EXPECT_EQ(c1->count(), 2); + EXPECT_EQ(c2->count(), 1); } TEST_F(BaseTest, Change) { @@ -202,37 +241,45 @@ TEST_F(BaseTest, Change) { EXPECT_EQ(t1->count(), 1); EXPECT_EQ(t2->count(), 1); EXPECT_EQ(c1->count(), 2); + EXPECT_EQ(c2->count(), 1); EXPECT_EQ(t1->getRecord(2), 2); EXPECT_EQ(t2->getRecord("sdfhga"), "world"); EXPECT_EQ(c1->getRecord(15), "world"); EXPECT_EQ(c1->getRecord(12), "grr grr"); + EXPECT_EQ(c2->getRecord("blues"), -749); t1->addRecord(58, 39); t2->addRecord("lawfirm", "stumble"); c1->addRecord(89, "answer"); + c2->addRecord("wielders", QStringList({"calcium", "eagles"})); t1->changeRecord(2, 49); t2->changeRecord("sdfhga", "void"); c1->changeRecord(15, "recording"); c1->changeRecord(12, "thermal"); + c2->changeRecord("blues", true); EXPECT_THROW(t1->changeRecord(37, 49), LMDBAL::NotFound); EXPECT_THROW(t2->changeRecord("precision", "cryoplastics"), LMDBAL::NotFound); EXPECT_THROW(c1->changeRecord(-62, "sharks"), LMDBAL::NotFound); + EXPECT_THROW(c2->changeRecord("visions", 90), LMDBAL::NotFound); EXPECT_EQ(t1->getRecord(2), 49); EXPECT_EQ(t2->getRecord("sdfhga"), "void"); EXPECT_EQ(c1->getRecord(15), "recording"); EXPECT_EQ(c1->getRecord(12), "thermal"); + EXPECT_EQ(c2->getRecord("blues"), true); EXPECT_EQ(t1->getRecord(58), 39); EXPECT_EQ(t2->getRecord("lawfirm"), "stumble"); EXPECT_EQ(c1->getRecord(89), "answer"); + EXPECT_EQ(c2->getRecord("wielders"), QStringList({"calcium", "eagles"})); EXPECT_EQ(t1->count(), 2); EXPECT_EQ(t2->count(), 2); EXPECT_EQ(c1->count(), 3); + EXPECT_EQ(c2->count(), 2); } TEST_F(BaseTest, Force) { @@ -244,21 +291,30 @@ TEST_F(BaseTest, Force) { EXPECT_EQ(t2->forceRecord("lawfirm", "paracetamol"), false); //changing EXPECT_EQ(c1->forceRecord(89, "canine"), false); //changing EXPECT_EQ(c1->forceRecord(98, "duration"), true); //adding + EXPECT_EQ(c2->forceRecord("wielders", "charm"), false); //changing + EXPECT_EQ(c2->forceRecord("tedious", 5732.89), true); //adding EXPECT_EQ(t1->getRecord(2), 49); EXPECT_EQ(t1->getRecord(58), 35); EXPECT_EQ(t1->getRecord(68), 36); + EXPECT_EQ(t2->getRecord("sdfhga"), "void"); EXPECT_EQ(t2->getRecord("prophecy"), "dumpling"); EXPECT_EQ(t2->getRecord("lawfirm"), "paracetamol"); + EXPECT_EQ(c1->getRecord(15), "recording"); EXPECT_EQ(c1->getRecord(12), "thermal"); EXPECT_EQ(c1->getRecord(89), "canine"); EXPECT_EQ(c1->getRecord(98), "duration"); + EXPECT_EQ(c2->getRecord("blues"), true); + EXPECT_EQ(c2->getRecord("tedious"), 5732.89); + EXPECT_EQ(c2->getRecord("wielders"), "charm"); + EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t2->count(), 3); EXPECT_EQ(c1->count(), 4); + EXPECT_EQ(c2->count(), 3); } TEST_F(BaseTest, ReadAll) { @@ -267,21 +323,29 @@ TEST_F(BaseTest, ReadAll) { std::map m1 = t1->readAll(); std::map m2 = t2->readAll(); std::map m3 = c1->readAll(); + std::map m4 = c2->readAll(); EXPECT_EQ(m1.at(2), 49); EXPECT_EQ(m1.at(58), 35); EXPECT_EQ(m1.at(68), 36); + EXPECT_EQ(m2.at("sdfhga"), "void"); EXPECT_EQ(m2.at("prophecy"), "dumpling"); EXPECT_EQ(m2.at("lawfirm"), "paracetamol"); + EXPECT_EQ(m3.at(15), "recording"); EXPECT_EQ(m3.at(12), "thermal"); EXPECT_EQ(m3.at(89), "canine"); EXPECT_EQ(m3.at(98), "duration"); + EXPECT_EQ(m4.at("blues"), true); + EXPECT_EQ(m4.at("tedious"), 5732.89); + EXPECT_EQ(m4.at("wielders"), "charm"); + EXPECT_EQ(m1.size(), 3); EXPECT_EQ(m2.size(), 3); EXPECT_EQ(m3.size(), 4); + EXPECT_EQ(m4.size(), 3); } TEST_F(BaseTest, ReplaceAll) { @@ -299,10 +363,14 @@ TEST_F(BaseTest, ReplaceAll) { {"ronin", "cheese"} }); c1->replaceAll({}); + c2->replaceAll({ + {"kind", 73} + }); EXPECT_EQ(t1->count(), 4); EXPECT_EQ(t2->count(), 3); EXPECT_EQ(c1->count(), 0); + EXPECT_EQ(c2->count(), 1); EXPECT_FALSE(t1->checkRecord(2)); EXPECT_FALSE(t1->checkRecord(58)); @@ -317,6 +385,10 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_FALSE(c1->checkRecord(89)); EXPECT_FALSE(c1->checkRecord(98)); + EXPECT_FALSE(c2->checkRecord("blues")); + EXPECT_FALSE(c2->checkRecord("tedious")); + EXPECT_FALSE(c2->checkRecord("wielders")); + EXPECT_EQ(t1->getRecord(7), 48); EXPECT_EQ(t1->getRecord(194), 582); EXPECT_EQ(t1->getRecord(857), 39); @@ -326,6 +398,8 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(t2->getRecord("cluster"), "throttle"); EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + EXPECT_EQ(c2->getRecord("kind"), 73); + c1->replaceAll({ {68, "quality"}, {31, "ridgid body"}, @@ -342,8 +416,6 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); } - - TEST_F(BaseTest, AddRecords) { EXPECT_EQ(db->ready(), true); @@ -472,4 +544,33 @@ TEST_F(BaseTest, AddRecords) { EXPECT_EQ(c1->getRecord(-32), "stitched"); EXPECT_EQ(c1->getRecord(123), "horibly unforseen"); + LMDBAL::SizeType s4 = c2->addRecords({ + {"and wholesome", "preaching"}, + {"ginger", false} + }); + + EXPECT_EQ(c2->count(), 3); + EXPECT_EQ(s4, 3); + + EXPECT_THROW( + s4 = c2->addRecords({ + {"returning favor", 'c'}, + {"ginger", 23}, + {"frames", true} + }), LMDBAL::Exist + ); + EXPECT_EQ(c2->count(), 3); + + s4 = c2->addRecords({ + {"and wholesome", -1}, + {"orcid", 89.9} + }, true); + + EXPECT_EQ(c2->count(), 4); + EXPECT_EQ(s4, 4); + + EXPECT_EQ(c2->getRecord("and wholesome"), -1); + EXPECT_EQ(c2->getRecord("orcid"), 89.9); + EXPECT_EQ(c2->getRecord("ginger"), false); + EXPECT_EQ(c2->getRecord("kind"), 73); } From a9aa6b549f33dce6c0a09c0eb1ec85c4adeb9108 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 15 Oct 2023 11:19:53 -0300 Subject: [PATCH 073/125] missing include --- test/basic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/basic.cpp b/test/basic.cpp index 559993d..bf0bf39 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -5,6 +5,7 @@ #include "cache.h" #include +#include class BaseTest : public ::testing::Test { protected: From de741eda21a653e879c5f9fc1d0a162a3f36acba Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 17 Oct 2023 18:06:11 -0300 Subject: [PATCH 074/125] RAII transactions --- CHANGELOG.md | 5 + CMakeLists.txt | 2 +- packaging/Archlinux/PKGBUILD | 4 +- src/CMakeLists.txt | 7 +- src/base.cpp | 44 +++-- src/base.h | 13 +- src/cache.h | 40 ++-- src/cache.hpp | 30 +-- src/cursor.h | 5 +- src/cursor.hpp | 93 +++++----- src/exceptions.cpp | 24 ++- src/exceptions.h | 21 +++ src/storage.cpp | 55 +++++- src/storage.h | 43 +++-- src/storage.hpp | 341 ++++++++++++++++++++++++++++------- src/transaction.cpp | 71 ++++++++ src/transaction.h | 46 +++++ test/cachecursor.cpp | 7 +- test/cachetransaction.cpp | 24 +-- test/duplicates.cpp | 5 +- test/storagecursor.cpp | 7 +- test/storagetransaction.cpp | 24 +-- 22 files changed, 689 insertions(+), 222 deletions(-) create mode 100644 src/transaction.cpp create mode 100644 src/transaction.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd74a2..990f0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## LMDBAL 0.5.1 (UNRELEASED) +### Improvements +- RAII transactions +- reduced overhead for private transaction finctions + ## LMDBAL 0.5.0 (October 15, 2023) ### New Features - duplicates support (only for table) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bfee90..4d73014 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.5.0 + VERSION 0.5.1 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index d51131b..4c0362b 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.5.0 +pkgver=0.5.1 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') @@ -11,7 +11,7 @@ makedepends=('cmake>=3.16' 'gcc') optdepends=() source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz") -sha256sums=('df1a9687d81d609d160754285f2613d7e07fc6deb781d0fb0084e4857ea82e95') +sha256sums=('SKIP') build() { cd "$srcdir/$pkgname" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -D QT_VERSION_MAJOR=5 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d88adf..564a59b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES exceptions.cpp storage.cpp base.cpp + transaction.cpp ) set(HEADERS @@ -29,11 +30,9 @@ set(HEADERS serializer_qstring.hpp serializer_qbytearray.hpp operators.hpp + transaction.h ) -target_sources(${PROJECT_NAME} PRIVATE - ${SOURCES} - ${HEADERS} -) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) diff --git a/src/base.cpp b/src/base.cpp index 04040fc..604f9da 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -19,6 +19,7 @@ #include "base.h" #include "exceptions.h" #include "storage.h" +#include "transaction.h" #define UNUSED(x) (void)(x) @@ -120,8 +121,7 @@ bool LMDBAL::Base::removeDirectory() { if (opened) throw Opened(name, "remove database directory"); - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + getName(); + QString path = getPath(); QDir cache(path); if (cache.exists()) @@ -148,10 +148,8 @@ QString LMDBAL::Base::createDirectory() { if (opened) throw Opened(name, "create database directory"); - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + getName(); + QString path = getPath(); QDir cache(path); - if (!cache.exists()) { bool res = cache.mkpath(path); if (!res) @@ -169,6 +167,17 @@ QString LMDBAL::Base::createDirectory() { QString LMDBAL::Base::getName() const { return QString::fromStdString(name);} +/** + * \brief Returns database name + * + * \returns database path + */ +QString LMDBAL::Base::getPath() const { + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + getName(); + + return path; +} /** * \brief Returns database state @@ -190,38 +199,45 @@ void LMDBAL::Base::drop() { if (!opened) throw Closed("drop", name); - TransactionID txn = beginTransaction(); + TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { int rc = pair.second->drop(txn); if (rc != MDB_SUCCESS) { - abortTransaction(txn); + abortPrivateTransaction(txn, emptyName); throw Unknown(name, mdb_strerror(rc), pair.first); } } - commitTransaction(txn); + + commitPrivateTransaction(txn, emptyName); + for (const std::pair& pair : storages) + pair.second->handleDrop(); } /** * \brief Begins read-only transaction * - * \returns read-only transaction ID + * \returns read-only transaction * * \exception LMDBAL::Closed - thrown if the database is closed * \exception LMDBAL::Unknown - thrown if something unexpected happened */ -LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction() const { - return beginReadOnlyTransaction(emptyName);} +LMDBAL::Transaction LMDBAL::Base::beginReadOnlyTransaction() const { + TransactionID id = beginReadOnlyTransaction(emptyName); + return Transaction(id, this); +} /** * \brief Begins writable transaction * - * \returns writable transaction ID + * \returns writable transaction * * \exception LMDBAL::Closed - thrown if the database is closed * \exception LMDBAL::Unknown - thrown if something unexpected happened */ -LMDBAL::TransactionID LMDBAL::Base::beginTransaction() const { - return beginTransaction(emptyName);} +LMDBAL::WriteTransaction LMDBAL::Base::beginTransaction() { + TransactionID id = beginTransaction(emptyName); + return WriteTransaction(id, this); +} /** * \brief Aborts transaction diff --git a/src/base.h b/src/base.h index 8b21c77..c99bf21 100644 --- a/src/base.h +++ b/src/base.h @@ -36,6 +36,8 @@ namespace LMDBAL { class iStorage; +class Transaction; +class WriteTransaction; template class Serializer; @@ -51,6 +53,8 @@ typedef uint32_t SizeType; /**<\brief All LMDBAL si class Base { friend class iStorage; + friend class Transaction; + friend class WriteTransaction; public: Base(const QString& name, uint16_t mapSize = 10); @@ -62,12 +66,11 @@ public: bool removeDirectory(); QString createDirectory(); QString getName() const; + QString getPath() const; void drop(); - TransactionID beginReadOnlyTransaction() const; - TransactionID beginTransaction() const; - void commitTransaction(TransactionID id); - void abortTransaction(TransactionID id) const; + Transaction beginReadOnlyTransaction() const; + WriteTransaction beginTransaction(); template LMDBAL::Storage* addStorage(const std::string& storageName, bool duplicates = false); @@ -85,6 +88,8 @@ private: typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ typedef std::set Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ + void commitTransaction(TransactionID id); + void abortTransaction(TransactionID id) const; TransactionID beginReadOnlyTransaction(const std::string& storageName) const; TransactionID beginTransaction(const std::string& storageName) const; void commitTransaction(TransactionID id, const std::string& storageName); diff --git a/src/cache.h b/src/cache.h index ff0d996..0883e65 100644 --- a/src/cache.h +++ b/src/cache.h @@ -54,12 +54,27 @@ protected: Cache(Base* parent, const std::string& name, bool duplicates = false); ~Cache() override; + virtual void handleDrop() override; virtual void transactionStarted(TransactionID txn, bool readOnly) const override; virtual void transactionCommited(TransactionID txn) override; virtual void transactionAborted(TransactionID txn) const override; virtual void discoveredRecord(const K& key, const V& value) const override; virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const override; + + virtual SizeType count(TransactionID txn) const override; + virtual void addRecord(const K& key, const V& value, TransactionID txn) override; + virtual bool forceRecord(const K& key, const V& value, TransactionID txn) override; + virtual void changeRecord(const K& key, const V& value, TransactionID txn) override; + virtual void removeRecord(const K& key, TransactionID txn) override; + virtual bool checkRecord(const K& key, TransactionID txn) const override; + virtual void getRecord(const K& key, V& out, TransactionID txn) const override; + virtual V getRecord(const K& key, TransactionID txn) const override; + virtual std::map readAll(TransactionID txn) const override; + virtual void readAll(std::map& out, TransactionID txn) const override; + virtual void replaceAll(const std::map& data, TransactionID txn) override; + virtual SizeType addRecords(const std::map& data, TransactionID txn, bool overwrite = false) override; + private: void handleMode() const; @@ -72,37 +87,34 @@ private: void handleForceRecord(const K& key, const V& value, bool added); void handleReplaceAll(std::map* data); void handleAddRecords(const std::map& data, bool overwrite, SizeType newSize); - void handleDrop(); void appendToCache(const K& key, const V& value) const; public: using Storage::drop; - virtual int drop(TransactionID transaction) override; + using Storage::addRecord; + using Storage::forceRecord; + using Storage::changeRecord; + using Storage::removeRecord; + using Storage::checkRecord; + using Storage::getRecord; + using Storage::readAll; + using Storage::replaceAll; + using Storage::addRecords; + using Storage::count; + virtual int drop(const WriteTransaction& transaction) override; virtual void addRecord(const K& key, const V& value) override; - virtual void addRecord(const K& key, const V& value, TransactionID txn) override; virtual bool forceRecord(const K& key, const V& value) override; - virtual bool forceRecord(const K& key, const V& value, TransactionID txn) override; virtual void changeRecord(const K& key, const V& value) override; - virtual void changeRecord(const K& key, const V& value, TransactionID txn) override; virtual void removeRecord(const K& key) override; - virtual void removeRecord(const K& key, TransactionID txn) override; virtual bool checkRecord(const K& key) const override; - virtual bool checkRecord(const K& key, TransactionID txn) const override; virtual void getRecord(const K& key, V& out) const override; - virtual void getRecord(const K& key, V& out, TransactionID txn) const override; virtual V getRecord(const K& key) const override; - virtual V getRecord(const K& key, TransactionID txn) const override; virtual SizeType count() const override; - virtual SizeType count(TransactionID txn) const override; virtual std::map readAll() const override; - virtual std::map readAll(TransactionID txn) const override; virtual void readAll(std::map& out) const override; - virtual void readAll(std::map& out, TransactionID txn) const override; virtual void replaceAll(const std::map& data) override; - virtual void replaceAll(const std::map& data, TransactionID txn) override; virtual SizeType addRecords(const std::map& data, bool overwrite = false) override; - virtual SizeType addRecords(const std::map& data, TransactionID txn, bool overwrite = false) override; protected: /** diff --git a/src/cache.hpp b/src/cache.hpp index 0f0470b..52b17de 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -75,8 +75,6 @@ void LMDBAL::Cache::addRecord(const K& key, const V& value) { template void LMDBAL::Cache::addRecord(const K& key, const V& value, TransactionID txn) { - iStorage::ensureOpened(iStorage::addRecordMethodName); - if (cache->count(key) > 0) iStorage::throwDuplicate(iStorage::toString(key)); @@ -109,8 +107,6 @@ bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { template bool LMDBAL::Cache::forceRecord(const K& key, const V& value, TransactionID txn) { - iStorage::ensureOpened(iStorage::forceRecordMethodName); - bool added = Storage::forceRecord(key, value, txn); typename TransactionCache::iterator tc = transactionCache->find(txn); @@ -171,8 +167,6 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value) { template void LMDBAL::Cache::changeRecord(const K& key, const V& value, TransactionID txn) { - iStorage::ensureOpened(iStorage::changeRecordMethodName); - if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) @@ -248,8 +242,6 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { template V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); - V value; Cache::getRecord(key, value, txn); return value; @@ -257,8 +249,6 @@ V LMDBAL::Cache::getRecord(const K& key, TransactionID txn) const { template void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); - //if there are any changes made within this transaction //I will be able to see them among pending changes //so, I'm going to go through them in reverse order @@ -387,8 +377,6 @@ bool LMDBAL::Cache::checkRecord(const K& key) const { template bool LMDBAL::Cache::checkRecord(const K& key, TransactionID txn) const { - iStorage::ensureOpened(iStorage::checkRecordMethodName); - //if there are any changes made within this transaction //I will be able to see them among pending changes //so, I'm going to go through them in reverse order @@ -497,8 +485,6 @@ void LMDBAL::Cache::readAll(std::map& out) const { template std::map LMDBAL::Cache::readAll(TransactionID txn) const { - iStorage::ensureOpened(iStorage::readAllMethodName); - std::map out; readAll(out, txn); @@ -507,8 +493,6 @@ std::map LMDBAL::Cache::readAll(TransactionID txn) const { template void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const { - iStorage::ensureOpened(iStorage::readAllMethodName); - typename TransactionCache::iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) { Queue& queue = tc->second; @@ -676,8 +660,6 @@ void LMDBAL::Cache::removeRecord(const K& key) { template void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { - iStorage::ensureOpened(iStorage::removeRecordMethodName); - bool noKey = false; if (mode != Mode::full) noKey = cache->count(key) == 0; @@ -729,7 +711,6 @@ uint32_t LMDBAL::Cache::count() const { template uint32_t LMDBAL::Cache::count(TransactionID txn) const { - int32_t diff = 0; bool currentTransaction = false; typename TransactionCache::const_iterator tc = transactionCache->find(txn); @@ -800,10 +781,15 @@ void LMDBAL::Cache::handleMode() const { } template -int LMDBAL::Cache::drop(TransactionID transaction) { - int res = Storage::drop(transaction); +int LMDBAL::Cache::drop(const WriteTransaction& transaction) { + iStorage::ensureOpened(iStorage::dropMethodName); + TransactionID txn = iStorage::extractTransactionId(transaction, iStorage::dropMethodName); + int res = Storage::drop(txn); - typename TransactionCache::iterator tc = transactionCache->find(transaction); + if (res != MDB_SUCCESS) + return res; + + typename TransactionCache::iterator tc = transactionCache->find(txn); if (tc != transactionCache->end()) tc->second.emplace_back(Operation::drop, nullptr); diff --git a/src/cursor.h b/src/cursor.h index 36d46ba..4f0a355 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -24,6 +24,7 @@ #include "lmdb.h" #include "base.h" #include "storage.h" +#include "transaction.h" namespace LMDBAL { @@ -42,9 +43,9 @@ private: public: void open() const; - void open(TransactionID txn) const; + void open(const Transaction& transaction) const; void renew() const; - void renew(TransactionID txn) const; + void renew(const Transaction& transaction) const; void close() const; bool opened() const; diff --git a/src/cursor.hpp b/src/cursor.hpp index 1430833..00fc346 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -20,6 +20,7 @@ #define LMDBAL_CURSOR_HPP #include "cursor.h" +#include /** * \class LMDBAL::Cursor @@ -79,8 +80,8 @@ void LMDBAL::Cursor::terminated () const { * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! * It will do nothing to a cursor that was already opened (no matter what way). * - * \throws LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \throws LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction */ template void LMDBAL::Cursor::open () const { @@ -109,12 +110,16 @@ void LMDBAL::Cursor::open () const { * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! * It will do nothing to a cursor that was already opened (no matter what way). * - * \throws LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \throws LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb + * \param[in] transaction - a transaction, can be read only + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ template -void LMDBAL::Cursor::open (TransactionID txn) const { +void LMDBAL::Cursor::open (const Transaction& transaction) const { storage->ensureOpened(openCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); switch (state) { case closed: { int result = mdb_cursor_open(txn, storage->dbi, &cursor); @@ -141,8 +146,8 @@ void LMDBAL::Cursor::open (TransactionID txn) const { * * This function does nothing if the cursor is closed * - * \throws LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \throws LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb */ template void LMDBAL::Cursor::renew () const { @@ -183,12 +188,14 @@ void LMDBAL::Cursor::renew () const { * * \param[in] txn a transaction you wish this cursor to be bound to * - * \throws LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \throws LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ template -void LMDBAL::Cursor::renew (TransactionID txn) const { +void LMDBAL::Cursor::renew (const Transaction& transaction) const { storage->ensureOpened(renewCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); switch (state) { case openedPrivate: { TransactionID txn = mdb_cursor_txn(cursor); @@ -255,9 +262,9 @@ bool LMDBAL::Cursor::opened () const { * \param[out] key a reference to an object the key of queried element is going to be assigned * \param[out] value a reference to an object the value of queried element is going to be assigned * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::first (K& key, V& value) const { @@ -272,9 +279,9 @@ void LMDBAL::Cursor::first (K& key, V& value) const { * \param[out] key a reference to an object the key of queried element is going to be assigned * \param[out] value a reference to an object the value of queried element is going to be assigned * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::last (K& key, V& value) const { @@ -295,9 +302,9 @@ void LMDBAL::Cursor::last (K& key, V& value) const { * \param[out] key a reference to an object the key of queried element is going to be assigned * \param[out] value a reference to an object the value of queried element is going to be assigned * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::next (K& key, V& value) const { @@ -318,9 +325,9 @@ void LMDBAL::Cursor::next (K& key, V& value) const { * \param[out] key a reference to an object the key of queried element is going to be assigned * \param[out] value a reference to an object the value of queried element is going to be assigned * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::prev (K& key, V& value) const { @@ -337,9 +344,9 @@ void LMDBAL::Cursor::prev (K& key, V& value) const { * \param[out] key a reference to an object the key of queried element is going to be assigned * \param[out] value a reference to an object the value of queried element is going to be assigned * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::current (K& key, V& value) const { @@ -353,9 +360,9 @@ void LMDBAL::Cursor::current (K& key, V& value) const { * * \returns std::pair where first is element key and second is element value * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template std::pair LMDBAL::Cursor::first () const { @@ -371,9 +378,9 @@ std::pair LMDBAL::Cursor::first () const { * * \returns std::pair where first is element key and second is element value * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template std::pair LMDBAL::Cursor::last () const { @@ -395,9 +402,9 @@ std::pair LMDBAL::Cursor::last () const { * * \returns std::pair where first is element key and second is element value * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if the cursor already was on the last element or if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template std::pair LMDBAL::Cursor::next () const { @@ -419,9 +426,9 @@ std::pair LMDBAL::Cursor::next () const { * * \returns std::pair where first is element key and second is element value * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage - * \throws LMDBAL::Unknown thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound thrown if the cursor already was on the first element or if there are no elements in the storage + * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template std::pair LMDBAL::Cursor::prev () const { @@ -439,9 +446,9 @@ std::pair LMDBAL::Cursor::prev () const { * * \returns std::pair where first is element key and second is element value * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about - * \throws LMDBAL::Unknown thrown if there was no positioning operation before of if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound probably never thrown but there might be still some corner case I don't know about + * \exception LMDBAL::Unknown thrown if there was no positioning operation before of if there was some unexpected problem with lmdb */ template std::pair LMDBAL::Cursor::current () const { @@ -461,9 +468,9 @@ std::pair LMDBAL::Cursor::current () const { * \param[in] methodName a name of the method you called it from, just for the exception message if something goes not as expected * \param[in] operationName a name of the opeartion, just for the exception message if something goes not as expected * - * \throws LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor - * \throws LMDBAL::NotFound mostly thrown if the query wasn't found - * \throws LMDBAL::Unknown mostly thrown if there was some unexpected problem with lmdb + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::NotFound mostly thrown if the query wasn't found + * \exception LMDBAL::Unknown mostly thrown if there was some unexpected problem with lmdb */ template void LMDBAL::Cursor::operateCursorRead( diff --git a/src/exceptions.cpp b/src/exceptions.cpp index b401511..757e96e 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -112,7 +112,6 @@ std::string LMDBAL::StorageDuplicate::getMessage() const { + " but the database already has a storage with given name"; } - LMDBAL::Exist::Exist( const std::string& p_key, const std::string& p_dbName, @@ -130,6 +129,29 @@ std::string LMDBAL::Exist::getMessage() const { + " but it already has an element with given id"; } +LMDBAL::TransactionTerminated::TransactionTerminated( + const std::string& dbName, + const std::string& tableName, + const std::string& action +) : + dbName(dbName), + tableName(tableName), + action(action) +{} + +std::string LMDBAL::TransactionTerminated::getMessage() const { + std::string result = "Error"; + + if (!action.empty()) + result += " perfoming action " + action; + + result += " in database " + dbName; + result += ", table " + tableName; + result += ". The transaction is already terminated"; + + return result; +} + LMDBAL::Unknown::Unknown( const std::string& p_dbName, const std::string& message, diff --git a/src/exceptions.h b/src/exceptions.h index fbc1f63..48642c1 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -177,6 +177,27 @@ private: std::string tableName; }; +/** + * Thrown if there was an attempt to perform action using terminated transaction + */ +class TransactionTerminated : public Exception { +public: + /** + * \brief Creates exception + * + * \param dbName - name of the database + * \param tableName - name of the storage that was operated with + * \param action - optional action, just to enrich the exception message + */ + TransactionTerminated(const std::string& dbName, const std::string& tableName, const std::string& action = ""); + + std::string getMessage() const; +private: + std::string dbName; + std::string tableName; + std::string action; +}; + /** * \brief Thrown if something unexpected happened */ diff --git a/src/storage.cpp b/src/storage.cpp index b96ad4c..26614dd 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -56,6 +56,23 @@ void LMDBAL::iStorage::close() { mdb_dbi_close(db->environment, dbi); } +/** + * \brief Checks if the transaction is still active, returns inner TransactionID + * + * This method is for internal usage only + * + * \param[in] txn - a transaction, you want to extract ID from + * \param[in] action - a description of what you're going to do, in case of exception the error description will be verbose + * \returns - Transaction inner TransactionID + * + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& txn, const std::string& action) const { + if (!txn.isActive()) + throw TransactionTerminated(db->name, name, action); + + return txn.txn; +} /** * \brief Drops content of a storage interface @@ -68,7 +85,7 @@ void LMDBAL::iStorage::close() { void LMDBAL::iStorage::drop() { ensureOpened(dropMethodName); - TransactionID txn = db->beginTransaction(); + TransactionID txn = beginTransaction(); int rc = iStorage::drop(txn); if (rc != MDB_SUCCESS) { abortTransaction(txn); @@ -76,6 +93,7 @@ void LMDBAL::iStorage::drop() { } db->commitTransaction(txn); + handleDrop(); } /** @@ -90,6 +108,21 @@ int LMDBAL::iStorage::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } +/** + * \brief Drops content of a storage interface (public transaction variant) + * + * Just performs content drop + * + * \param[in] txn - transaction ID, must be writable transaction! + * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise + * + * \exception LMDBAL::TransactionTerminated thrown if the transaction was not active + */ +int LMDBAL::iStorage::drop(const WriteTransaction& txn) { + ensureOpened(dropMethodName); + return drop(extractTransactionId(txn, dropMethodName)); +} + /** * \brief Helper function, thows exception if the database is not opened * @@ -127,7 +160,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { } /** - * \brief Storage size (transaction variant) + * \brief Storage size (private transaction variant) * * \param[in] txn - transaction ID, can be read-only transaction * \returns amount of records in the storage @@ -143,6 +176,21 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { return stat.ms_entries; } +/** + * \brief Storage size (public transaction variant) + * + * \param[in] txn - transaction, can be read-only transaction + * \returns amount of records in the storage + * + * \exception LMDBAL::Closed thrown if the database was closed + * \exception LMDBAL::Unknown thrown if something unexpected happened + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const { + ensureOpened(countMethodName); + return count(extractTransactionId(txn, countMethodName)); +} + /** * \brief Throws LMDBAL::Exist or LMDBAL::Unknown (transaction vairiant) * @@ -391,5 +439,4 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} - - +void LMDBAL::iStorage::handleDrop() {} diff --git a/src/storage.h b/src/storage.h index 8e6e31f..1f9db9e 100644 --- a/src/storage.h +++ b/src/storage.h @@ -25,6 +25,7 @@ #include "base.h" #include "serializer.h" #include "cursor.h" +#include "transaction.h" class BaseTest; class DuplicatesTest; @@ -46,6 +47,7 @@ protected: */ virtual int open(MDB_txn * transaction) = 0; virtual void close(); + virtual void handleDrop(); bool isDBOpened() const; const std::string& dbName() const; @@ -62,6 +64,7 @@ protected: void throwNotFound(const std::string& key) const; void throwCursorNotReady(const std::string& method) const; + TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const; TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; void commitTransaction(TransactionID id); @@ -69,12 +72,14 @@ protected: virtual void transactionStarted(TransactionID txn, bool readOnly) const; virtual void transactionCommited(TransactionID txn); virtual void transactionAborted(TransactionID txn) const; + virtual int drop(TransactionID transaction); + virtual SizeType count(TransactionID txn) const; public: virtual void drop(); - virtual int drop(TransactionID transaction); + virtual int drop(const WriteTransaction& txn); virtual SizeType count() const; - virtual SizeType count(TransactionID txn) const; + virtual SizeType count(const Transaction& txn) const; protected: MDB_dbi dbi; /**<\brief lmdb storage handle*/ @@ -118,30 +123,42 @@ protected: virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const; uint32_t flags() const; + virtual void addRecord(const K& key, const V& value, TransactionID txn); + virtual bool forceRecord(const K& key, const V& value, TransactionID txn); + virtual void changeRecord(const K& key, const V& value, TransactionID txn); + virtual void removeRecord(const K& key, TransactionID txn); + virtual bool checkRecord(const K& key, TransactionID txn) const; + virtual void getRecord(const K& key, V& value, TransactionID txn) const; + virtual V getRecord(const K& key, TransactionID txn) const; + virtual std::map readAll(TransactionID txn) const; + virtual void readAll(std::map& result, TransactionID txn) const; + virtual void replaceAll(const std::map& data, TransactionID txn); + virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); + public: using iStorage::drop; virtual void addRecord(const K& key, const V& value); - virtual void addRecord(const K& key, const V& value, TransactionID txn); + virtual void addRecord(const K& key, const V& value, const WriteTransaction& txn); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change - virtual bool forceRecord(const K& key, const V& value, TransactionID txn); + virtual bool forceRecord(const K& key, const V& value, const WriteTransaction& txn); virtual void changeRecord(const K& key, const V& value); - virtual void changeRecord(const K& key, const V& value, TransactionID txn); + virtual void changeRecord(const K& key, const V& value, const WriteTransaction& txn); virtual void removeRecord(const K& key); - virtual void removeRecord(const K& key, TransactionID txn); + virtual void removeRecord(const K& key, const WriteTransaction& txn); virtual bool checkRecord(const K& key) const; //checks if there is a record with given key - virtual bool checkRecord(const K& key, TransactionID txn) const; + virtual bool checkRecord(const K& key, const Transaction& txn) const; virtual void getRecord(const K& key, V& value) const; - virtual void getRecord(const K& key, V& value, TransactionID txn) const; + virtual void getRecord(const K& key, V& value, const Transaction& txn) const; virtual V getRecord(const K& key) const; - virtual V getRecord(const K& key, TransactionID txn) const; + virtual V getRecord(const K& key, const Transaction& txn) const; virtual std::map readAll() const; - virtual std::map readAll(TransactionID txn) const; + virtual std::map readAll(const Transaction& txn) const; virtual void readAll(std::map& result) const; - virtual void readAll(std::map& result, TransactionID txn) const; + virtual void readAll(std::map& result, const Transaction& txn) const; virtual void replaceAll(const std::map& data); - virtual void replaceAll(const std::map& data, TransactionID txn); + virtual void replaceAll(const std::map& data, const WriteTransaction& txn); virtual uint32_t addRecords(const std::map& data, bool overwrite = false); - virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); + virtual uint32_t addRecords(const std::map& data, const WriteTransaction& txn, bool overwrite = false); Cursor* createCursor(); void destroyCursor(Cursor* cursor); diff --git a/src/storage.hpp b/src/storage.hpp index 8c52bf6..4662fc3 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -91,10 +91,7 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { } /** - * \brief Adds a key-value record to the storage (transaction variant) - * - * This method schedules an addition of a key-value record, but doesn't immidiately adds it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * \brief Adds a key-value record to the storage (private transaction variant) * * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown. * If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database. @@ -105,13 +102,10 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { * \param[in] txn transaction ID, needs to be a writable transaction! * * \exception LMDBAL::Exist thrown if the storage already has a record with the given key - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { - ensureOpened(addRecordMethodName); - MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData = valueSerializer.setData(value); @@ -126,6 +120,31 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI throwDuplicateOrUnknown(rc, toString(key)); } +/** + * \brief Adds a key-value record to the storage (public transaction variant) + * + * This method schedules an addition of a key-value record, but doesn't immidiately adds it. + * You can obtain LMDBAL::WriteTransaction calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown. + * If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database. + * If your storage supports duplicates LMDBAL::Exist is thrown only if the record with the same key AND already exists in the database. + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction + * + * \exception LMDBAL::Exist thrown if the storage already has a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::addRecord(const K& key, const V& value, const WriteTransaction& txn) { + ensureOpened(addRecordMethodName); + addRecord(key, value, extractTransactionId(txn, addRecordMethodName)); +} + /** * \brief Adds a key-value record to the storage, overwrites if it already exists * @@ -160,11 +179,9 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { } /** - * \brief Adds a key-value record to the storage, overwrites if it already exists (transaction variant) + * \brief Adds a key-value record to the storage, overwrites if it already exists (private transaction variant) * * This method schedules an addition of a key-value record, but doesn't immidiately add it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). - * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::Base::commitTransaction(). * * This method is mostly useless in duplicates mode. * In this mode it basically does the same thing LMDBAL::Storage::addRecord() does, @@ -176,13 +193,10 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { * \param[in] txn transaction ID, needs to be a writable transaction! * \returns true if the record was added, false otherwise * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value, TransactionID txn) { - ensureOpened(forceRecordMethodName); - bool added; if (duplicates) { try { @@ -216,6 +230,33 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio return added; } +/** + * \brief Adds a key-value record to the storage, overwrites if it already exists (public transaction variant) + * + * This method schedules an addition of a key-value record, but doesn't immidiately add it. + * You can obtain LMDBAL::WriteTransaction calling LMDBAL::Base::beginTransaction(). + * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::WriteTransaction::commit(). + * + * This method is mostly useless in duplicates mode. + * In this mode it basically does the same thing LMDBAL::Storage::addRecord() does, + * but suppresses LMDBAL::Exist exception if the record with the same key-value pair existed in the storage. + * In this case just false is returned from the method. + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction + * \returns true if the record was added, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +bool LMDBAL::Storage::forceRecord(const K& key, const V& value, const WriteTransaction& txn) { + ensureOpened(forceRecordMethodName); + return forceRecord(key, value, extractTransactionId(txn, forceRecordMethodName)); +} + /** * \brief Changes key-value record to the storage. * @@ -251,11 +292,9 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { } /** - * \brief Changes key-value record to the storage (transaction variant) + * \brief Changes key-value record to the storage (private transaction variant) * * This method schedules a modification of a key-value record, but doesn't immidiately changes it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). - * * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown * * If duplicates mode is enabled this function will find the first entry of the key @@ -270,13 +309,10 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key * \exception LMDBAL::Exist thrown in duplicates mode when the given value matches some of existing values for the given key - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { - ensureOpened(changeRecordMethodName); - MDB_cursor* cursor; int rc = mdb_cursor_open(txn, dbi, &cursor); if (rc != MDB_SUCCESS) @@ -313,6 +349,37 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti throwDuplicateOrUnknown(rc, toString(key)); } + +/** + * \brief Changes key-value record to the storage (public transaction variant) + * + * This method schedules a modification of a key-value record, but doesn't immidiately changes it. + * You can obtain LMDBAL::WriteTransaction calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * If duplicates mode is enabled this function will find the first entry of the key + * (which is pretty couterintuitive, see LMDBAL::Storage::getRecord() description) + * and change it's value to the given one. + * If the given value matches some of the other values for given key the method will throw LMDBAL::Exist, + * if no key was found it will still throw LMDBAL::NotFound. + * + * \param[in] key key of the record + * \param[in] value new value of the record + * \param[in] txn transaction + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Exist thrown in duplicates mode when the given value matches some of existing values for the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::changeRecord(const K& key, const V& value, const WriteTransaction& txn) { + ensureOpened(changeRecordMethodName); + changeRecord(key, value, extractTransactionId(txn, changeRecordMethodName)); +} + /** * \brief Gets the record from the database @@ -374,10 +441,7 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { } /** - * \brief Gets the record from the database (transaction variant) - * - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). - * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * \brief Gets the record from the database (private transaction variant) * * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown * @@ -392,22 +456,19 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { * \returns the value from the storage * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { - ensureOpened(getRecordMethodName); - V value; Storage::getRecord(key, value, txn); return value; } /** - * \brief Gets the record from the database (transaction, reference variant) + * \brief Gets the record from the database (public transaction variant) * - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). * * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown @@ -419,17 +480,40 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" * * \param[in] key key of the record you look for + * \param[in] txn transaction ID, can be read only transaction + * \returns the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +V LMDBAL::Storage::getRecord(const K& key, const Transaction& txn) const { + ensureOpened(getRecordMethodName); + return getRecord(key, extractTransactionId(txn, getRecordMethodName)); +} + +/** + * \brief Gets the record from the database (private transaction, reference variant) + * + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown + * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" + * + * \param[in] key key of the record you look for * \param[out] value the value from the storage * \param[in] txn transaction ID, can be read only transaction * * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { - ensureOpened(getRecordMethodName); - MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; @@ -440,6 +524,36 @@ void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) valueSerializer.deserialize(lmdbData, value); } + +/** + * \brief Gets the record from the database (public transaction, reference variant) + * + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown + * + * If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb. + * It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result. + * Anyway: + * - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as UNSIGNED. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by UNSIGNED comparison. + * - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50" + * + * \param[in] key key of the record you look for + * \param[out] value the value from the storage + * \param[in] txn transaction ID, can be read only transaction + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::getRecord(const K& key, V& value, const Transaction& txn) const { + ensureOpened(getRecordMethodName); + getRecord(key, value, extractTransactionId(txn, getRecordMethodName)); +} + /** * \brief Chechs if storage has value * @@ -467,22 +581,16 @@ bool LMDBAL::Storage::checkRecord(const K& key) const { } /** - * \brief Chechs if storage has value (transaction variant) - * - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). - * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * \brief Chechs if storage has value (private transaction variant) * * \param[in] key key of the record you look for * \param[in] txn transaction ID, can be read only transaction * \returns true if there was a record with given key, false otherwise * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { - ensureOpened(checkRecordMethodName); - MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; @@ -496,6 +604,26 @@ bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { return false; } +/** + * \brief Chechs if storage has value (public transaction variant) + * + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * \param[in] key key of the record you look for + * \param[in] txn transaction, can be read only transaction + * \returns true if there was a record with given key, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +bool LMDBAL::Storage::checkRecord(const K& key, const Transaction& txn) const { + ensureOpened(checkRecordMethodName); + return checkRecord(key, extractTransactionId(txn, checkRecordMethodName)); +} + /** * \brief Reads whole storage into a map * @@ -545,31 +673,25 @@ void LMDBAL::Storage::readAll(std::map& result) const { } /** - * \brief Reads whole storage into a map (transaction variant) + * \brief Reads whole storage into a map (private transaction variant) * * Basically just reads all database in an std::map, usefull when you store small storages - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). - * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). - * * In case storage supports duplicates only what lmdb considered to be lowest value * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map * * \param[in] txn transaction ID, can be read only transaction * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template std::map LMDBAL::Storage::readAll(TransactionID txn) const { - ensureOpened(readAllMethodName); - std::map result; Storage::readAll(result, txn); return result; } /** - * \brief Reads whole storage into a map (transaction, reference variant) + * \brief Reads whole storage into a map (public transaction variant) * * Basically just reads all database in an std::map, usefull when you store small storages * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). @@ -578,16 +700,32 @@ std::map LMDBAL::Storage::readAll(TransactionID txn) const { * In case storage supports duplicates only what lmdb considered to be lowest value * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map * + * \param[in] txn transaction, can be read only transaction + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +std::map LMDBAL::Storage::readAll(const Transaction& txn) const { + ensureOpened(readAllMethodName); + return readAll(extractTransactionId(txn, readAllMethodName)); +} + +/** + * \brief Reads whole storage into a map (private transaction, reference variant) + * + * Basically just reads all database in an std::map, usefull when you store small storages + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * * \param[out] result a map that is going to contain all data * \param[in] txn transaction ID, can be read only transaction * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) const { - ensureOpened(readAllMethodName); - MDB_cursor* cursor; MDB_val lmdbKey, lmdbData; @@ -610,6 +748,29 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c throwUnknown(rc); } +/** + * \brief Reads whole storage into a map (public transaction, reference variant) + * + * Basically just reads all database in an std::map, usefull when you store small storages + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * In case storage supports duplicates only what lmdb considered to be lowest value + * (see LMDBAL::Storage::getRecord() description) is returned in the resulting map + * + * \param[out] result a map that is going to contain all data + * \param[in] txn transaction, can be read only transaction + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::readAll(std::map& result, const Transaction& txn) const { + ensureOpened(readAllMethodName); + readAll(result, extractTransactionId(txn, readAllMethodName)); +} + /** * \brief Replaces the content of the whole storage with the given * @@ -636,22 +797,17 @@ void LMDBAL::Storage::replaceAll(const std::map& data) { } /** - * \brief Replaces the content of the whole storage with the given (transaction variant) + * \brief Replaces the content of the whole storage with the given (private transaction variant) * * Basically this method drops the database and adds all the records from the given map - * This method schedules a data replacement, but doesn't immidiately execute it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] data new data of the storage * \param[in] txn transaction ID, needs to be a writable transaction! * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID txn) { - ensureOpened(replaceAllMethodName); - int rc = drop(txn); if (rc != MDB_SUCCESS) throwUnknown(rc); @@ -667,6 +823,26 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID } } +/** + * \brief Replaces the content of the whole storage with the given (public transaction variant) + * + * Basically this method drops the database and adds all the records from the given map + * This method schedules a data replacement, but doesn't immidiately execute it. + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginTransaction(). + * + * \param[in] data new data of the storage + * \param[in] txn transaction + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::replaceAll(const std::map& data, const WriteTransaction& txn) { + ensureOpened(replaceAllMethodName); + replaceAll(data, extractTransactionId(txn, replaceAllMethodName)); +} + /** * \brief Adds records in bulk * @@ -696,24 +872,20 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, bool over } /** - * \brief Adds records in bulk (transaction variant) + * \brief Adds records in bulk (private transaction variant) * * This method schedules a data addition, but doesn't immidiately execute it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] data the data to be added * \param[in] txn transaction ID, needs to be a writable transaction! * \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it * \returns new actual amount of records in the storage * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::Exist thrown if overwrite==false and at least one of the keys of data already exists in the storage * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template uint32_t LMDBAL::Storage::addRecords(const std::map& data, TransactionID txn, bool overwrite) { - ensureOpened(addRecordsMethodName); - MDB_val lmdbKey, lmdbData; int rc; for (const std::pair& pair : data) { @@ -736,6 +908,28 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti return stat.ms_entries; } +/** + * \brief Adds records in bulk (public transaction variant) + * + * This method schedules a data addition, but doesn't immidiately execute it. + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginTransaction(). + * + * \param[in] data the data to be added + * \param[in] txn transaction + * \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it + * \returns new actual amount of records in the storage + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Exist thrown if overwrite==false and at least one of the keys of data already exists in the storage + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +uint32_t LMDBAL::Storage::addRecords(const std::map& data, const WriteTransaction& txn, bool overwrite) { + ensureOpened(addRecordsMethodName); + return addRecords(data, extractTransactionId(txn, addRecordsMethodName), overwrite); +} + /** * \brief Removes one of the records * @@ -763,29 +957,46 @@ void LMDBAL::Storage::removeRecord(const K& key) { } /** - * \brief Removes one of the records (transaction variant) + * \brief Removes one of the records (private transaction variant) * * Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown * This method schedules a record removal, but doesn't immidiately execute it. - * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). * * \param[in] key key of the record you wish to be removed * \param[in] txn transaction ID, needs to be a writable transaction! * - * \exception LMDBAL::Closed thrown if the database was not opened * \exception LMDBAL::NotFound thrown if the record with given key wasn't found * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb */ template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { - ensureOpened(removeRecordMethodName); - MDB_val lmdbKey = keySerializer.setData(key); int rc = mdb_del(txn, dbi, &lmdbKey, NULL); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); } +/** + * \brief Removes one of the records (transaction variant) + * + * Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown + * This method schedules a record removal, but doesn't immidiately execute it. + * You can obtain LMDBAL::Transaction calling LMDBAL::Base::beginTransaction(). + * + * \param[in] key key of the record you wish to be removed + * \param[in] txn transaction ID + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::NotFound thrown if the record with given key wasn't found + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + */ +template +void LMDBAL::Storage::removeRecord(const K& key, const WriteTransaction& txn) { + ensureOpened(removeRecordMethodName); + removeRecord(key, extractTransactionId(txn, removeRecordMethodName)); +} + /** * \brief A private virtual method I need to open each storage in the database * diff --git a/src/transaction.cpp b/src/transaction.cpp new file mode 100644 index 0000000..47744b4 --- /dev/null +++ b/src/transaction.cpp @@ -0,0 +1,71 @@ +#include "transaction.h" + +LMDBAL::Transaction::Transaction(): + txn(nullptr), + active(false), + parent(nullptr) +{} + +LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : + txn(txn), + active(true), + parent(parent) +{} + +LMDBAL::Transaction::Transaction(Transaction&& other): + txn(other.txn), + active(other.active), + parent(other.parent) +{ + other.active = false; +} + +LMDBAL::Transaction::~Transaction() { + terminate(); +} + +LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { + terminate(); + + txn = other.txn; + active = other.active; + parent = other.parent; + + other.active = false; + + return *this; +} + +void LMDBAL::Transaction::terminate() { + if (active) { + parent->abortTransaction(txn); + active = false; + } +} + +bool LMDBAL::Transaction::isActive() const { + return active; //todo may be it's better if I query it from DB? +} + +LMDBAL::WriteTransaction::WriteTransaction(TransactionID txn, Base* parent): + Transaction(txn, parent) +{} + +LMDBAL::WriteTransaction::WriteTransaction(): + Transaction() +{} + +LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other): + Transaction(std::move(other)) +{} + +void LMDBAL::WriteTransaction::abort() { + terminate(); +} + +void LMDBAL::WriteTransaction::commit() { + if (active) { + const_cast(parent)->commitTransaction(txn); + active = false; + } +} diff --git a/src/transaction.h b/src/transaction.h new file mode 100644 index 0000000..77ce26a --- /dev/null +++ b/src/transaction.h @@ -0,0 +1,46 @@ +#pragma once + +#include "base.h" + +namespace LMDBAL { +class iStorage; + +class Transaction { + friend class Base; + friend class iStorage; +public: + explicit Transaction(); + explicit Transaction(Transaction&& other); + Transaction(const Transaction& other) = delete; + Transaction& operator = (const Transaction& other) = delete; + Transaction& operator = (Transaction&& other); + virtual ~Transaction(); + + void terminate(); + bool isActive() const; + +protected: + Transaction(TransactionID txn, const Base* parent); + +protected: + TransactionID txn; + bool active; + const Base* parent; +}; + +class WriteTransaction : public Transaction { + friend class Base; +public: + explicit WriteTransaction(); + explicit WriteTransaction(WriteTransaction&& other); + WriteTransaction(const WriteTransaction& other) = delete; + WriteTransaction& operator = (const WriteTransaction& other) = delete; + + void commit(); + void abort(); + +protected: + WriteTransaction(TransactionID txn, Base* parent); +}; + +} diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index d1afa3e..dc3f3e5 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -24,6 +24,7 @@ protected: } static void TearDownTestSuite() { + transaction.terminate(); db->close(); db->removeDirectory(); delete db; @@ -35,7 +36,7 @@ protected: static LMDBAL::Base* db; static LMDBAL::Cursor* cursor; static LMDBAL::Cursor* emptyCursor; - static LMDBAL::TransactionID transaction; + static LMDBAL::Transaction transaction; LMDBAL::Cache* cache; LMDBAL::Cache* emptyCache; @@ -44,7 +45,7 @@ protected: LMDBAL::Base* CacheCursorTest::db = nullptr; LMDBAL::Cursor* CacheCursorTest::cursor = nullptr; LMDBAL::Cursor* CacheCursorTest::emptyCursor = nullptr; -LMDBAL::TransactionID CacheCursorTest::transaction = nullptr; +LMDBAL::Transaction CacheCursorTest::transaction; static const std::map data({ {245665783, "bothering nerds"}, @@ -288,7 +289,7 @@ TEST_F(CacheCursorTest, CurrentPublic) { } TEST_F(CacheCursorTest, CornerCases) { - db->abortTransaction(transaction); + transaction.terminate(); EXPECT_THROW(cursor->current(), LMDBAL::Unknown); cursor->close(); diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index f158f0e..b86fd2f 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -64,7 +64,7 @@ TEST_F(CacheTransactionsTest, Adding) { EXPECT_EQ(c1->count(), 0); EXPECT_EQ(c2->count(), 0); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); c1->addRecord(5, 13, txn); c1->addRecord(-53, 782, txn); c1->addRecord(5892, -37829, txn); @@ -76,7 +76,7 @@ TEST_F(CacheTransactionsTest, Adding) { EXPECT_EQ(c1->count(), 0); EXPECT_EQ(c2->count(), 0); - db->commitTransaction(txn); + txn.commit(); EXPECT_EQ(c1->count(), 3); EXPECT_EQ(c1->getRecord(5), 13); @@ -95,7 +95,7 @@ TEST_F(CacheTransactionsTest, Aborting) { LMDBAL::SizeType s1 = c1->count(); LMDBAL::SizeType s2 = c2->count(); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); c1->addRecord(18, 40, txn); c1->addRecord(85, -4, txn); c1->addRecord(-5, -3, txn); @@ -107,7 +107,7 @@ TEST_F(CacheTransactionsTest, Aborting) { EXPECT_EQ(c1->count(), s1); EXPECT_EQ(c2->count(), s2); - db->abortTransaction(txn); + txn.abort(); EXPECT_EQ(c1->count(), s1); EXPECT_EQ(c2->count(), s2); @@ -116,7 +116,7 @@ TEST_F(CacheTransactionsTest, Aborting) { TEST_F(CacheTransactionsTest, Reading) { EXPECT_EQ(db->ready(), true); - LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); EXPECT_EQ(c1->count(txn), 3); EXPECT_EQ(c1->getRecord(5, txn), 13); @@ -128,14 +128,14 @@ TEST_F(CacheTransactionsTest, Reading) { EXPECT_FLOAT_EQ(c2->getRecord("decallence", txn), 8532.48); EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery", txn), -64.64); - db->abortTransaction(txn); + txn.terminate(); } TEST_F(CacheTransactionsTest, ConcurentReading) { EXPECT_EQ(db->ready(), true); LMDBAL::SizeType size = c1->count(); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); EXPECT_EQ(c1->getRecord(5, txn), 13); EXPECT_EQ(c1->getRecord(5), 13); @@ -160,7 +160,7 @@ TEST_F(CacheTransactionsTest, ConcurentReading) { EXPECT_EQ(c1->count(txn), 1); EXPECT_EQ(c1->count(), size); - db->commitTransaction(txn); + txn.commit(); EXPECT_FALSE(c1->checkRecord(5)); EXPECT_EQ(c1->count(), 1); @@ -183,7 +183,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child std::cout << "beggining second transaction" << std::endl; - LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished std::cout << "checking result of the first transaction value" << std::endl; EXPECT_EQ(c1->getRecord(5, txn2), 812); @@ -198,13 +198,13 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { EXPECT_EQ(c1->getRecord(5), 812); std::cout << "commiting second transaction" << std::endl; - db->commitTransaction(txn2); + txn2.commit(); std::cout << "quitting child thread" << std::endl; exit(testing::Test::HasFailure()); } else { // I am the parent std::cout << "beggining first transaction" << std::endl; - LMDBAL::TransactionID txn1 = db->beginTransaction(); + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; usleep(5); @@ -219,7 +219,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { EXPECT_FALSE(c1->checkRecord(5)); std::cout << "commiting first transaction" << std::endl; - db->commitTransaction(txn1); + txn1.commit(); std::cout << "waiting for the other thread to finish" << std::endl; ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems diff --git a/test/duplicates.cpp b/test/duplicates.cpp index 56b63fd..db16dce 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -331,7 +331,7 @@ TEST_F(DuplicatesTest, Changing) { } TEST_F(DuplicatesTest, GettingAllRecords) { - LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); bool cycle; LMDBAL::SizeType iterations; @@ -462,6 +462,5 @@ TEST_F(DuplicatesTest, GettingAllRecords) { EXPECT_NE(iterations, 0); EXPECT_NE(k4.size(), 0); - - db->abortTransaction(txn); + txn.terminate(); } diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index 513c9e0..a75dba0 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -23,6 +23,7 @@ protected: } static void TearDownTestSuite() { + transaction.terminate(); db->close(); db->removeDirectory(); delete db; @@ -34,7 +35,7 @@ protected: static LMDBAL::Base* db; static LMDBAL::Cursor* cursor; static LMDBAL::Cursor* emptyCursor; - static LMDBAL::TransactionID transaction; + static LMDBAL::Transaction transaction; LMDBAL::Storage* table; LMDBAL::Storage* emptyTable; @@ -43,7 +44,7 @@ protected: LMDBAL::Base* StorageCursorTest::db = nullptr; LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; LMDBAL::Cursor* StorageCursorTest::emptyCursor = nullptr; -LMDBAL::TransactionID StorageCursorTest::transaction = nullptr; +LMDBAL::Transaction StorageCursorTest::transaction = LMDBAL::Transaction(); static const std::map data({ {245665783, "bothering nerds"}, @@ -265,7 +266,7 @@ TEST_F(StorageCursorTest, CurrentPublic) { } TEST_F(StorageCursorTest, CornerCases) { - db->abortTransaction(transaction); + transaction.terminate(); EXPECT_THROW(cursor->current(), LMDBAL::Unknown); cursor->close(); diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index eac7ac2..1d93946 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -64,7 +64,7 @@ TEST_F(StorageTransactionsTest, Adding) { EXPECT_EQ(t1->count(), 0); EXPECT_EQ(t2->count(), 0); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); t1->addRecord(5, 13, txn); t1->addRecord(-53, 782, txn); t1->addRecord(5892, -37829, txn); @@ -76,7 +76,7 @@ TEST_F(StorageTransactionsTest, Adding) { EXPECT_EQ(t1->count(), 0); EXPECT_EQ(t2->count(), 0); - db->commitTransaction(txn); + txn.commit(); EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t1->getRecord(5), 13); @@ -95,7 +95,7 @@ TEST_F(StorageTransactionsTest, Aborting) { LMDBAL::SizeType s1 = t1->count(); LMDBAL::SizeType s2 = t2->count(); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); t1->addRecord(18, 40, txn); t1->addRecord(85, -4, txn); t1->addRecord(-5, -3, txn); @@ -107,7 +107,7 @@ TEST_F(StorageTransactionsTest, Aborting) { EXPECT_EQ(t1->count(), s1); EXPECT_EQ(t2->count(), s2); - db->abortTransaction(txn); + txn.abort(); EXPECT_EQ(t1->count(), s1); EXPECT_EQ(t2->count(), s2); @@ -116,7 +116,7 @@ TEST_F(StorageTransactionsTest, Aborting) { TEST_F(StorageTransactionsTest, Reading) { EXPECT_EQ(db->ready(), true); - LMDBAL::TransactionID txn = db->beginReadOnlyTransaction(); + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); EXPECT_EQ(t1->count(txn), 3); EXPECT_EQ(t1->getRecord(5, txn), 13); @@ -128,14 +128,14 @@ TEST_F(StorageTransactionsTest, Reading) { EXPECT_FLOAT_EQ(t2->getRecord("decallence", txn), 8532.48); EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery", txn), -64.64); - db->abortTransaction(txn); + txn.terminate(); } TEST_F(StorageTransactionsTest, ConcurentReading) { EXPECT_EQ(db->ready(), true); LMDBAL::SizeType size = t1->count(); - LMDBAL::TransactionID txn = db->beginTransaction(); + LMDBAL::WriteTransaction txn = db->beginTransaction(); EXPECT_EQ(t1->getRecord(5, txn), 13); EXPECT_EQ(t1->getRecord(5), 13); @@ -160,7 +160,7 @@ TEST_F(StorageTransactionsTest, ConcurentReading) { EXPECT_EQ(t1->count(txn), 1); EXPECT_EQ(t1->count(), size); - db->commitTransaction(txn); + txn.commit(); EXPECT_FALSE(t1->checkRecord(5)); EXPECT_EQ(t1->count(), 1); @@ -182,7 +182,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child std::cout << "beggining second transaction" << std::endl; - LMDBAL::TransactionID txn2 = db->beginTransaction(); //<--- this is where the execution should pause + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished std::cout << "checking result of the first transaction value" << std::endl; EXPECT_EQ(t1->getRecord(5, txn2), 812); @@ -197,13 +197,13 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { EXPECT_EQ(t1->getRecord(5), 812); std::cout << "commiting second transaction" << std::endl; - db->commitTransaction(txn2); + txn2.commit(); std::cout << "quitting child thread" << std::endl; exit(testing::Test::HasFailure()); } else { // I am the parent std::cout << "beggining first transaction" << std::endl; - LMDBAL::TransactionID txn1 = db->beginTransaction(); + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; usleep(5); @@ -218,7 +218,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { EXPECT_FALSE(t1->checkRecord(5)); std::cout << "commiting first transaction" << std::endl; - db->commitTransaction(txn1); + txn1.commit(); std::cout << "waiting for the other thread to finish" << std::endl; ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems From 6b348023bb7a69e7bb608e5595dc10a95b20d8b3 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 18 Oct 2023 12:44:53 -0300 Subject: [PATCH 075/125] tests for transaction RAII behaviour, transaction documentation, minor doc fixes --- doc/mainpage.dox | 8 ++-- src/base.cpp | 18 ++++++++ src/cursor.hpp | 2 +- src/storage.cpp | 6 +++ src/transaction.cpp | 83 +++++++++++++++++++++++++++++++++++++ src/transaction.h | 6 +-- test/cachetransaction.cpp | 48 +++++++++++++++++++++ test/storagetransaction.cpp | 56 +++++++++++++++++++++++-- 8 files changed, 215 insertions(+), 12 deletions(-) diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 2192aa4..2f51d77 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -1,12 +1,12 @@ /*! \mainpage Getting Started * - * Everything begins with a data nase represented by the class LMDBAL::Base. + * Everything begins with a data base represented by the class LMDBAL::Base. * It repesents a collection of key-value storages that are going to be stored in a sigle data base directory. * To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine. * * LMDBAL::Base creates or opens existing directory with the given name in the location acquired with * QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - * so, the file system destination of your data would depend on the + * so, the file system destination of your data depends on the * QCoreApplication configuration of your app. * * After you have created a LMDBAL::Base you probably want to obtain storage handlers. @@ -15,10 +15,10 @@ * std::map * to speed up the access. * - * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&, bool) or LMDBAL::Base::addCache(const std::string& name). + * You can obtain handlers by calling LMDBAL::Base::addStorage() or LMDBAL::Base::addCache(). * Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them. * You are not obliged to save those handlers, - * you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&) + * you can obtain them at any time later using methods LMDBAL::Base::getStorage() or LMDBAL::Base::getCache() * calling them with the same template types and names. * * After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open(). diff --git a/src/base.cpp b/src/base.cpp index 604f9da..81f9bdb 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -216,6 +216,14 @@ void LMDBAL::Base::drop() { /** * \brief Begins read-only transaction * + * This is the legitimate way to retrieve LMDBAL::Transaction. + * LMDBAL::Transaction is considered runnig right after creation by this method. + * You can terminate transaction manually calling LMDBAL::Transaction::terminate + * but it's not required, because transaction will be terminated automatically + * (if it was not terminated manually) upon the call of the destructor. + * + * You can not use termitated transaction any more. + * * \returns read-only transaction * * \exception LMDBAL::Closed - thrown if the database is closed @@ -229,6 +237,16 @@ LMDBAL::Transaction LMDBAL::Base::beginReadOnlyTransaction() const { /** * \brief Begins writable transaction * + * This is the legitimate way to retrieve LMDBAL::WriteTransaction. + * LMDBAL::WriteTransaction is considered runnig right after creation by this method. + * You can commit all the changes made by this transaction calling LMDBAL::WriteTransaction::commit. + * You can cancel any changes made bu this transaction calling LMDBAL::WriteTransaction::abort + * (or LMDBAL::Transaction::terminate which LMDBAL::WriteTransaction inherits), + * but it's not required, because transaction will be aborted automatically + * (if it was not terminated (committed OR aborted) manually) upon the call of the destructor. + * + * You can not use termitated transaction any more. + * * \returns writable transaction * * \exception LMDBAL::Closed - thrown if the database is closed diff --git a/src/cursor.hpp b/src/cursor.hpp index 00fc346..cbc293f 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -186,7 +186,7 @@ void LMDBAL::Cursor::renew () const { * * This function does nothing if the cursor is closed * - * \param[in] txn a transaction you wish this cursor to be bound to + * \param[in] transaction - a transaction you wish this cursor to be bound to * * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb diff --git a/src/storage.cpp b/src/storage.cpp index 26614dd..f1258fa 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -439,4 +439,10 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} +/** + * \brief A method where database additionally handles drop + * + * It's a protected method that is called to optimise drop process + * after the transaction is commited. Used just for optimisations. + */ void LMDBAL::iStorage::handleDrop() {} diff --git a/src/transaction.cpp b/src/transaction.cpp index 47744b4..3632ad6 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -1,17 +1,42 @@ #include "transaction.h" +/** + * \class LMDBAL::Transaction + * \brief Public read only transaction + * + * This class provides read only transactions you can use + * to speed to your queries keeping them thread safe. + * LMDBAL::Transaction is NOT COPYABLE but MOVABLE. + * Transaction can be in one of two states: active or terminated. + * The way to receive an active LMDBAL::Transaction is to call LMDBAL::Base::beginReadOnlyTransaction. + * + * Active transactions become terminated upon the call of LMDBAL::Transaction::terminate. + * Active transactions automaticaly terminate themselves upon destruction. + * + * You CAN NOT use inactive transactions for any query. + */ + +/** + * \brief Constructs inactive transaction + */ LMDBAL::Transaction::Transaction(): txn(nullptr), active(false), parent(nullptr) {} +/** + * \brief Constructs an active transaction + */ LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : txn(txn), active(true), parent(parent) {} +/** + * \brief Moves transaction to a new object + */ LMDBAL::Transaction::Transaction(Transaction&& other): txn(other.txn), active(other.active), @@ -20,10 +45,16 @@ LMDBAL::Transaction::Transaction(Transaction&& other): other.active = false; } +/** + * \brief Destroys transaction + */ LMDBAL::Transaction::~Transaction() { terminate(); } +/** + * \brief Move-assigns transaction to the new object + */ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { terminate(); @@ -36,6 +67,11 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { return *this; } +/** + * \brief Terminates transaction if it was active + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::Transaction::terminate() { if (active) { parent->abortTransaction(txn); @@ -43,26 +79,73 @@ void LMDBAL::Transaction::terminate() { } } +/** + * \brief Returns transaction states + * + * \returns true if the transaction is active, false otherwise + */ bool LMDBAL::Transaction::isActive() const { return active; //todo may be it's better if I query it from DB? } + +/** + * \class LMDBAL::WriteTransaction + * \brief Public writable transaction + * + * This class provides writable transactions you can use + * to speed to your queries and modifications keeping them thread safe. + * LMDBAL::WriteTransaction is NOT COPYABLE but MOVABLE. + * Transaction can be in one of two states: active or terminated. + * The way to receive an active LMDBAL::WriteTransaction is to call LMDBAL::Base::beginTransaction. + * You can use LMDBAL::WriteTransaction for everything instead of LMDBAL::Transaction + * + * Active transactions become terminated upon the call of + * LMDBAL::WriteTransaction::abort or LMDBAL::WriteTransaction::commit. + * Calling LMDBAL::Transaction::terminate on LMDBAL::WriteTransaction + * is exactly the same as calling LMDBAL::WriteTransaction::abort. + * + * Active transactions automaticaly terminate themselves upon destruction. + * For LMDBAL::WriteTransaction default behaviour upon destruction is to abort. + * + * You CAN NOT use inactive transactions for any query. + */ + +/** + * \brief Constructs active write transaction + */ LMDBAL::WriteTransaction::WriteTransaction(TransactionID txn, Base* parent): Transaction(txn, parent) {} +/** + * \brief Constructs inactive write transaction + */ LMDBAL::WriteTransaction::WriteTransaction(): Transaction() {} +/** + * \brief Moves transaction to the new object + */ LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other): Transaction(std::move(other)) {} +/** + * \brief Aborts transaction cancelling all changes + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::WriteTransaction::abort() { terminate(); } +/** + * \brief Commits transaction submitting all changes + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::WriteTransaction::commit() { if (active) { const_cast(parent)->commitTransaction(txn); diff --git a/src/transaction.h b/src/transaction.h index 77ce26a..43bb69e 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -23,9 +23,9 @@ protected: Transaction(TransactionID txn, const Base* parent); protected: - TransactionID txn; - bool active; - const Base* parent; + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ }; class WriteTransaction : public Transaction { diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index b86fd2f..6092dfb 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -229,3 +229,51 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { EXPECT_EQ(c1->getRecord(5), -46); } +TEST_F(CacheTransactionsTest, RAIIResourceFree) { + EXPECT_EQ(db->ready(), true); + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining child transaction" << std::endl; + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the parent transaction value" << std::endl; + EXPECT_FALSE(c1->checkRecord(221, txn2)); + + std::cout << "performing modification from the child thread" << std::endl; + c1->addRecord(221, 14, txn2); + + std::cout << "commiting child transaction" << std::endl; + txn2.commit(); + + std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining parent transaction" << std::endl; + { + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + std::cout << "parent thread woke up" << std::endl; + + std::cout << "adding value from parent thread" << std::endl; + c1->addRecord(221, 320, txn1); + + std::cout << "checking value from parent thread using transaction" << std::endl; + EXPECT_EQ(c1->getRecord(221, txn1), 320); + + std::cout << "checking value independently from parent thread" << std::endl; + EXPECT_FALSE(c1->checkRecord(221)); + + std::cout << "implicitly aborting transaction by leaving the scope" << std::endl; + } + + std::cout << "child thread should resume after this line" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking the final result" << std::endl; + EXPECT_EQ(c1->getRecord(221), 14); +} + diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 1d93946..ad21846 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -14,7 +14,7 @@ protected: ~StorageTransactionsTest() {} - int waitForChildFork(int pid) { + int waitForChildFork(int pid) { int status; if (0 > waitpid(pid, &status, 0)) { std::cerr << "[----------] Waitpid error!" << std::endl; @@ -22,9 +22,9 @@ protected: } if (WIFEXITED(status)) { const int exit_status = WEXITSTATUS(status); - if (exit_status != 0) { + if (exit_status != 0) std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl; - } + return exit_status; } else { std::cerr << "[----------] Non-normal exit from child!" << std::endl; @@ -224,6 +224,54 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems } - std::cout << "checking final result" << std::endl; + std::cout << "checking the final result" << std::endl; EXPECT_EQ(t1->getRecord(5), -46); } + +TEST_F(StorageTransactionsTest, RAIIResourceFree) { + EXPECT_EQ(db->ready(), true); + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining child transaction" << std::endl; + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the parent transaction value" << std::endl; + EXPECT_FALSE(t1->checkRecord(221, txn2)); + + std::cout << "performing modification from the child thread" << std::endl; + t1->addRecord(221, 14, txn2); + + std::cout << "commiting child transaction" << std::endl; + txn2.commit(); + + std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining parent transaction" << std::endl; + { + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + std::cout << "parent thread woke up" << std::endl; + + std::cout << "adding value from parent thread" << std::endl; + t1->addRecord(221, 320, txn1); + + std::cout << "checking value from parent thread using transaction" << std::endl; + EXPECT_EQ(t1->getRecord(221, txn1), 320); + + std::cout << "checking value independently from parent thread" << std::endl; + EXPECT_FALSE(t1->checkRecord(221)); + + std::cout << "implicitly aborting transaction by leaving the scope" << std::endl; + } + + std::cout << "child thread should resume after this line" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking the final result" << std::endl; + EXPECT_EQ(t1->getRecord(221), 14); +} From 0079f6e96e4cadb45588104c888f95943971ba12 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 21 Oct 2023 16:41:41 -0300 Subject: [PATCH 076/125] a bug fix, reorganization and 0.5.1 --- CHANGELOG.md | 5 ++++- CMakeLists.txt | 5 +---- src/CMakeLists.txt | 18 +++------------- src/cache.hpp | 15 +++++-------- src/serializer/CMakeLists.txt | 21 +++++++++++++++++++ src/{ => serializer}/serializer.h | 0 src/{ => serializer}/serializer.hpp | 0 src/{ => serializer}/serializer_double.hpp | 0 src/{ => serializer}/serializer_float.hpp | 0 src/{ => serializer}/serializer_int16.hpp | 0 src/{ => serializer}/serializer_int32.hpp | 0 src/{ => serializer}/serializer_int64.hpp | 0 src/{ => serializer}/serializer_int8.hpp | 0 .../serializer_qbytearray.hpp | 0 src/{ => serializer}/serializer_qstring.hpp | 0 src/{ => serializer}/serializer_stdstring.hpp | 0 src/{ => serializer}/serializer_uint16.hpp | 0 src/{ => serializer}/serializer_uint32.hpp | 0 src/{ => serializer}/serializer_uint64.hpp | 0 src/{ => serializer}/serializer_uint8.hpp | 0 test/CMakeLists.txt | 2 +- test/storagetransaction.cpp | 2 ++ 22 files changed, 37 insertions(+), 31 deletions(-) create mode 100644 src/serializer/CMakeLists.txt rename src/{ => serializer}/serializer.h (100%) rename src/{ => serializer}/serializer.hpp (100%) rename src/{ => serializer}/serializer_double.hpp (100%) rename src/{ => serializer}/serializer_float.hpp (100%) rename src/{ => serializer}/serializer_int16.hpp (100%) rename src/{ => serializer}/serializer_int32.hpp (100%) rename src/{ => serializer}/serializer_int64.hpp (100%) rename src/{ => serializer}/serializer_int8.hpp (100%) rename src/{ => serializer}/serializer_qbytearray.hpp (100%) rename src/{ => serializer}/serializer_qstring.hpp (100%) rename src/{ => serializer}/serializer_stdstring.hpp (100%) rename src/{ => serializer}/serializer_uint16.hpp (100%) rename src/{ => serializer}/serializer_uint32.hpp (100%) rename src/{ => serializer}/serializer_uint64.hpp (100%) rename src/{ => serializer}/serializer_uint8.hpp (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 990f0e6..8329234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog -## LMDBAL 0.5.1 (UNRELEASED) +## LMDBAL 0.5.1 (October 21, 2023) ### Improvements - RAII transactions - reduced overhead for private transaction finctions +### Bug fixes +- bug fix with cache fallthough + ## LMDBAL 0.5.0 (October 15, 2023) ### New Features - duplicates support (only for table) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d73014..6825b0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,10 +74,7 @@ if (BUILD_TESTS) add_subdirectory(test) endif () -target_include_directories(${PROJECT_NAME} PUBLIC - "$" - "$" -) +target_include_directories(${PROJECT_NAME} PUBLIC "$") target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 564a59b..2da5cd8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,25 +14,13 @@ set(HEADERS cursor.hpp cache.h cache.hpp - serializer.h - serializer.hpp - serializer_uint8.hpp - serializer_uint16.hpp - serializer_uint32.hpp - serializer_uint64.hpp - serializer_int8.hpp - serializer_int16.hpp - serializer_int32.hpp - serializer_int64.hpp - serializer_float.hpp - serializer_double.hpp - serializer_stdstring.hpp - serializer_qstring.hpp - serializer_qbytearray.hpp operators.hpp transaction.h ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC "$") + +add_subdirectory(serializer) install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) diff --git a/src/cache.hpp b/src/cache.hpp index 52b17de..e9ae0aa 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -841,27 +841,23 @@ void LMDBAL::Cache::handleTransactionEntry(const Entry& entry) { std::pair* pair = static_cast*>(entry.second); handleAddRecord(pair->first, pair->second); delete pair; - } - break; + } break; case Operation::remove: { K* key = static_cast(entry.second); handleRemoveRecord(*key); delete key; - } - - break; + } break; case Operation::change: { std::pair* pair = static_cast*>(entry.second); handleChangeRecord(pair->first, pair->second); delete pair; - } + } break; case Operation::force: { std::tuple* tuple = static_cast*>(entry.second); const std::tuple& t = *tuple; handleForceRecord(std::get<1>(t), std::get<2>(t), std::get<0>(t)); delete tuple; - } - break; + } break; case Operation::drop: handleDrop(); break; @@ -873,8 +869,7 @@ void LMDBAL::Cache::handleTransactionEntry(const Entry& entry) { const std::tuple>& t = * tuple; handleAddRecords(std::get<2>(t), std::get<0>(t), std::get<1>(t)); delete tuple; - } - break; + } break; } } diff --git a/src/serializer/CMakeLists.txt b/src/serializer/CMakeLists.txt new file mode 100644 index 0000000..f4148e9 --- /dev/null +++ b/src/serializer/CMakeLists.txt @@ -0,0 +1,21 @@ +set(HEADERS + serializer.h + serializer.hpp + serializer_uint8.hpp + serializer_uint16.hpp + serializer_uint32.hpp + serializer_uint64.hpp + serializer_int8.hpp + serializer_int16.hpp + serializer_int32.hpp + serializer_int64.hpp + serializer_float.hpp + serializer_double.hpp + serializer_stdstring.hpp + serializer_qstring.hpp + serializer_qbytearray.hpp +) + +target_include_directories(${PROJECT_NAME} PUBLIC "$") + +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) diff --git a/src/serializer.h b/src/serializer/serializer.h similarity index 100% rename from src/serializer.h rename to src/serializer/serializer.h diff --git a/src/serializer.hpp b/src/serializer/serializer.hpp similarity index 100% rename from src/serializer.hpp rename to src/serializer/serializer.hpp diff --git a/src/serializer_double.hpp b/src/serializer/serializer_double.hpp similarity index 100% rename from src/serializer_double.hpp rename to src/serializer/serializer_double.hpp diff --git a/src/serializer_float.hpp b/src/serializer/serializer_float.hpp similarity index 100% rename from src/serializer_float.hpp rename to src/serializer/serializer_float.hpp diff --git a/src/serializer_int16.hpp b/src/serializer/serializer_int16.hpp similarity index 100% rename from src/serializer_int16.hpp rename to src/serializer/serializer_int16.hpp diff --git a/src/serializer_int32.hpp b/src/serializer/serializer_int32.hpp similarity index 100% rename from src/serializer_int32.hpp rename to src/serializer/serializer_int32.hpp diff --git a/src/serializer_int64.hpp b/src/serializer/serializer_int64.hpp similarity index 100% rename from src/serializer_int64.hpp rename to src/serializer/serializer_int64.hpp diff --git a/src/serializer_int8.hpp b/src/serializer/serializer_int8.hpp similarity index 100% rename from src/serializer_int8.hpp rename to src/serializer/serializer_int8.hpp diff --git a/src/serializer_qbytearray.hpp b/src/serializer/serializer_qbytearray.hpp similarity index 100% rename from src/serializer_qbytearray.hpp rename to src/serializer/serializer_qbytearray.hpp diff --git a/src/serializer_qstring.hpp b/src/serializer/serializer_qstring.hpp similarity index 100% rename from src/serializer_qstring.hpp rename to src/serializer/serializer_qstring.hpp diff --git a/src/serializer_stdstring.hpp b/src/serializer/serializer_stdstring.hpp similarity index 100% rename from src/serializer_stdstring.hpp rename to src/serializer/serializer_stdstring.hpp diff --git a/src/serializer_uint16.hpp b/src/serializer/serializer_uint16.hpp similarity index 100% rename from src/serializer_uint16.hpp rename to src/serializer/serializer_uint16.hpp diff --git a/src/serializer_uint32.hpp b/src/serializer/serializer_uint32.hpp similarity index 100% rename from src/serializer_uint32.hpp rename to src/serializer/serializer_uint32.hpp diff --git a/src/serializer_uint64.hpp b/src/serializer/serializer_uint64.hpp similarity index 100% rename from src/serializer_uint64.hpp rename to src/serializer/serializer_uint64.hpp diff --git a/src/serializer_uint8.hpp b/src/serializer/serializer_uint8.hpp similarity index 100% rename from src/serializer_uint8.hpp rename to src/serializer/serializer_uint8.hpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ab59a88..3994aac 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,7 +12,7 @@ add_executable(runUnitTests duplicates.cpp ) -target_compile_options(runUnitTests PRIVATE -fPIC) +target_compile_options(runUnitTests PRIVATE -fPIC -Wall -Wextra -O0) target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}/src) target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index ad21846..4f04931 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -181,6 +181,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child + usleep(1); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -233,6 +234,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child + usleep(1); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished From a0eebc978d82f71e31b0766fa97b1d9dd38ab009 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 21 Oct 2023 16:45:18 -0300 Subject: [PATCH 077/125] a sleep to confurency test --- test/cachetransaction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index 6092dfb..e0bac76 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -182,6 +182,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child + usleep(1); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -234,6 +235,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child + usleep(1); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished From 96d7d9ef641854e987ecc8d28151e1f55d2f9035 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 25 Oct 2023 19:18:23 -0300 Subject: [PATCH 078/125] First attempt to make RAII cursors, no tests yet --- CHANGELOG.md | 6 +- CMakeLists.txt | 2 +- packaging/Archlinux/PKGBUILD | 2 +- src/cursor.h | 46 ++++++---- src/cursor.hpp | 168 ++++++++++++++++++++++++++++++----- src/exceptions.cpp | 8 ++ src/exceptions.h | 17 ++++ src/storage.h | 10 ++- src/storage.hpp | 34 ++----- test/cachecursor.cpp | 141 ++++++++++++++--------------- test/duplicates.cpp | 32 +++---- test/storagecursor.cpp | 141 ++++++++++++++--------------- 12 files changed, 374 insertions(+), 233 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8329234..d596471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # Changelog +## LMDBAL 0.5.2 (October 21, 2023) +### Improvements +- RAII cursors + ## LMDBAL 0.5.1 (October 21, 2023) ### Improvements - RAII transactions -- reduced overhead for private transaction finctions +- reduced overhead for private transaction functions ### Bug fixes - bug fix with cache fallthough diff --git a/CMakeLists.txt b/CMakeLists.txt index 6825b0e..3231cf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.5.1 + VERSION 0.5.2 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 4c0362b..0232f4e 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.5.1 +pkgver=0.5.2 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') diff --git a/src/cursor.h b/src/cursor.h index 4f0a355..2a5114b 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -38,37 +38,49 @@ private: openedPrivate /**< - opened with private transaction, only current storage will be notified when cursor is closed*/ }; +public: + Cursor(); Cursor(Storage* parent); + Cursor(const Cursor& other) = delete; + Cursor(Cursor&& other); ~Cursor(); -public: - void open() const; - void open(const Transaction& transaction) const; - void renew() const; - void renew(const Transaction& transaction) const; - void close() const; + Cursor& operator = (const Cursor& other) = delete; + Cursor& operator = (Cursor&& other); + + void open(); + void open(const Transaction& transaction); + void renew(); + void renew(const Transaction& transaction); + void close(); bool opened() const; + bool empty() const; - std::pair first() const; - std::pair last() const; - std::pair next() const; - std::pair prev() const; + void drop(); + + std::pair first(); + std::pair last(); + std::pair next(); + std::pair prev(); std::pair current() const; + bool set(const K& target); - void first(K& key, V& value) const; - void last(K& key, V& value) const; - void next(K& key, V& value) const; - void prev(K& key, V& value) const; + void first(K& key, V& value); + void last(K& key, V& value); + void next(K& key, V& value); + void prev(K& key, V& value); void current(K& key, V& value) const; private: - void terminated() const; + void dropped(); + void terminated(); void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; private: Storage* storage; - mutable MDB_cursor* cursor; - mutable State state; + MDB_cursor* cursor; + State state; + uint32_t id; inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ diff --git a/src/cursor.hpp b/src/cursor.hpp index cbc293f..027c6fd 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -41,6 +41,8 @@ * You are not supposed to instantiate or destory instances of this class yourself! */ +static uint32_t idCounter = 0; + /** * \brief Creates a cursor * @@ -50,9 +52,74 @@ template LMDBAL::Cursor::Cursor(Storage* parent): storage(parent), cursor(nullptr), - state(closed) + state(closed), + id(++idCounter) +{ + storage->cursors[id] = this; +} + +/** + * \brief Creates an empty cursor. + * + * It's not usable, but can exist just to be a target of moves + */ +template +LMDBAL::Cursor::Cursor(): + storage(nullptr), + cursor(nullptr), + state(closed), + id(0) {} +/** + * \brief Moves from another cursor + */ +template +LMDBAL::Cursor::Cursor(Cursor&& other): + storage(other.storage), + cursor(other.cursor), + state(other.state), + id(other.id) +{ + if (id != 0) + storage->cursors[id] = this; + + other.cursor = nullptr; + other.storage = nullptr; + other.id = 0; + other.state = closed; +} + +/** + * \brief A private function that turns cursor into an empty one + * + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. + * Those cursors will become empty, and can't be used anymore + */ +template +LMDBAL::Cursor& LMDBAL::Cursor::operator = (Cursor&& other) { + terminated(); + + if (id != 0) + storage->cursors.erase(id); + + storage = other.storage; + cursor = other.cursor; + state = other.state; + id = other.id; + + if (id != 0) { + other.cursor = nullptr; + other.storage = nullptr; + other.id = 0; + other.state = closed; + + storage->cursors[id] = this; + } + + return *this; +} + /** * \brief Destroys a cursor * @@ -61,13 +128,58 @@ LMDBAL::Cursor::Cursor(Storage* parent): template LMDBAL::Cursor::~Cursor () { close(); + + if (id != 0) + storage->cursors.erase(id); +} + +/** + * \brief Turns cursor into an empty one, releasing resources + * + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. + * Those cursors will become empty, and can't be used anymore + */ +template +void LMDBAL::Cursor::drop () { + close(); + + if (id != 0) + storage->cursors.erase(id); + + cursor = nullptr; + storage = nullptr; + id = 0; +} + +/** + * \brief A private method that turns cursor into an empty one + * + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. + * Those cursors will become empty, and can't be used anymore + */ +template +void LMDBAL::Cursor::dropped () { + terminated(); + cursor = nullptr; + storage = nullptr; + id = 0; +} + +/** + * \brief Returns true if the cursor is empty + * + * Empty cursors can't be used, they can be only targets of move operations + */ +template +bool LMDBAL::Cursor::empty () const { + return id == 0; } /** * \brief A private function the storage owning this cursor will call to inform this cursor that the thansaction needs to be aborted */ template -void LMDBAL::Cursor::terminated () const { +void LMDBAL::Cursor::terminated () { close(); //for now it's the same, but if I ever going to make writable cursor - here is where it's gonna be different } @@ -80,11 +192,15 @@ void LMDBAL::Cursor::terminated () const { * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! * It will do nothing to a cursor that was already opened (no matter what way). * - * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty */ template -void LMDBAL::Cursor::open () const { +void LMDBAL::Cursor::open () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + storage->ensureOpened(openCursorMethodName); switch (state) { case closed: { @@ -115,9 +231,13 @@ void LMDBAL::Cursor::open () const { * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty */ template -void LMDBAL::Cursor::open (const Transaction& transaction) const { +void LMDBAL::Cursor::open (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + storage->ensureOpened(openCursorMethodName); TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); switch (state) { @@ -146,11 +266,15 @@ void LMDBAL::Cursor::open (const Transaction& transaction) const { * * This function does nothing if the cursor is closed * - * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty */ template -void LMDBAL::Cursor::renew () const { +void LMDBAL::Cursor::renew () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { @@ -191,9 +315,13 @@ void LMDBAL::Cursor::renew () const { * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty */ template -void LMDBAL::Cursor::renew (const Transaction& transaction) const { +void LMDBAL::Cursor::renew (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + storage->ensureOpened(renewCursorMethodName); TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); switch (state) { @@ -226,7 +354,7 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) const { * This function does nothing on a closed cursor. */ template -void LMDBAL::Cursor::close () const { +void LMDBAL::Cursor::close () { switch (state) { case openedPublic: { mdb_cursor_close(cursor); @@ -267,7 +395,7 @@ bool LMDBAL::Cursor::opened () const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::first (K& key, V& value) const { +void LMDBAL::Cursor::first (K& key, V& value) { operateCursorRead(key, value, MDB_FIRST, firstMethodName, firstOperationName); } @@ -284,7 +412,7 @@ void LMDBAL::Cursor::first (K& key, V& value) const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::last (K& key, V& value) const { +void LMDBAL::Cursor::last (K& key, V& value) { operateCursorRead(key, value, MDB_LAST, lastMethodName, lastOperationName); } @@ -307,7 +435,7 @@ void LMDBAL::Cursor::last (K& key, V& value) const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::next (K& key, V& value) const { +void LMDBAL::Cursor::next (K& key, V& value) { operateCursorRead(key, value, MDB_NEXT, nextMethodName, nextOperationName); } @@ -330,7 +458,7 @@ void LMDBAL::Cursor::next (K& key, V& value) const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::prev (K& key, V& value) const { +void LMDBAL::Cursor::prev (K& key, V& value) { operateCursorRead(key, value, MDB_PREV, prevMethodName, prevOperationName); } @@ -349,7 +477,7 @@ void LMDBAL::Cursor::prev (K& key, V& value) const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::current (K& key, V& value) const { +void LMDBAL::Cursor::current (K& key, V& value) const { operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName); } @@ -365,7 +493,7 @@ void LMDBAL::Cursor::current (K& key, V& value) const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -std::pair LMDBAL::Cursor::first () const { +std::pair LMDBAL::Cursor::first () { std::pair result; operateCursorRead(result.first, result.second, MDB_FIRST, firstMethodName, firstOperationName); return result; @@ -383,7 +511,7 @@ std::pair LMDBAL::Cursor::first () const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -std::pair LMDBAL::Cursor::last () const { +std::pair LMDBAL::Cursor::last () { std::pair result; operateCursorRead(result.first, result.second, MDB_LAST, lastMethodName, lastOperationName); return result; @@ -407,7 +535,7 @@ std::pair LMDBAL::Cursor::last () const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -std::pair LMDBAL::Cursor::next () const { +std::pair LMDBAL::Cursor::next () { std::pair result; operateCursorRead(result.first, result.second, MDB_NEXT, nextMethodName, nextOperationName); return result; @@ -431,7 +559,7 @@ std::pair LMDBAL::Cursor::next () const { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -std::pair LMDBAL::Cursor::prev () const { +std::pair LMDBAL::Cursor::prev () { std::pair result; operateCursorRead(result.first, result.second, MDB_PREV, prevMethodName, prevOperationName); return result; diff --git a/src/exceptions.cpp b/src/exceptions.cpp index 757e96e..c0d618a 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -72,6 +72,14 @@ std::string LMDBAL::CursorNotReady::getMessage() const { return msg; } +LMDBAL::CursorEmpty::CursorEmpty(const std::string & operation): + Exception(), + operation(operation) {} + +std::string LMDBAL::CursorEmpty::getMessage() const { + return "An attempt to perform an operation \"" + operation + "\" on an empty cursor"; +} + LMDBAL::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), diff --git a/src/exceptions.h b/src/exceptions.h index 48642c1..58bbea4 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -97,6 +97,23 @@ private: std::string tableName; }; +/** + * \brief Thrown if an empty cursor was somehow operated + */ +class CursorEmpty : public Exception { +public: + /** + * \brief Creates exception + * + * \param operation - text name of the method that was called on an empty cursor + */ + CursorEmpty(const std::string& operation); + + std::string getMessage() const; +private: + std::string operation; +}; + /** * \brief Thrown if something in the database was called on opened state and it is not supported */ diff --git a/src/storage.h b/src/storage.h index 1f9db9e..de38568 100644 --- a/src/storage.h +++ b/src/storage.h @@ -29,6 +29,8 @@ class BaseTest; class DuplicatesTest; +class CacheCursorTest; +class StorageCursorTest; namespace LMDBAL { @@ -113,6 +115,8 @@ template class Storage : public iStorage { friend class ::BaseTest; friend class ::DuplicatesTest; + friend class ::CacheCursorTest; + friend class ::StorageCursorTest; friend class Base; friend class Cursor; protected: @@ -160,13 +164,13 @@ public: virtual uint32_t addRecords(const std::map& data, bool overwrite = false); virtual uint32_t addRecords(const std::map& data, const WriteTransaction& txn, bool overwrite = false); - Cursor* createCursor(); - void destroyCursor(Cursor* cursor); + Cursor createCursor(); + void destroyCursor(Cursor& cursor); protected: mutable Serializer keySerializer; /**<\brief internal object that would serialize and deserialize keys*/ mutable Serializer valueSerializer; /**<\brief internal object that would serialize and deserialize values*/ - std::set*> cursors; /**<\brief a set of cursors that has been created under this storage*/ + std::map*> cursors; /**<\brief a set of cursors that has been created under this storage*/ int open(MDB_txn* transaction) override; void close() override; diff --git a/src/storage.hpp b/src/storage.hpp index 4662fc3..f533217 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -58,8 +58,8 @@ LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool dupli */ template LMDBAL::Storage::~Storage() { - for (Cursor* cursor : cursors) - delete cursor; + for (const std::pair*>& pair : cursors) + pair.second->dropped(); } /** @@ -1013,8 +1013,8 @@ int LMDBAL::Storage::open(MDB_txn* transaction) { */ template void LMDBAL::Storage::close() { - for (Cursor* cursor : cursors) - cursor->terminated(); + for (const std::pair*>& pair : cursors) + pair.second->terminated(); iStorage::close(); } @@ -1025,11 +1025,8 @@ void LMDBAL::Storage::close() { * \returns LMDBAL::Cursor for this storage and returs you a pointer to a created cursor */ template -LMDBAL::Cursor* LMDBAL::Storage::createCursor() { - Cursor* cursor = new Cursor(this); - cursors.insert(cursor); - - return cursor; +LMDBAL::Cursor LMDBAL::Storage::createCursor() { + return Cursor(this); } /** @@ -1056,25 +1053,6 @@ uint32_t LMDBAL::Storage::flags() const { return result; } -/** - * \brief Destroys cursor - * - * This a normal way to discard a cursor you don't need anymore - * - * \param[in] cursor a pointer to a cursor you want to destroy - * - * \exception LMDBAL::Unknown thrown if you try to destroy something that this storage didn't create - */ -template -void LMDBAL::Storage::destroyCursor(Cursor* cursor) { - typename std::set*>::const_iterator itr = cursors.find(cursor); - if (itr == cursors.end()) - throwUnknown("An attempt to destroy a cursor the storage doesn't own"); - - cursors.erase(itr); - delete cursor; -} - /** * \brief A private virtual method that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache * diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index dc3f3e5..c988bc1 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -29,13 +29,10 @@ protected: db->removeDirectory(); delete db; db = nullptr; - cursor = nullptr; - emptyCursor = nullptr; } static LMDBAL::Base* db; - static LMDBAL::Cursor* cursor; - static LMDBAL::Cursor* emptyCursor; + static LMDBAL::Cursor cursor; static LMDBAL::Transaction transaction; LMDBAL::Cache* cache; @@ -43,8 +40,7 @@ protected: }; LMDBAL::Base* CacheCursorTest::db = nullptr; -LMDBAL::Cursor* CacheCursorTest::cursor = nullptr; -LMDBAL::Cursor* CacheCursorTest::emptyCursor = nullptr; +LMDBAL::Cursor CacheCursorTest::cursor; LMDBAL::Transaction CacheCursorTest::transaction; static const std::map data({ @@ -67,21 +63,20 @@ TEST_F(CacheCursorTest, PopulatingTheTable) { TEST_F(CacheCursorTest, Creation) { cursor = cache->createCursor(); - emptyCursor = emptyCache->createCursor(); - EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); - cursor->open(); + cursor.open(); } TEST_F(CacheCursorTest, FirstPrivate) { EXPECT_EQ(cache->count(), data.size()); - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -94,16 +89,16 @@ TEST_F(CacheCursorTest, NextPrivate) { reference++; for (; reference != data.end(); ++reference) { - std::pair element = cursor->next(); + std::pair element = cursor.next(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_EQ(cache->count(), data.size()); } - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); EXPECT_EQ(cache->count(), data.size()); - std::pair element = cursor->first(); + std::pair element = cursor.first(); reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -114,7 +109,7 @@ TEST_F(CacheCursorTest, NextPrivate) { TEST_F(CacheCursorTest, LastPrivate) { EXPECT_EQ(cache->count(), data.size()); - std::pair element = cursor->last(); + std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -127,16 +122,16 @@ TEST_F(CacheCursorTest, PrevPrivate) { reference++; for (; reference != data.rend(); ++reference) { - std::pair element = cursor->prev(); + std::pair element = cursor.prev(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_EQ(cache->count(), data.size()); } - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); EXPECT_EQ(cache->count(), data.size()); - std::pair element = cursor->last(); + std::pair element = cursor.last(); reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -145,29 +140,29 @@ TEST_F(CacheCursorTest, PrevPrivate) { } TEST_F(CacheCursorTest, CurrentPrivate) { - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(cache->count(), data.size()); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - element = cursor->current(); + cursor.next(); + element = cursor.current(); ++reference; EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - cursor->next(); - cursor->prev(); - element = cursor->current(); + cursor.next(); + cursor.next(); + cursor.prev(); + element = cursor.current(); ++reference; ++reference; --reference; @@ -178,16 +173,15 @@ TEST_F(CacheCursorTest, CurrentPrivate) { } TEST_F(CacheCursorTest, Destruction) { - cursor->close(); + cursor.close(); - EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); - EXPECT_THROW(emptyCache->destroyCursor(cursor), LMDBAL::Unknown); - cache->destroyCursor(cursor); + cursor = LMDBAL::Cursor(); cursor = cache->createCursor(); } @@ -195,8 +189,8 @@ TEST_F(CacheCursorTest, Destruction) { TEST_F(CacheCursorTest, FirstPublic) { transaction = db->beginTransaction(); - cursor->open(transaction); - std::pair element = cursor->first(); + cursor.open(transaction); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -209,16 +203,16 @@ TEST_F(CacheCursorTest, NextPublic) { reference++; for (; reference != data.end(); ++reference) { - std::pair element = cursor->next(); + std::pair element = cursor.next(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_EQ(cache->count(), data.size()); } - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); EXPECT_EQ(cache->count(), data.size()); - std::pair element = cursor->first(); + std::pair element = cursor.first(); reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -226,7 +220,7 @@ TEST_F(CacheCursorTest, NextPublic) { } TEST_F(CacheCursorTest, LastPublic) { - std::pair element = cursor->last(); + std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -239,15 +233,15 @@ TEST_F(CacheCursorTest, PrevPublic) { reference++; for (; reference != data.rend(); ++reference) { - std::pair element = cursor->prev(); + std::pair element = cursor.prev(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_EQ(cache->count(), data.size()); } - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); - std::pair element = cursor->last(); + std::pair element = cursor.last(); reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -256,29 +250,29 @@ TEST_F(CacheCursorTest, PrevPublic) { } TEST_F(CacheCursorTest, CurrentPublic) { - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(cache->count(), data.size()); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - element = cursor->current(); + cursor.next(); + element = cursor.current(); ++reference; EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - cursor->next(); - cursor->prev(); - element = cursor->current(); + cursor.next(); + cursor.next(); + cursor.prev(); + element = cursor.current(); ++reference; ++reference; --reference; @@ -290,38 +284,39 @@ TEST_F(CacheCursorTest, CurrentPublic) { TEST_F(CacheCursorTest, CornerCases) { transaction.terminate(); - EXPECT_THROW(cursor->current(), LMDBAL::Unknown); - cursor->close(); + EXPECT_THROW(cursor.current(), LMDBAL::Unknown); + cursor.close(); - emptyCursor->open(); - EXPECT_THROW(emptyCursor->first(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->last(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->next(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->prev(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->current(), LMDBAL::Unknown); - emptyCursor->close(); + LMDBAL::Cursor emptyCursor = emptyCache->createCursor(); + emptyCursor.open(); + EXPECT_THROW(emptyCursor.first(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.last(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.next(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.prev(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.current(), LMDBAL::Unknown); + emptyCursor.close(); - cursor->open(); - EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + cursor.open(); + EXPECT_THROW(cursor.current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc std::map::const_reverse_iterator breference = data.rbegin(); - std::pair element(cursor->prev()); + std::pair element(cursor.prev()); EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again! EXPECT_EQ(element.second, breference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, breference->first); EXPECT_EQ(element.second, breference->second); - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); - cursor->close(); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); + cursor.close(); - cursor->open(); - element = cursor->next(); + cursor.open(); + element = cursor.next(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); } diff --git a/test/duplicates.cpp b/test/duplicates.cpp index db16dce..4fbe12a 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -337,15 +337,15 @@ TEST_F(DuplicatesTest, GettingAllRecords) { std::map m1; std::set k1; - LMDBAL::Cursor* c1 = tu1->createCursor(); + LMDBAL::Cursor c1 = tu1->createCursor(); tu1->readAll(m1, txn); - c1->open(txn); + c1.open(txn); cycle = false; iterations = 0; do { try { - std::pair pair = c1->next(); + std::pair pair = c1.next(); cycle = true; std::pair::const_iterator, bool> probe = k1.insert(pair.first); if (probe.second) { @@ -359,25 +359,25 @@ TEST_F(DuplicatesTest, GettingAllRecords) { cycle = false; } } while (cycle); - tu1->destroyCursor(c1); EXPECT_EQ(iterations, tu1->count(txn)); EXPECT_EQ(k1.size(), m1.size()); EXPECT_NE(iterations, 0); EXPECT_NE(k1.size(), 0); + c1.drop(); std::map m2; std::set k2; - LMDBAL::Cursor* c2 = tu2->createCursor(); + LMDBAL::Cursor c2 = tu2->createCursor(); tu2->readAll(m2, txn); - c2->open(txn); + c2.open(txn); cycle = false; iterations = 0; do { try { - std::pair pair = c2->next(); + std::pair pair = c2.next(); cycle = true; std::pair::const_iterator, bool> probe = k2.insert(pair.first); if (probe.second) { @@ -391,25 +391,25 @@ TEST_F(DuplicatesTest, GettingAllRecords) { cycle = false; } } while (cycle); - tu2->destroyCursor(c2); EXPECT_EQ(iterations, tu2->count(txn)); EXPECT_EQ(k2.size(), m2.size()); EXPECT_NE(iterations, 0); EXPECT_NE(k2.size(), 0); + c2.drop(); std::map m3; std::set k3; - LMDBAL::Cursor* c3 = tu3->createCursor(); + LMDBAL::Cursor c3 = tu3->createCursor(); tu3->readAll(m3, txn); - c3->open(txn); + c3.open(txn); cycle = false; iterations = 0; do { try { - std::pair pair = c3->next(); + std::pair pair = c3.next(); cycle = true; std::pair::const_iterator, bool> probe = k3.insert(pair.first); if (probe.second) { @@ -423,25 +423,25 @@ TEST_F(DuplicatesTest, GettingAllRecords) { cycle = false; } } while (cycle); - tu3->destroyCursor(c3); EXPECT_EQ(iterations, tu3->count(txn)); EXPECT_EQ(k3.size(), m3.size()); EXPECT_NE(iterations, 0); EXPECT_NE(k3.size(), 0); + c3.drop(); std::map m4; std::set k4; - LMDBAL::Cursor* c4 = tu4->createCursor(); + LMDBAL::Cursor c4 = tu4->createCursor(); tu4->readAll(m4, txn); - c4->open(txn); + c4.open(txn); cycle = false; iterations = 0; do { try { - std::pair pair = c4->next(); + std::pair pair = c4.next(); cycle = true; std::pair::const_iterator, bool> probe = k4.insert(pair.first); if (probe.second) { @@ -455,7 +455,7 @@ TEST_F(DuplicatesTest, GettingAllRecords) { cycle = false; } } while (cycle); - tu4->destroyCursor(c4); + c4.drop(); EXPECT_EQ(iterations, tu4->count(txn)); EXPECT_EQ(k4.size(), m4.size()); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index a75dba0..b8de67c 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -28,13 +28,10 @@ protected: db->removeDirectory(); delete db; db = nullptr; - cursor = nullptr; - emptyCursor = nullptr; } static LMDBAL::Base* db; - static LMDBAL::Cursor* cursor; - static LMDBAL::Cursor* emptyCursor; + static LMDBAL::Cursor cursor; static LMDBAL::Transaction transaction; LMDBAL::Storage* table; @@ -42,8 +39,7 @@ protected: }; LMDBAL::Base* StorageCursorTest::db = nullptr; -LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; -LMDBAL::Cursor* StorageCursorTest::emptyCursor = nullptr; +LMDBAL::Cursor StorageCursorTest::cursor; LMDBAL::Transaction StorageCursorTest::transaction = LMDBAL::Transaction(); static const std::map data({ @@ -66,19 +62,18 @@ TEST_F(StorageCursorTest, PopulatingTheTable) { TEST_F(StorageCursorTest, Creation) { cursor = table->createCursor(); - emptyCursor = emptyTable->createCursor(); - EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); - cursor->open(); + cursor.open(); } TEST_F(StorageCursorTest, FirstPrivate) { - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -90,14 +85,14 @@ TEST_F(StorageCursorTest, NextPrivate) { reference++; for (; reference != data.end(); ++reference) { - std::pair element = cursor->next(); + std::pair element = cursor.next(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); } - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); - std::pair element = cursor->first(); + std::pair element = cursor.first(); reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -105,7 +100,7 @@ TEST_F(StorageCursorTest, NextPrivate) { } TEST_F(StorageCursorTest, LastPrivate) { - std::pair element = cursor->last(); + std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -117,14 +112,14 @@ TEST_F(StorageCursorTest, PrevPrivate) { reference++; for (; reference != data.rend(); ++reference) { - std::pair element = cursor->prev(); + std::pair element = cursor.prev(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); } - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); - std::pair element = cursor->last(); + std::pair element = cursor.last(); reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -132,28 +127,28 @@ TEST_F(StorageCursorTest, PrevPrivate) { } TEST_F(StorageCursorTest, CurrentPrivate) { - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - element = cursor->current(); + cursor.next(); + element = cursor.current(); ++reference; EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - cursor->next(); - cursor->prev(); - element = cursor->current(); + cursor.next(); + cursor.next(); + cursor.prev(); + element = cursor.current(); ++reference; ++reference; --reference; @@ -163,16 +158,15 @@ TEST_F(StorageCursorTest, CurrentPrivate) { } TEST_F(StorageCursorTest, Destruction) { - cursor->close(); + cursor.close(); - EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); - EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); - EXPECT_THROW(emptyTable->destroyCursor(cursor), LMDBAL::Unknown); - table->destroyCursor(cursor); + cursor = LMDBAL::Cursor(); cursor = table->createCursor(); } @@ -180,8 +174,8 @@ TEST_F(StorageCursorTest, Destruction) { TEST_F(StorageCursorTest, FirstPublic) { transaction = db->beginReadOnlyTransaction(); - cursor->open(transaction); - std::pair element = cursor->first(); + cursor.open(transaction); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -193,14 +187,14 @@ TEST_F(StorageCursorTest, NextPublic) { reference++; for (; reference != data.end(); ++reference) { - std::pair element = cursor->next(); + std::pair element = cursor.next(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); } - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); - std::pair element = cursor->first(); + std::pair element = cursor.first(); reference = data.begin(); EXPECT_EQ(element.first, reference->first); @@ -208,7 +202,7 @@ TEST_F(StorageCursorTest, NextPublic) { } TEST_F(StorageCursorTest, LastPublic) { - std::pair element = cursor->last(); + std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -220,14 +214,14 @@ TEST_F(StorageCursorTest, PrevPublic) { reference++; for (; reference != data.rend(); ++reference) { - std::pair element = cursor->prev(); + std::pair element = cursor.prev(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); } - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); - std::pair element = cursor->last(); + std::pair element = cursor.last(); reference = data.rbegin(); EXPECT_EQ(element.first, reference->first); @@ -235,28 +229,28 @@ TEST_F(StorageCursorTest, PrevPublic) { } TEST_F(StorageCursorTest, CurrentPublic) { - std::pair element = cursor->first(); + std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - element = cursor->current(); + cursor.next(); + element = cursor.current(); ++reference; EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - cursor->next(); - cursor->next(); - cursor->prev(); - element = cursor->current(); + cursor.next(); + cursor.next(); + cursor.prev(); + element = cursor.current(); ++reference; ++reference; --reference; @@ -267,37 +261,38 @@ TEST_F(StorageCursorTest, CurrentPublic) { TEST_F(StorageCursorTest, CornerCases) { transaction.terminate(); - EXPECT_THROW(cursor->current(), LMDBAL::Unknown); - cursor->close(); + EXPECT_THROW(cursor.current(), LMDBAL::Unknown); + cursor.close(); - emptyCursor->open(); - EXPECT_THROW(emptyCursor->first(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->last(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->next(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->prev(), LMDBAL::NotFound); - EXPECT_THROW(emptyCursor->current(), LMDBAL::Unknown); - emptyCursor->close(); + LMDBAL::Cursor emptyCursor = emptyTable->createCursor(); + emptyCursor.open(); + EXPECT_THROW(emptyCursor.first(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.last(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.next(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.prev(), LMDBAL::NotFound); + EXPECT_THROW(emptyCursor.current(), LMDBAL::Unknown); + emptyCursor.close(); - cursor->open(); - EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + cursor.open(); + EXPECT_THROW(cursor.current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc std::map::const_reverse_iterator breference = data.rbegin(); - std::pair element(cursor->prev()); + std::pair element(cursor.prev()); EXPECT_EQ(element.first, breference->first); //nice thing to write in the doc, again! EXPECT_EQ(element.second, breference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, breference->first); EXPECT_EQ(element.second, breference->second); - EXPECT_THROW(cursor->next(), LMDBAL::NotFound); - cursor->close(); + EXPECT_THROW(cursor.next(), LMDBAL::NotFound); + cursor.close(); - cursor->open(); - element = cursor->next(); + cursor.open(); + element = cursor.next(); std::map::const_iterator reference = data.begin(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - element = cursor->current(); + element = cursor.current(); EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); - EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); } From 3307860ca625dc4f0f8bfed72e66d559bb5ca7a1 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 1 Nov 2023 19:45:42 -0300 Subject: [PATCH 079/125] tests, set method, tests for set method --- CHANGELOG.md | 6 ++- src/base.cpp | 5 +- src/cursor.h | 2 + src/cursor.hpp | 55 +++++++++++++++---- src/storage.hpp | 26 +++++++++ test/cachecursor.cpp | 118 ++++++++++++++++++++++++++++++++++++++++ test/storagecursor.cpp | 119 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 316 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d596471..7862d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog -## LMDBAL 0.5.2 (October 21, 2023) +## LMDBAL 0.5.2 (November 01, 2023) ### Improvements - RAII cursors +- operation set for cursors + +### Bug fixes +- error beginning transaction is now correctly handled and doesn't segfault ## LMDBAL 0.5.1 (October 21, 2023) ### Improvements diff --git a/src/base.cpp b/src/base.cpp index 81f9bdb..149343b 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -402,10 +402,9 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::string& storageName) const { MDB_txn* txn; int rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - if (rc != MDB_SUCCESS) { - mdb_txn_abort(txn); + if (rc != MDB_SUCCESS) throw Unknown(name, mdb_strerror(rc), storageName); - } + return txn; } diff --git a/src/cursor.h b/src/cursor.h index 2a5114b..c3ff362 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -73,6 +73,7 @@ public: private: void dropped(); + void freed(); void terminated(); void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; @@ -91,6 +92,7 @@ private: inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/ inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/ inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/ + inline static const std::string setMethodName = "set"; /**<\brief member function name, just for exceptions*/ inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/ inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/ diff --git a/src/cursor.hpp b/src/cursor.hpp index 027c6fd..bfcc44a 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -81,13 +81,11 @@ LMDBAL::Cursor::Cursor(Cursor&& other): state(other.state), id(other.id) { + other.terminated(); if (id != 0) storage->cursors[id] = this; - other.cursor = nullptr; - other.storage = nullptr; - other.id = 0; - other.state = closed; + other.freed(); } /** @@ -109,9 +107,7 @@ LMDBAL::Cursor& LMDBAL::Cursor::operator = (Cursor&& other) { id = other.id; if (id != 0) { - other.cursor = nullptr; - other.storage = nullptr; - other.id = 0; + other.freed(); other.state = closed; storage->cursors[id] = this; @@ -146,20 +142,29 @@ void LMDBAL::Cursor::drop () { if (id != 0) storage->cursors.erase(id); - cursor = nullptr; - storage = nullptr; - id = 0; + freed(); } /** * \brief A private method that turns cursor into an empty one * - * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors. * Those cursors will become empty, and can't be used anymore */ template void LMDBAL::Cursor::dropped () { terminated(); + freed(); +} + +/** + * \brief A private method that turns cursor into an empty one (submethod) + * + * This function is called from LMDBAL::Storage, when the cursor is getting destoryed. + * Those cursors will become empty, and can't be used anymore + */ +template +void LMDBAL::Cursor::freed () { cursor = nullptr; storage = nullptr; id = 0; @@ -585,6 +590,34 @@ std::pair LMDBAL::Cursor::current () const { return result; } +/** + * \brief Sets cursors to the defined position + * + * If exactly the same element wasn't found it sets the cursor to the first + * element greater then the key and returns false + * + * \param[in] key a key of the element you would like the cursor to be + * \returns true if the exact value was found, false otherwise + * + * \exception LMDBAL::CursorNotReady thrown if you try to call this method on a closed cursor + * \exception LMDBAL::Unknown thrown if there was some unexpected problem + */ +template +bool LMDBAL::Cursor::set (const K& key) { + if (state == closed) + storage->throwCursorNotReady(setMethodName); + + MDB_val mdbKey = storage->keySerializer.setData(key); + int result = mdb_cursor_get(cursor, &mdbKey, nullptr, MDB_SET); + if (result == MDB_SUCCESS) + return true; + else if (result == MDB_NOTFOUND) + return false; + + storage->throwUnknown(result); + return false; //unreachable, just to suppress the warning +} + /** * \brief a private mothod that actually doing all the reading * diff --git a/src/storage.hpp b/src/storage.hpp index f533217..1bab63b 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -1022,6 +1022,9 @@ void LMDBAL::Storage::close() { /** * \brief Creates cursor * + * This is a legitimate way to aquire cursor to a storage. + * Aquired cursor is RAII safe, automatic destructor will free everything it occupied. + * * \returns LMDBAL::Cursor for this storage and returs you a pointer to a created cursor */ template @@ -1029,6 +1032,29 @@ LMDBAL::Cursor LMDBAL::Storage::createCursor() { return Cursor(this); } +/** + * \brief Frees cursor + * + * This is a legitimate way to free cursor. + * You don't actually need to do it manually, + * you can just reassign cursor, let it be destroyed by leaving the scope + * or call LMDBAL::Cursor::drop, but you may if you wish. + * + * \param[in] cursor cursor you wish to destroy + * + * \exception LMDBAL::Unknown thrown if you try to destroy a cursor this storage didn't create + */ +template +void LMDBAL::Storage::destroyCursor(LMDBAL::Cursor& cursor) { + typename std::map*>::iterator itr = cursors.find(cursor.id); + if (itr == cursors.end()) + throwUnknown("An attempt to destroy a cursor the storage doesn't own"); + + cursor.close(); + cursors.erase(itr); + cursor.freed(); +} + /** * \brief Reads current storage flags it was opened with * diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index c988bc1..7d9d440 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -23,7 +23,12 @@ protected: } } + int getCacheCursorsSize() const { + return cache->cursors.size(); + } + static void TearDownTestSuite() { + cursor.drop(); transaction.terminate(); db->close(); db->removeDirectory(); @@ -62,7 +67,9 @@ TEST_F(CacheCursorTest, PopulatingTheTable) { } TEST_F(CacheCursorTest, Creation) { + EXPECT_EQ(getCacheCursorsSize(), 0); cursor = cache->createCursor(); + EXPECT_EQ(getCacheCursorsSize(), 1); EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); @@ -74,6 +81,7 @@ TEST_F(CacheCursorTest, Creation) { } TEST_F(CacheCursorTest, FirstPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); EXPECT_EQ(cache->count(), data.size()); std::pair element = cursor.first(); @@ -85,6 +93,7 @@ TEST_F(CacheCursorTest, FirstPrivate) { } TEST_F(CacheCursorTest, NextPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::map::const_iterator reference = data.begin(); reference++; @@ -107,6 +116,7 @@ TEST_F(CacheCursorTest, NextPrivate) { } TEST_F(CacheCursorTest, LastPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); EXPECT_EQ(cache->count(), data.size()); std::pair element = cursor.last(); @@ -118,6 +128,7 @@ TEST_F(CacheCursorTest, LastPrivate) { } TEST_F(CacheCursorTest, PrevPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::map::const_reverse_iterator reference = data.rbegin(); reference++; @@ -140,6 +151,7 @@ TEST_F(CacheCursorTest, PrevPrivate) { } TEST_F(CacheCursorTest, CurrentPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); @@ -172,8 +184,42 @@ TEST_F(CacheCursorTest, CurrentPrivate) { EXPECT_EQ(cache->count(), data.size()); } +TEST_F(CacheCursorTest, SettingPrivate) { + EXPECT_EQ(getCacheCursorsSize(), 1); + + EXPECT_FALSE(cursor.set(6684)); + EXPECT_EQ(cursor.current().second, "tanned inmate"); + + std::map::const_iterator reference = data.begin(); + std::advance(reference, 5); + EXPECT_TRUE(cursor.set(reference->first)); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); +} + TEST_F(CacheCursorTest, Destruction) { + EXPECT_EQ(getCacheCursorsSize(), 1); cursor.close(); + EXPECT_EQ(getCacheCursorsSize(), 1); EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); @@ -182,11 +228,14 @@ TEST_F(CacheCursorTest, Destruction) { EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); cursor = LMDBAL::Cursor(); + EXPECT_EQ(getCacheCursorsSize(), 0); cursor = cache->createCursor(); + EXPECT_EQ(getCacheCursorsSize(), 1); } TEST_F(CacheCursorTest, FirstPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); transaction = db->beginTransaction(); cursor.open(transaction); @@ -199,6 +248,7 @@ TEST_F(CacheCursorTest, FirstPublic) { } TEST_F(CacheCursorTest, NextPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::map::const_iterator reference = data.begin(); reference++; @@ -220,6 +270,7 @@ TEST_F(CacheCursorTest, NextPublic) { } TEST_F(CacheCursorTest, LastPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); @@ -229,6 +280,7 @@ TEST_F(CacheCursorTest, LastPublic) { } TEST_F(CacheCursorTest, PrevPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::map::const_reverse_iterator reference = data.rbegin(); reference++; @@ -250,6 +302,7 @@ TEST_F(CacheCursorTest, PrevPublic) { } TEST_F(CacheCursorTest, CurrentPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); @@ -282,6 +335,71 @@ TEST_F(CacheCursorTest, CurrentPublic) { EXPECT_EQ(cache->count(), data.size()); } +TEST_F(CacheCursorTest, SettingPublic) { + EXPECT_EQ(getCacheCursorsSize(), 1); + + EXPECT_FALSE(cursor.set(557)); + EXPECT_EQ(cursor.current().second, "resilent pick forefront"); + + std::map::const_iterator reference = data.begin(); + std::advance(reference, 3); + EXPECT_TRUE(cursor.set(reference->first)); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); +} + +TEST_F(CacheCursorTest, CursorRAIIBehaviour) { + int initialiCursorsAmount = getCacheCursorsSize(); + { + LMDBAL::Cursor cur = cache->createCursor(); + EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize()); + cur.open(transaction); + EXPECT_NO_THROW(cur.first()); + } + + EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize()); + LMDBAL::Cursor cur; + EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize()); + EXPECT_EQ(cur.empty(), true); + cur = cache->createCursor(); + EXPECT_EQ(cur.empty(), false); + EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize()); + EXPECT_THROW(emptyCache->destroyCursor(cur), LMDBAL::Unknown); + cache->destroyCursor(cur); + EXPECT_EQ(cur.empty(), true); + EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize()); + + cur = cache->createCursor(); + EXPECT_EQ(initialiCursorsAmount + 1, getCacheCursorsSize()); + EXPECT_EQ(cur.empty(), false); + + cur.drop(); + EXPECT_EQ(cur.empty(), true); + EXPECT_EQ(initialiCursorsAmount, getCacheCursorsSize()); + + EXPECT_NO_THROW(cur.drop()); + EXPECT_THROW(cache->destroyCursor(cur), LMDBAL::Unknown); +} + TEST_F(CacheCursorTest, CornerCases) { transaction.terminate(); EXPECT_THROW(cursor.current(), LMDBAL::Unknown); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index b8de67c..dde8d82 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -22,7 +22,12 @@ protected: } } + int getTableCursorsSize() const { + return table->cursors.size(); + } + static void TearDownTestSuite() { + cursor.drop(); transaction.terminate(); db->close(); db->removeDirectory(); @@ -61,7 +66,9 @@ TEST_F(StorageCursorTest, PopulatingTheTable) { } TEST_F(StorageCursorTest, Creation) { + EXPECT_EQ(getTableCursorsSize(), 0); cursor = table->createCursor(); + EXPECT_EQ(getTableCursorsSize(), 1); EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); @@ -73,6 +80,7 @@ TEST_F(StorageCursorTest, Creation) { } TEST_F(StorageCursorTest, FirstPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); @@ -81,6 +89,7 @@ TEST_F(StorageCursorTest, FirstPrivate) { } TEST_F(StorageCursorTest, NextPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); std::map::const_iterator reference = data.begin(); reference++; @@ -100,6 +109,7 @@ TEST_F(StorageCursorTest, NextPrivate) { } TEST_F(StorageCursorTest, LastPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); @@ -108,6 +118,7 @@ TEST_F(StorageCursorTest, LastPrivate) { } TEST_F(StorageCursorTest, PrevPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); std::map::const_reverse_iterator reference = data.rbegin(); reference++; @@ -127,6 +138,7 @@ TEST_F(StorageCursorTest, PrevPrivate) { } TEST_F(StorageCursorTest, CurrentPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); @@ -157,8 +169,42 @@ TEST_F(StorageCursorTest, CurrentPrivate) { EXPECT_EQ(element.second, reference->second); } +TEST_F(StorageCursorTest, SettingPrivate) { + EXPECT_EQ(getTableCursorsSize(), 1); + + EXPECT_FALSE(cursor.set(6684)); + EXPECT_EQ(cursor.current().second, "tanned inmate"); + + std::map::const_iterator reference = data.begin(); + std::advance(reference, 5); + EXPECT_TRUE(cursor.set(reference->first)); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); +} + TEST_F(StorageCursorTest, Destruction) { + EXPECT_EQ(getTableCursorsSize(), 1); cursor.close(); + EXPECT_EQ(getTableCursorsSize(), 1); EXPECT_THROW(cursor.first(), LMDBAL::CursorNotReady); EXPECT_THROW(cursor.last(), LMDBAL::CursorNotReady); @@ -167,11 +213,14 @@ TEST_F(StorageCursorTest, Destruction) { EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); cursor = LMDBAL::Cursor(); + EXPECT_EQ(getTableCursorsSize(), 0); cursor = table->createCursor(); + EXPECT_EQ(getTableCursorsSize(), 1); } TEST_F(StorageCursorTest, FirstPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); transaction = db->beginReadOnlyTransaction(); cursor.open(transaction); @@ -183,6 +232,7 @@ TEST_F(StorageCursorTest, FirstPublic) { } TEST_F(StorageCursorTest, NextPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); std::map::const_iterator reference = data.begin(); reference++; @@ -202,6 +252,7 @@ TEST_F(StorageCursorTest, NextPublic) { } TEST_F(StorageCursorTest, LastPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); std::pair element = cursor.last(); std::map::const_reverse_iterator reference = data.rbegin(); @@ -210,6 +261,7 @@ TEST_F(StorageCursorTest, LastPublic) { } TEST_F(StorageCursorTest, PrevPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); std::map::const_reverse_iterator reference = data.rbegin(); reference++; @@ -229,6 +281,7 @@ TEST_F(StorageCursorTest, PrevPublic) { } TEST_F(StorageCursorTest, CurrentPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); std::pair element = cursor.first(); std::map::const_iterator reference = data.begin(); @@ -259,7 +312,73 @@ TEST_F(StorageCursorTest, CurrentPublic) { EXPECT_EQ(element.second, reference->second); } +TEST_F(StorageCursorTest, SettingPublic) { + EXPECT_EQ(getTableCursorsSize(), 1); + + EXPECT_FALSE(cursor.set(557)); + EXPECT_EQ(cursor.current().second, "resilent pick forefront"); + + std::map::const_iterator reference = data.begin(); + std::advance(reference, 3); + EXPECT_TRUE(cursor.set(reference->first)); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + ++reference; + cursor.next(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); + + --reference; + cursor.prev(); + EXPECT_EQ(cursor.current().second, reference->second); +} + +TEST_F(StorageCursorTest, CursorRAIIBehaviour) { + int initialiCursorsAmount = getTableCursorsSize(); + { + LMDBAL::Cursor cur = table->createCursor(); + EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize()); + cur.open(transaction); + EXPECT_NO_THROW(cur.first()); + } + + EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize()); + LMDBAL::Cursor cur; + EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize()); + EXPECT_EQ(cur.empty(), true); + cur = table->createCursor(); + EXPECT_EQ(cur.empty(), false); + EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize()); + EXPECT_THROW(emptyTable->destroyCursor(cur), LMDBAL::Unknown); + table->destroyCursor(cur); + EXPECT_EQ(cur.empty(), true); + EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize()); + + cur = table->createCursor(); + EXPECT_EQ(initialiCursorsAmount + 1, getTableCursorsSize()); + EXPECT_EQ(cur.empty(), false); + + cur.drop(); + EXPECT_EQ(cur.empty(), true); + EXPECT_EQ(initialiCursorsAmount, getTableCursorsSize()); + + EXPECT_NO_THROW(cur.drop()); + EXPECT_THROW(table->destroyCursor(cur), LMDBAL::Unknown); +} + TEST_F(StorageCursorTest, CornerCases) { + EXPECT_EQ(getTableCursorsSize(), 1); transaction.terminate(); EXPECT_THROW(cursor.current(), LMDBAL::Unknown); cursor.close(); From 77ba8f9e7be50b5b028f58cd8dc4809076e15d6d Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 10 Nov 2023 19:27:04 -0300 Subject: [PATCH 080/125] a bug in readAll of cache --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- packaging/Archlinux/PKGBUILD | 2 +- src/cache.hpp | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7862d0c..1480e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# LMDBAL 0.5.3 (UNRELEASED) +### Bug fixes +- transaction error in LMDBAL::Cache::readAll + ## LMDBAL 0.5.2 (November 01, 2023) ### Improvements - RAII cursors diff --git a/CMakeLists.txt b/CMakeLists.txt index 3231cf3..21237e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.5.2 + VERSION 0.5.3 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 0232f4e..b8542ce 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.5.2 +pkgver=0.5.3 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') diff --git a/src/cache.hpp b/src/cache.hpp index e9ae0aa..e3c6d60 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -549,7 +549,7 @@ void LMDBAL::Cache::readAll(std::map& out, TransactionID txn) const } else { if (mode != Mode::full) { //there is a room for optimization mode = Mode::full; //I can read and deserialize only those values - Storage::readAll(out); //that are missing in the cache + Storage::readAll(out, txn); //that are missing in the cache *cache = out; abscent->clear(); sizeDifference = 0; From 6b475f615ea4936419bb1e001dd82a56323d61b0 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 14 Nov 2023 20:15:16 -0300 Subject: [PATCH 081/125] lmdb calls are now compiled into .so --- CHANGELOG.md | 5 +++- src/cursor.hpp | 24 +++++++++--------- src/storage.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++ src/storage.h | 20 +++++++++++++++ src/storage.hpp | 42 +++++++++++++++---------------- test/CMakeLists.txt | 1 - 6 files changed, 118 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1480e8e..a70560d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog -# LMDBAL 0.5.3 (UNRELEASED) +# LMDBAL 0.5.3 (November 14, 2023) +### Improvements +- Now you don't need to link agains lmdb, just linking against LMDBAL is enough + ### Bug fixes - transaction error in LMDBAL::Cache::readAll diff --git a/src/cursor.hpp b/src/cursor.hpp index bfcc44a..519bd41 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -210,7 +210,7 @@ void LMDBAL::Cursor::open () { switch (state) { case closed: { TransactionID txn = storage->beginReadOnlyTransaction(); - int result = mdb_cursor_open(txn, storage->dbi, &cursor); + int result = storage->_mdbCursorOpen(txn, &cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result, txn); @@ -247,7 +247,7 @@ void LMDBAL::Cursor::open (const Transaction& transaction) { TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); switch (state) { case closed: { - int result = mdb_cursor_open(txn, storage->dbi, &cursor); + int result = storage->_mdbCursorOpen(txn, &cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result); @@ -283,14 +283,14 @@ void LMDBAL::Cursor::renew () { storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { - TransactionID txn = mdb_cursor_txn(cursor); + TransactionID txn = storage->_mdbCursorTxn(cursor); storage->abortTransaction(txn); storage->transactionAborted(txn); [[fallthrough]]; } case openedPublic: { TransactionID txn = storage->beginReadOnlyTransaction(); - int result = mdb_cursor_renew(txn, cursor); + int result = storage->_mdbCursorRenew(txn, cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result, txn); @@ -331,13 +331,13 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) { TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); switch (state) { case openedPrivate: { - TransactionID txn = mdb_cursor_txn(cursor); + TransactionID txn = storage->_mdbCursorTxn(cursor); storage->abortTransaction(txn); storage->transactionAborted(txn); [[fallthrough]]; } case openedPublic: { - int result = mdb_cursor_renew(txn, cursor); + int result = storage->_mdbCursorRenew(txn, cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result); @@ -362,13 +362,13 @@ template void LMDBAL::Cursor::close () { switch (state) { case openedPublic: { - mdb_cursor_close(cursor); + storage->_mdbCursorClose(cursor); state = closed; } break; case openedPrivate: { - TransactionID txn = mdb_cursor_txn(cursor); - mdb_cursor_close(cursor); + TransactionID txn = storage->_mdbCursorTxn(cursor); + storage->_mdbCursorClose(cursor); storage->abortTransaction(txn); storage->transactionAborted(txn); @@ -608,7 +608,7 @@ bool LMDBAL::Cursor::set (const K& key) { storage->throwCursorNotReady(setMethodName); MDB_val mdbKey = storage->keySerializer.setData(key); - int result = mdb_cursor_get(cursor, &mdbKey, nullptr, MDB_SET); + int result = storage->_mdbCursorSet(cursor, mdbKey); if (result == MDB_SUCCESS) return true; else if (result == MDB_NOTFOUND) @@ -645,7 +645,7 @@ void LMDBAL::Cursor::operateCursorRead( storage->throwCursorNotReady(methodName); MDB_val mdbKey, mdbValue; - int result = mdb_cursor_get(cursor, &mdbKey, &mdbValue, operation); + int result = storage->_mdbCursorGet(cursor, mdbKey, mdbValue, operation); if (result != MDB_SUCCESS) storage->throwNotFoundOrUnknown(result, operationName); @@ -655,7 +655,7 @@ void LMDBAL::Cursor::operateCursorRead( if (state == openedPrivate) storage->discoveredRecord(key, value); else - storage->discoveredRecord(key, value, mdb_cursor_txn(cursor)); + storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor)); } #endif //LMDBAL_CURSOR_HPP diff --git a/src/storage.cpp b/src/storage.cpp index f1258fa..cea474d 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -446,3 +446,64 @@ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { * after the transaction is commited. Used just for optimisations. */ void LMDBAL::iStorage::handleDrop() {} + +int LMDBAL::iStorage::_mdbOpen(MDB_txn *txn, unsigned int flags) { + return mdb_dbi_open(txn, name.c_str(), flags, &dbi); +} + +int LMDBAL::iStorage::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) { + return mdb_put(txn, dbi, &key, &data, flags); +} + +int LMDBAL::iStorage::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const { + return mdb_get(txn, dbi, &key, &data); +} + +int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key) { + return mdb_del(txn, dbi, &key, NULL); +} + +int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) { + return mdb_del(txn, dbi, &key, &data); +} + +int LMDBAL::iStorage::_mdbStat(MDB_txn* txn, MDB_stat& stat) const { + return mdb_stat(txn, dbi, &stat); +} + +int LMDBAL::iStorage::_mdbFlags(MDB_txn* txn, uint32_t& flags) const { + return mdb_dbi_flags(txn, dbi, &flags); +} + + +int LMDBAL::iStorage::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { + return mdb_cursor_open(txn, dbi, cursor); +} + +void LMDBAL::iStorage::_mdbCursorClose(MDB_cursor *cursor) const { + mdb_cursor_close(cursor); +} + +int LMDBAL::iStorage::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const { + return mdb_cursor_get(cursor, &key, &data, operation); +} + +int LMDBAL::iStorage::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const { + return mdb_cursor_get(cursor, &key, NULL, MDB_SET); +} + +int LMDBAL::iStorage::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) { + return mdb_cursor_del(cursor, flags); +} + +int LMDBAL::iStorage::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) { + return mdb_cursor_put(cursor, &key, &data, flags); +} + +int LMDBAL::iStorage::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const { + return mdb_cursor_renew(txn, cursor); +} + +MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const { + return mdb_cursor_txn(cursor); +} diff --git a/src/storage.h b/src/storage.h index de38568..82735a7 100644 --- a/src/storage.h +++ b/src/storage.h @@ -77,6 +77,26 @@ protected: virtual int drop(TransactionID transaction); virtual SizeType count(TransactionID txn) const; + int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); + + int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); + int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const; + int _mdbDel(MDB_txn* txn, MDB_val& key); + int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data); + + int _mdbStat(MDB_txn* txn, MDB_stat& stat) const; + int _mdbFlags(MDB_txn* txn, uint32_t& flags) const; + + int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const; + void _mdbCursorClose(MDB_cursor* cursor) const; + int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const; + int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const; + int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0); + int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0); + + int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const; + MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const; + public: virtual void drop(); virtual int drop(const WriteTransaction& txn); diff --git a/src/storage.hpp b/src/storage.hpp index 1bab63b..91756b3 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -115,7 +115,7 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI else flags |= MDB_NOOVERWRITE; - int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, flags); + int rc = _mdbPut(txn, lmdbKey, lmdbData, flags); if (rc != MDB_SUCCESS) throwDuplicateOrUnknown(rc, toString(key)); } @@ -209,7 +209,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; - int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + int rc = _mdbGet(txn, lmdbKey, lmdbData); switch (rc) { case MDB_SUCCESS: added = false; @@ -223,7 +223,7 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio } lmdbData = valueSerializer.setData(value); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + rc = _mdbPut(txn, lmdbKey, lmdbData); if (rc != MDB_SUCCESS) throwUnknown(rc); } @@ -314,13 +314,13 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { template void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { MDB_cursor* cursor; - int rc = mdb_cursor_open(txn, dbi, &cursor); + int rc = _mdbCursorOpen(txn, &cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; - rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET); + rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_SET); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); @@ -330,21 +330,21 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value, Transacti if (sameSize) { //can compare only if they are the same size firstDifferentByte = memcmp(lmdbData.mv_data, lmdbNewData.mv_data, lmdbData.mv_size); if (firstDifferentByte == 0) { //old and new is the same, nothing to do - mdb_cursor_close(cursor); + _mdbCursorClose(cursor); return; } } unsigned int flags = MDB_CURRENT; if (duplicates && (!sameSize || firstDifferentByte < 0)) { //if new value is greater than the old one - rc = mdb_cursor_del(cursor, 0); //we need to initiate duplicates sort, for it to be in the correct place + rc = _mdbCursorDel(cursor); //we need to initiate duplicates sort, for it to be in the correct place flags = MDB_NODUPDATA; } if (rc == MDB_SUCCESS) - rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbNewData, flags); + rc = _mdbCursorPut(cursor, lmdbKey, lmdbNewData, flags); - mdb_cursor_close(cursor); + _mdbCursorClose(cursor); if (rc != MDB_SUCCESS) throwDuplicateOrUnknown(rc, toString(key)); } @@ -517,7 +517,7 @@ void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; - int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + int rc = _mdbGet(txn, lmdbKey, lmdbData); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); @@ -594,7 +594,7 @@ bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { MDB_val lmdbKey = keySerializer.setData(key); MDB_val lmdbData; - int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData); + int rc = _mdbGet(txn, lmdbKey, lmdbData); if (rc == MDB_SUCCESS) return true; @@ -729,11 +729,11 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c MDB_cursor* cursor; MDB_val lmdbKey, lmdbData; - int rc = mdb_cursor_open(txn, dbi, &cursor); + int rc = _mdbCursorOpen(txn, &cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); - rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_FIRST); while (rc == MDB_SUCCESS) { K key; keySerializer.deserialize(lmdbKey, key); @@ -741,9 +741,9 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c if (probe.second) //I do this to avoid overwrites in case duplicates are enabled valueSerializer.deserialize(lmdbData, probe.first->second); - rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); + rc = _mdbCursorGet(cursor, lmdbKey, lmdbData, MDB_NEXT); } - mdb_cursor_close(cursor); + _mdbCursorClose(cursor); if (rc != MDB_NOTFOUND) throwUnknown(rc); } @@ -817,7 +817,7 @@ void LMDBAL::Storage::replaceAll(const std::map& data, TransactionID lmdbKey = keySerializer.setData(pair.first); lmdbData = valueSerializer.setData(pair.second); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here? + rc = _mdbPut(txn, lmdbKey, lmdbData, MDB_NOOVERWRITE); //TODO may be appending with cursor makes sence here? if (rc != MDB_SUCCESS) throwUnknown(rc); } @@ -892,7 +892,7 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti lmdbKey = keySerializer.setData(pair.first); lmdbData = valueSerializer.setData(pair.second); - rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); + rc = _mdbPut(txn, lmdbKey, lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); if (rc == MDB_KEYEXIST) throwDuplicate(toString(pair.first)); @@ -901,7 +901,7 @@ uint32_t LMDBAL::Storage::addRecords(const std::map& data, Transacti } MDB_stat stat; - rc = mdb_stat(txn, dbi, &stat); + rc = _mdbStat(txn, stat); if (rc != MDB_SUCCESS) throwUnknown(rc); @@ -971,7 +971,7 @@ void LMDBAL::Storage::removeRecord(const K& key) { template void LMDBAL::Storage::removeRecord(const K& key, TransactionID txn) { MDB_val lmdbKey = keySerializer.setData(key); - int rc = mdb_del(txn, dbi, &lmdbKey, NULL); + int rc = _mdbDel(txn, lmdbKey); if (rc != MDB_SUCCESS) throwNotFoundOrUnknown(rc, toString(key)); } @@ -1071,7 +1071,7 @@ uint32_t LMDBAL::Storage::flags() const { uint32_t result; TransactionID txn = beginReadOnlyTransaction(); - int res = mdb_dbi_flags(txn, dbi, &result); + int res = _mdbFlags(txn, result); abortTransaction(txn); if (res != MDB_SUCCESS) throwUnknown(res); @@ -1140,7 +1140,7 @@ inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) flags |= MDB_INTEGERDUP; } - return mdb_dbi_open(transaction, name.c_str(), flags, &dbi); + return _mdbOpen(transaction, flags); } /** diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3994aac..e9c7476 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,7 +23,6 @@ target_link_libraries( GTest::gtest_main ${PROJECT_NAME} Qt${QT_VERSION_MAJOR}::Core - lmdb ) include(GoogleTest) gtest_discover_tests(runUnitTests) From 79240aa5354e9fa9d09d3b12ea1077a81bb90809 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 31 Jan 2024 10:13:27 -0300 Subject: [PATCH 082/125] a little fix for building as subdirectory --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21237e7..6f5ea25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ include(CMakePackageConfigHelpers) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") if (NOT DEFINED QT_VERSION_MAJOR) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) From d62eddc47edbec9f8c071459e045578f61ab58df Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 1 Feb 2024 13:05:49 -0300 Subject: [PATCH 083/125] small build adjustments --- CMakeLists.txt | 17 +++++++++++++---- src/CMakeLists.txt | 1 - test/CMakeLists.txt | 1 - 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f5ea25..cec7b85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,12 +54,14 @@ target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version}) set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1) +set_property(TARGET ${PROJECT_NAME} PROPERTY EXPORT_NAME ${PROJECT_NAME}) set_property(TARGET ${PROJECT_NAME} PROPERTY INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION ) + add_subdirectory(src) if (BUILD_DOC) find_package(Doxygen) @@ -74,13 +76,20 @@ if (BUILD_TESTS) add_subdirectory(test) endif () -target_include_directories(${PROJECT_NAME} PUBLIC "$") +target_include_directories( + ${PROJECT_NAME} + PUBLIC + $ + $ +) target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PRIVATE - Qt${QT_VERSION_MAJOR}::Core - lmdb +target_link_libraries( + ${PROJECT_NAME} + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + lmdb ) configure_package_config_file( diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2da5cd8..956e6b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,6 @@ set(HEADERS ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) -target_include_directories(${PROJECT_NAME} PUBLIC "$") add_subdirectory(serializer) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e9c7476..93d54e3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,7 +14,6 @@ add_executable(runUnitTests target_compile_options(runUnitTests PRIVATE -fPIC -Wall -Wextra -O0) -target_include_directories(runUnitTests PRIVATE ${CMAKE_SOURCE_DIR}/src) target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) From 6dddd06f937cdbda35eee3268e79b0ff51347e50 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 4 Feb 2024 16:10:43 -0300 Subject: [PATCH 084/125] some cmake magick to make it work with installed paths --- CMakeLists.txt | 8 +++++--- packaging/Archlinux/PKGBUILD | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cec7b85..3f5c3c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.5.3 + VERSION 0.5.4 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) @@ -55,12 +55,14 @@ target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version}) set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1) set_property(TARGET ${PROJECT_NAME} PROPERTY EXPORT_NAME ${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY - INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1) +set_property(TARGET ${PROJECT_NAME} PROPERTY INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION ) +if (UNIX) + set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW}") +endif() add_subdirectory(src) if (BUILD_DOC) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index b8542ce..0f4b0da 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.5.3 +pkgver=0.5.4 pkgrel=1 pkgdesc="LMDB Abstraction Layer, qt5 version" arch=('i686' 'x86_64') From f638228d24ecbf22eb35daed96a0512803e00fbb Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 4 Feb 2024 19:49:06 -0300 Subject: [PATCH 085/125] removed lmdbal subdir from lib on installation --- CMakeLists.txt | 8 +++++--- src/serializer/CMakeLists.txt | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f5c3c5..ac8a188 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,9 @@ set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY if (UNIX) set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW}") + set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) + set_property(TARGET ${PROJECT_NAME} PROPERTY SKIP_BUILD_RPATH FALSE) + set_property(TARGET ${PROJECT_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) endif() add_subdirectory(src) @@ -83,13 +86,13 @@ target_include_directories( PUBLIC $ $ + $ ) target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) target_link_libraries( ${PROJECT_NAME} - PRIVATE Qt${QT_VERSION_MAJOR}::Core lmdb ) @@ -108,8 +111,7 @@ write_basic_package_version_file( install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_LOW}Targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW} ) diff --git a/src/serializer/CMakeLists.txt b/src/serializer/CMakeLists.txt index f4148e9..0c0a633 100644 --- a/src/serializer/CMakeLists.txt +++ b/src/serializer/CMakeLists.txt @@ -16,6 +16,4 @@ set(HEADERS serializer_qbytearray.hpp ) -target_include_directories(${PROJECT_NAME} PUBLIC "$") - install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) From dcf2d289dc284e68556a8dce984a08cccd311d55 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 3 Mar 2024 20:09:06 -0300 Subject: [PATCH 086/125] workflow destination transition, some examples on title docs page --- .gitea/workflows/main.yml | 2 +- doc/mainpage.dox | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index b6fab4e..b22a553 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -35,5 +35,5 @@ jobs: username: ${{ secrets.DEPLOY_USER_NAME }} key: ${{ secrets.DEPLOY_PRIVATE_KEY }} source: "build/doc/html/*,build/doc/xml/*,build/doc/man/*" - target: ${{ secrets.LMDBAL_DOCS_DEPLOY_PATH }} + target: "/srv/lmdbal/doc" strip_components: 2 diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 2f51d77..89411e3 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -4,6 +4,15 @@ * It repesents a collection of key-value storages that are going to be stored in a sigle data base directory. * To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine. * + * @code{.cpp} + * + * #include "base.h" + * + * //... + * + * LMDBAL::Base base("myDataBase"); + * @endcode + * * LMDBAL::Base creates or opens existing directory with the given name in the location acquired with * QStandardPaths::writableLocation(QStandardPaths::CacheLocation) * so, the file system destination of your data depends on the @@ -15,17 +24,55 @@ * std::map * to speed up the access. * + * @code{.cpp} + * + * #include "storage.h" + * #include "cache.h" + * + * //... + * + * LMDBAL::Storage storage = base.addStorage("storage"); + * LMDBAL::Cache cache = base.addCache("cache"); + * + * @endcode + * * You can obtain handlers by calling LMDBAL::Base::addStorage() or LMDBAL::Base::addCache(). * Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them. * You are not obliged to save those handlers, * you can obtain them at any time later using methods LMDBAL::Base::getStorage() or LMDBAL::Base::getCache() * calling them with the same template types and names. * + * @code{.cpp} + * + * //... + * + * base.open(); + * + * @endcode + * * After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open(). * At this point you are not allowed to add any more storages, otherwise LMDBAL::Opened exception will be thrown. * It's currently the limitation of this little library and I might solve it in the future. * Database will throw no exception if you will try to close the closed LMDBAL::Base or open again already opened one. * Also it will automatically close itself if you'll try to destoroy onpened LMDBAL::Base. * + * @code{.cpp} + * + * //... + * + * storage->addRecord(54, 75); + * cache->addRecord(9, "my value"); + * + * uint32_t value1 = storage->getRecord(54); //75 + * std::string value2 = cache->getRecord(9); //"myValue" + * + * uint32_t count1 = storage->count(); //1 + * uint32_t count2 = cache->count(); //1 + * + * storage->removeRecord(54); + * cache->removeRecord(9); + * + * @endcode + * * To discover how to store read and modify data take a look at LMDBAL::Storage and LMDBAL::Cache classes. */ From a6ea9bedc58582ef73415cc5d5d60c4ae50f3206 Mon Sep 17 00:00:00 2001 From: bmckwm Date: Tue, 1 Oct 2024 11:13:48 +0000 Subject: [PATCH 087/125] Option to build documentation formats separately Signed-off-by: bmckwm --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac8a188..dcafaa1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,9 @@ cmake_policy(SET CMP0079 NEW) option(BUILD_STATIC "Builds library as static library" OFF) option(BUILD_TESTS "Builds tests" OFF) -option(BUILD_DOC "Builds documentation" OFF) +option(BUILD_DOC_MAN "Builds man page documentation" OFF) +option(BUILD_DOC_HTML "Builds html documentation" OFF) +option(BUILD_DOC_XML "Builds xml documentation" OFF) option(BUILD_DOXYGEN_AWESOME "Builds documentation alternative style" OFF) include(GNUInstallDirs) @@ -68,7 +70,7 @@ if (UNIX) endif() add_subdirectory(src) -if (BUILD_DOC) +if (BUILD_DOC_MAN OR BUILD_DOC_HTML OR BUILD_DOC_XML) find_package(Doxygen) if (DOXYGEN_FOUND) add_subdirectory(doc) From 8ad684144fddd469ec17a17be825b3edd4c5af6d Mon Sep 17 00:00:00 2001 From: bmckwm Date: Tue, 1 Oct 2024 11:20:37 +0000 Subject: [PATCH 088/125] Only install documentation if built --- doc/CMakeLists.txt | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 48e365d..0757340 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,6 +1,12 @@ -set(DOXYGEN_GENERATE_HTML YES) -set(DOXYGEN_GENERATE_MAN YES) -set(DOXYGEN_GENERATE_XML YES) +if (BUILD_DOC_HTML) + set(DOXYGEN_GENERATE_HTML YES) +endif() +if (BUILD_DOC_MAN) + set(DOXYGEN_GENERATE_MAN YES) +endif() +if (BUILD_DOC_XML) + set(DOXYGEN_GENERATE_XML YES) +endif() if (BUILD_DOXYGEN_AWESOME) include(ExternalProject) @@ -36,13 +42,25 @@ doxygen_add_docs( ALL COMMENT "Generate man and html pages" ) -install(DIRECTORY +if (BUILD_DOC_MAN) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man + TYPE DOC +) +endif() +if (BUILD_DOC_HTML) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html + TYPE DOC +) +endif() +if (BUILD_DOC_XML) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xml TYPE DOC ) +endif() if (BUILD_DOXYGEN_AWESOME) add_dependencies(documentation doxygen-awesome-css) -endif() +endif() \ No newline at end of file From d2a68ef5ee17040404588de84aac05effc8683ec Mon Sep 17 00:00:00 2001 From: bmckwm Date: Sat, 5 Oct 2024 11:19:39 +0000 Subject: [PATCH 089/125] Indicate available under GPL-3.0-or-later Signed-off-by: bmckwm --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c73873..23d28e3 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,4 @@ if you're in the same directory with it ## License -This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details +This project is available under the GPL-3.0 License or any later version - see the [LICENSE.md](LICENSE.md) file for details From a09030cd4e686393c470f94ca33059aaf07eb6a7 Mon Sep 17 00:00:00 2001 From: bmckwm Date: Sat, 5 Oct 2024 11:26:03 +0000 Subject: [PATCH 090/125] Specify which files are under which licenses --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23d28e3..73302ef 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,4 @@ if you're in the same directory with it ## License -This project is available under the GPL-3.0 License or any later version - see the [LICENSE.md](LICENSE.md) file for details +This project is available under the GPL-3.0 License or any later version. The file [cmake/FindLMDB.cmake](cmake/FindLMDB.cmake) is available under the GPL-3.0 License only, - see the [LICENSE.md](LICENSE.md) file for details From deded0c4e1cd3c6031068b4b55d8bf7358842f47 Mon Sep 17 00:00:00 2001 From: bmckwm Date: Sat, 5 Oct 2024 12:15:03 +0000 Subject: [PATCH 091/125] Use text from fsf.org https://www.gnu.org/licenses/gpl-3.0.md --- LICENSE.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 2fb2e74..3b176db 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -### GNU GENERAL PUBLIC LICENSE +# GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -8,7 +8,7 @@ Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. -### Preamble +## Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. @@ -73,9 +73,9 @@ assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. -### TERMS AND CONDITIONS +## TERMS AND CONDITIONS -#### 0. Definitions. +### 0. Definitions. "This License" refers to version 3 of the GNU General Public License. @@ -115,7 +115,7 @@ 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. +### 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 @@ -156,7 +156,7 @@ 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. +### 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 @@ -181,7 +181,7 @@ 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. +### 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 @@ -197,7 +197,7 @@ 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. +### 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 @@ -210,7 +210,7 @@ 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. +### 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 @@ -245,7 +245,7 @@ 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. +### 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 @@ -341,7 +341,7 @@ 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. +### 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. @@ -400,7 +400,7 @@ 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. +### 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or @@ -428,7 +428,7 @@ 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. +### 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 @@ -439,7 +439,7 @@ 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. +### 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 @@ -464,7 +464,7 @@ rights granted under this License, and you may not initiate litigation any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. -#### 11. Patents. +### 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 @@ -533,7 +533,7 @@ 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. +### 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 @@ -546,7 +546,7 @@ 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. Use with the GNU Affero General Public License. +### 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed @@ -557,7 +557,7 @@ but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. -#### 14. Revised Versions of this License. +### 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions @@ -583,7 +583,7 @@ 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. +### 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 @@ -595,7 +595,7 @@ 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. +### 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 @@ -607,7 +607,7 @@ 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. +### 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, @@ -618,7 +618,7 @@ copy of the Program in return for a fee. END OF TERMS AND CONDITIONS -### How to Apply These Terms to Your New Programs +## 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 @@ -673,3 +673,4 @@ library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . + From fc55ec67376cd4b9147528e285c62c7936b0553c Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 6 Oct 2024 12:18:07 +0300 Subject: [PATCH 092/125] Pipeline fix on the new document build options --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index b22a553..8c29f65 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -17,7 +17,7 @@ jobs: - name: Configure working-directory: ./build - run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 - name: Build working-directory: ./build From def2b8bc4dcdbb9e6f953a3248b5f745e9a7eeaa Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 6 Oct 2024 20:11:44 +0300 Subject: [PATCH 093/125] Some test fixes --- test/basic.cpp | 9 ++++--- test/duplicates.cpp | 57 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/test/basic.cpp b/test/basic.cpp index bf0bf39..fb6b0c5 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -113,10 +113,9 @@ TEST_F(BaseTest, AddingKeysToCache) { EXPECT_EQ(db->ready(), true); c1->addRecord(2, "blah balah"); c1->addRecord(-4, "testing goes brrr"); - c1->addRecord(140, "whatever"); + c1->addRecord(40, "whatever"); c1->addRecord(-37, "aaaaa tss tsss tsss tsss aaaaaaa"); - EXPECT_EQ(c1->getRecord(140), "whatever"); - EXPECT_EQ(c1->getRecord(-116), "whatever"); + EXPECT_EQ(c1->getRecord(40), "whatever"); } TEST_F(BaseTest, AddingKeysToVariableCache) { @@ -189,8 +188,8 @@ TEST_F(BaseTest, Persistence) { EXPECT_EQ(t2->count(), t2Size); EXPECT_EQ(c1->count(), c1Size); - EXPECT_EQ(c1->checkRecord(-116), true); - EXPECT_EQ(c1->getRecord(-116), "whatever"); + EXPECT_EQ(c1->checkRecord(40), true); + EXPECT_EQ(c1->getRecord(40), "whatever"); EXPECT_EQ(c1->checkRecord(-4), true); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); diff --git a/test/duplicates.cpp b/test/duplicates.cpp index 4fbe12a..0bc6b56 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -130,7 +130,10 @@ TEST_F(DuplicatesTest, Adding) { tu3->addRecord(7.20001, 4.00000001); //not sure how exactly, but it works EXPECT_EQ(tu3->count(), 7); - EXPECT_EQ(tu3->getRecord(7.2), -113); + + std::set res72({-113, -53.5478, 697, 4, 4.00000001}); + EXPECT_EQ(res72.count(tu3->getRecord(7.2)), 1); + float tu3dd = tu3->getRecord(5119); EXPECT_TRUE(tu3ds == tu3dd); EXPECT_EQ(tu3ds, tu3dd); @@ -144,8 +147,11 @@ TEST_F(DuplicatesTest, Adding) { EXPECT_THROW(tu4->addRecord(327, 79.624923), LMDBAL::Exist); EXPECT_EQ(tu4->count(), 4); - EXPECT_EQ(tu4->getRecord(172), 0.00000001); - EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically + + std::set res327({463.28348, 79.624923}); + std::set res172({0.00001, 0.00000001}); + EXPECT_EQ(res172.count(tu4->getRecord(172)), 1); + EXPECT_EQ(res327.count(tu4->getRecord(327)), 1); tu5->addRecord(-84.7, 45656753); EXPECT_THROW(tu5->addRecord(-84.7, 45656753), LMDBAL::Exist); @@ -174,13 +180,19 @@ TEST_F(DuplicatesTest, Forcing) { tu1->addRecord(-56, 71); tu1->addRecord(-56, 274); tu1->addRecord(-56, 732); + + std::set res56({71, 274, 732}); EXPECT_EQ(tu1->count(), tu1Size += 3); EXPECT_TRUE(tu1->forceRecord(-56, 322)); + res56.insert(322); EXPECT_EQ(tu1->count(), tu1Size += 1); - EXPECT_EQ(tu1->getRecord(-56), 274); //like yeah, it's really counterintuitive, since it's compared byte by byte + EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1); + + res56.insert(14); EXPECT_TRUE(tu1->forceRecord(-56, 14)); EXPECT_EQ(tu1->count(), tu1Size += 1); - EXPECT_EQ(tu1->getRecord(-56), 14); + EXPECT_EQ(res56.count(tu1->getRecord(-56)), 1); + EXPECT_FALSE(tu1->forceRecord(-56, 274)); EXPECT_EQ(tu1->count(), tu1Size); @@ -201,26 +213,38 @@ TEST_F(DuplicatesTest, Forcing) { tu3->addRecord(17.3, 93.21); tu3->addRecord(17.3, 6.6); tu3->addRecord(17.3, 105.1); + std::set res17({93.21, 6.6, 105.1}); + EXPECT_EQ(tu3->count(), tu3Size += 3); EXPECT_TRUE(tu3->forceRecord(17.3, 74.9)); + res17.insert(74.9); EXPECT_EQ(tu3->count(), tu3Size += 1); - EXPECT_EQ(tu3->getRecord(17.3), 105.1f); //here too, really one should not use this function with duplicates, - EXPECT_TRUE(tu3->forceRecord(17.3, 5.1)); //unless he wishes for kinda randomish result + EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1); + + EXPECT_TRUE(tu3->forceRecord(17.3, 5.1)); + res17.insert(5.1); EXPECT_EQ(tu3->count(), tu3Size += 1); - EXPECT_EQ(tu3->getRecord(17.3), 5.1f); + EXPECT_EQ(res17.count(tu3->getRecord(17.3)), 1); + EXPECT_FALSE(tu3->forceRecord(17.3, 93.21)); EXPECT_EQ(tu3->count(), tu3Size); LMDBAL::SizeType tu4Size = tu4->count(); tu4->addRecord(84, -359.109); tu4->addRecord(84, 2879.654); + std::set res84({-359.109, 2879.654}); + EXPECT_EQ(tu4->count(), tu4Size += 2); EXPECT_TRUE(tu4->forceRecord(84, 72.9)); + res84.insert(72.9); EXPECT_EQ(tu4->count(), tu4Size += 1); - EXPECT_EQ(tu4->getRecord(84), 2879.654); + EXPECT_EQ(res84.count(tu4->getRecord(84)), 1); + EXPECT_TRUE(tu4->forceRecord(84, 2679.5)); + res84.insert(2679.5); EXPECT_EQ(tu4->count(), tu4Size += 1); - EXPECT_EQ(tu4->getRecord(84), 2679.5); + EXPECT_EQ(res84.count(tu4->getRecord(84)), 1); + EXPECT_FALSE(tu4->forceRecord(84, -359.109)); EXPECT_EQ(tu4->count(), tu4Size); @@ -286,6 +310,7 @@ TEST_F(DuplicatesTest, Changing) { EXPECT_THROW(tu2->changeRecord("jeremy spins", -7), LMDBAL::Exist); LMDBAL::SizeType tu3Size = tu3->count(); + std::set res26; EXPECT_THROW(tu3->changeRecord(26.7, 68.22), LMDBAL::NotFound); EXPECT_EQ(tu3->count(), tu3Size); tu3->addRecord(26.7, 68.22); @@ -294,11 +319,13 @@ TEST_F(DuplicatesTest, Changing) { tu3->changeRecord(26.7, 68.22); //should just do nothing usefull, but work normally EXPECT_EQ(tu3->getRecord(26.7), 68.22f); tu3->changeRecord(26.7, 23.18); + res26.insert(23.18); EXPECT_EQ(tu3->count(), tu3Size); EXPECT_EQ(tu3->getRecord(26.7), 23.18f); tu3->addRecord(26.7, 22.16); + res26.insert(22.16); EXPECT_EQ(tu3->count(), tu3Size += 1); - EXPECT_EQ(tu3->getRecord(26.7), 23.18f); + EXPECT_EQ(res26.count(tu3->getRecord(26.7)), 1); tu3->changeRecord(26.7, 21.7); EXPECT_EQ(tu3->count(), tu3Size); EXPECT_EQ(tu3->getRecord(26.7), 21.7f); @@ -316,17 +343,21 @@ TEST_F(DuplicatesTest, Changing) { tu4->changeRecord(852, 6795.349); //should just do nothing usefull, but work normally EXPECT_EQ(tu4->getRecord(852), 6795.349); tu4->changeRecord(852, 13.54); + std::set res852({13.54}); EXPECT_EQ(tu4->count(), tu4Size); EXPECT_EQ(tu4->getRecord(852), 13.54); tu4->addRecord(852, 213.85); + res852.insert(213.85); EXPECT_EQ(tu4->count(), tu4Size += 1); - EXPECT_EQ(tu4->getRecord(852), 13.54); + EXPECT_EQ(res852.count(tu4->getRecord(852)), 1); tu4->changeRecord(852, 236.21); + res852.insert(236.21); EXPECT_EQ(tu4->count(), tu4Size); EXPECT_EQ(tu4->getRecord(852), 236.21); tu4->changeRecord(852, 46324.1135); + res852.insert(46324.1135); EXPECT_EQ(tu4->count(), tu4Size); - EXPECT_EQ(tu4->getRecord(852), 213.85); + EXPECT_EQ(res852.count(tu4->getRecord(852)), 1); EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist); } From 4fd3799c1912cd1c8cd87c5924728c5f5821707d Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 28 Oct 2024 14:11:30 +0200 Subject: [PATCH 094/125] Playing with CI --- .gitea/workflows/aur.yml | 80 ++++++++++++++++++++++++++++++++++++ packaging/Archlinux/PKGBUILD | 6 +-- 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 .gitea/workflows/aur.yml diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml new file mode 100644 index 0000000..bdd6e32 --- /dev/null +++ b/.gitea/workflows/aur.yml @@ -0,0 +1,80 @@ +name: Release to AUR +on: + workflow_call: + inputs: + package-name: + required: true + type: string + version: + required: true + type: string + description: + required: true + type: string + depends: + required: true + type: string + workflow_dispatch: + inputs: + package-name: + description: "The name under which the package is going to be packaged to AUR" + type: string + default: "lmdbal" + version: + description: "The version of the published package" + type: string + default: "1.0.0" + description: + description: "The description of the package" + type: string + default: "LMDB Abstraction Layer" + depends: + description: "Additional dependencies" + type: string + default: "" + +jobs: + aur: + name: Release ${{ inputs.package-name }} to AUR + runs-on: archlinux + steps: + - name: Download the release tarball + run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz + + - name: Calculate SHA256 for the tarball + run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV + + - name: Unarchive tarball + run: tar -xvzf tarball.tar.gz + + - name: Clone the AUR repository + run: | + echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key + chmod 600 key + GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/${{ inputs.package-name }}.git aur + chmod 777 -R aur + cd aur + git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} + git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }} + + + - name: Copy PKGBUILD to the directory + run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/ + + - name: Patch PKGBUILD file, and generate .SRCINFO + working-directory: aur + run: | + sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD + sed -i "/pkgname=lmdbal/c\pkgname=(${{ inputs.package-name }})" PKGBUILD + sed -i "/pkgver=1.0.0/c\pkgver=(${{ inputs.version }})" PKGBUILD + sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=(\"${{ inputs.description }}\")" PKGBUILD + sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} ))" PKGBUILD +# sed -i "/pkgname=lmdbal/c\pkgname=('${{ gitea.event.release.tag_name }}')" PKGBUILD + sudo -u build makepkg --printsrcinfo > .SRCINFO + + - name: Commit package to aur + working-directory: aur + run: | + git add PKGBUILD .SRCINFO + git commit -m "${{ gitea.event.release.body }}" + GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 0f4b0da..d8e2277 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,12 +1,12 @@ # Maintainer: Yury Gubich pkgname=lmdbal -pkgver=0.5.4 +pkgver=1.0.0 pkgrel=1 -pkgdesc="LMDB Abstraction Layer, qt5 version" +pkgdesc="LMDB Abstraction Layer" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/lmdbal" license=('GPL3') -depends=( 'lmdb' 'qt5-base') +depends=( 'lmdb' ) makedepends=('cmake>=3.16' 'gcc') optdepends=() From 75aafd2750dcb2273566f94a196d87dfe5f3550f Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 28 Oct 2024 14:14:39 +0200 Subject: [PATCH 095/125] A fix for CI hopefully --- .gitea/workflows/aur.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml index bdd6e32..f0b1dc3 100644 --- a/.gitea/workflows/aur.yml +++ b/.gitea/workflows/aur.yml @@ -69,7 +69,6 @@ jobs: sed -i "/pkgver=1.0.0/c\pkgver=(${{ inputs.version }})" PKGBUILD sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=(\"${{ inputs.description }}\")" PKGBUILD sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} ))" PKGBUILD -# sed -i "/pkgname=lmdbal/c\pkgname=('${{ gitea.event.release.tag_name }}')" PKGBUILD sudo -u build makepkg --printsrcinfo > .SRCINFO - name: Commit package to aur From 93ed15e1da1cc100c687becc318ffb68e655e9d4 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 19:30:19 +0200 Subject: [PATCH 096/125] CI try 1 --- .gitea/workflows/aur.yml | 19 +++++++------ .gitea/workflows/release.yml | 53 +++++++++++------------------------- packaging/Archlinux/PKGBUILD | 2 +- 3 files changed, 27 insertions(+), 47 deletions(-) diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml index f0b1dc3..a1d08cb 100644 --- a/.gitea/workflows/aur.yml +++ b/.gitea/workflows/aur.yml @@ -5,25 +5,21 @@ on: package-name: required: true type: string - version: - required: true - type: string description: required: true type: string depends: required: true type: string + flags: + required: true + type: string workflow_dispatch: inputs: package-name: description: "The name under which the package is going to be packaged to AUR" type: string default: "lmdbal" - version: - description: "The version of the published package" - type: string - default: "1.0.0" description: description: "The description of the package" type: string @@ -32,10 +28,14 @@ on: description: "Additional dependencies" type: string default: "" + flags: + description: "Additional CMake flags" + type: string + default: "" jobs: aur: - name: Release ${{ inputs.package-name }} to AUR + name: Releasing ${{ inputs.package-name }} to AUR runs-on: archlinux steps: - name: Download the release tarball @@ -66,9 +66,10 @@ jobs: run: | sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD sed -i "/pkgname=lmdbal/c\pkgname=(${{ inputs.package-name }})" PKGBUILD - sed -i "/pkgver=1.0.0/c\pkgver=(${{ inputs.version }})" PKGBUILD + sed -i "/pkgver=1.0.0/c\pkgver=(${{ gitea.event.release.tag_name }})" PKGBUILD sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=(\"${{ inputs.description }}\")" PKGBUILD sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} ))" PKGBUILD + sed -i '/cmake . -D/s/$/ ${{ input.flags }}/' PKGBUILD sudo -u build makepkg --printsrcinfo > .SRCINFO - name: Commit package to aur diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index e3772ff..21c263b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -5,42 +5,21 @@ on: types: [published] jobs: - Archlinux: - runs-on: archlinux - steps: - - name: Download the release tarball - run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz + qt5: + name: Release qt5 build to AUR + uses: ./.gitea/workflows/aur.yml + with: + package-name: lmdbal-qt5 + description: LMDB Abstraction Layer, qt5 version + depends: 'qt5-base' + flags: -D QT_VERSION_MAJOR=5 - - name: Calculate SHA256 for the tarball - run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV - - - name: Unarchive tarball - run: tar -xvzf tarball.tar.gz - - - name: Clone the AUR repository - run: | - echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key - chmod 600 key - GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/lmdbal.git aur - chmod 777 -R aur - cd aur - git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} - git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }} - - - - name: Copy PKGBUILD to the directory - run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/ - - - name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO - working-directory: aur - run: | - sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD - sudo -u build makepkg --printsrcinfo > .SRCINFO - - - name: Commit package to aur - working-directory: aur - run: | - git add PKGBUILD .SRCINFO - git commit -m "${{ gitea.event.release.body }}" - GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push + qt6: + name: Release qt6 build to AUR + uses: ./.gitea/workflows/aur.yml + with: + package-name: lmdbal-qt6 + description: LMDB Abstraction Layer, qt6 version + depends: 'qt6-base' + flags: -D QT_VERSION_MAJOR=6 diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index d8e2277..f1dba99 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -14,7 +14,7 @@ source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pk sha256sums=('SKIP') build() { cd "$srcdir/$pkgname" - cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -D QT_VERSION_MAJOR=5 + cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release cmake --build . } package() { From 29b126c30ead7dd8f9fe2d3257e799a3e8d8fb89 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 19:44:07 +0200 Subject: [PATCH 097/125] CI Try 2 --- .gitea/workflows/main.yml | 21 +++++++++++++++------ CHANGELOG.md | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 8c29f65..82291cb 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -6,7 +6,20 @@ on: - master jobs: - Archlinux: + test-qt5: + name: Test LMDBAL with qt5 + uses: uses: ./.gitea/workflows/aur.yml + with: + flags: -D QT_VERSION_MAJOR=5 + + test-qt5: + name: Test LMDBAL with qt6 + uses: uses: ./.gitea/workflows/aur.yml + with: + flags: -D QT_VERSION_MAJOR=6 + + docs: + name: Release documentation runs-on: archlinux steps: - name: Check out repository code @@ -17,16 +30,12 @@ jobs: - name: Configure working-directory: ./build - run: cmake .. -D BUILD_TESTS=True -D BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True -D QT_VERSION_MAJOR=5 + run: cmake .. BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True - name: Build working-directory: ./build run: cmake --build . - - name: Run tests - working-directory: ./build/test - run: ./runUnitTests - - name: Copy docs via scp uses: appleboy/scp-action@master # working-directory: ./build/doc //doesn't work diff --git a/CHANGELOG.md b/CHANGELOG.md index a70560d..32ea9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# LMDBAL 0.5.4 (November 30, 2024) +### Improvements +- Technical release just to resolve build issues + # LMDBAL 0.5.3 (November 14, 2023) ### Improvements - Now you don't need to link agains lmdb, just linking against LMDBAL is enough From 7c26b09056aba7457dbc6a6af1b6d44b7a86be4e Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 19:45:27 +0200 Subject: [PATCH 098/125] CI try 3 --- .gitea/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 82291cb..a4a6087 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -8,13 +8,13 @@ on: jobs: test-qt5: name: Test LMDBAL with qt5 - uses: uses: ./.gitea/workflows/aur.yml + uses: ./.gitea/workflows/aur.yml with: flags: -D QT_VERSION_MAJOR=5 test-qt5: name: Test LMDBAL with qt6 - uses: uses: ./.gitea/workflows/aur.yml + uses: ./.gitea/workflows/aur.yml with: flags: -D QT_VERSION_MAJOR=6 From 8b7d548df6317d8ab77526272d49a335eb010e7a Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 19:46:43 +0200 Subject: [PATCH 099/125] CI try 4 --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index a4a6087..bf54766 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -12,7 +12,7 @@ jobs: with: flags: -D QT_VERSION_MAJOR=5 - test-qt5: + test-qt6: name: Test LMDBAL with qt6 uses: ./.gitea/workflows/aur.yml with: From c3246fd2b59be3366c28c5689f8241e9ac0bd1ac Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 20:37:28 +0200 Subject: [PATCH 100/125] CI try 5 --- .gitea/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index bf54766..5cbf3af 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -8,13 +8,13 @@ on: jobs: test-qt5: name: Test LMDBAL with qt5 - uses: ./.gitea/workflows/aur.yml + uses: ./.gitea/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=5 test-qt6: name: Test LMDBAL with qt6 - uses: ./.gitea/workflows/aur.yml + uses: ./.gitea/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=6 From 551495843d60f3c9fe6f69da1df55d53aa8d225c Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 20:39:34 +0200 Subject: [PATCH 101/125] CI try 5 --- .gitea/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 5cbf3af..066f206 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -21,6 +21,7 @@ jobs: docs: name: Release documentation runs-on: archlinux + needs: [test-qt5, test-qt6] steps: - name: Check out repository code uses: actions/checkout@v3 From 1083df36ee58505523960decf202838142851961 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 20:59:49 +0200 Subject: [PATCH 102/125] CI try 6 --- .gitea/workflows/main.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 066f206..558e28f 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -6,6 +6,18 @@ on: - master jobs: + debug-path: + name: Debug Repository State + runs-on: archlinux + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: List all files + run: | + echo "Listing files in $(pwd):" + ls -R + test-qt5: name: Test LMDBAL with qt5 uses: ./.gitea/workflows/test.yml From f5d6fb914105c8b1c9f79ef3dfc5ce30c22211d7 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 21:11:30 +0200 Subject: [PATCH 103/125] CI try 7 --- .gitea/workflows/main.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 558e28f..25650fe 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -12,11 +12,17 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v3 + with: + fetch-depth: 0 # Ensure all files and full history are fetched + path: . # Clone into the root workspace directory - - name: List all files + - name: List all files in the workspace run: | - echo "Listing files in $(pwd):" - ls -R + echo "Listing files in $(pwd):" + ls -la + echo "Checking .gitea contents:" + ls -la .gitea/workflows || echo ".gitea/workflows not found!" + test-qt5: name: Test LMDBAL with qt5 From 4c5e4bb6ac77043aa332a19a2da673d8d4869f08 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 21:17:07 +0200 Subject: [PATCH 104/125] CI try 8 --- .gitea/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 25650fe..4f52da0 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -26,13 +26,13 @@ jobs: test-qt5: name: Test LMDBAL with qt5 - uses: ./.gitea/workflows/test.yml + uses: ./.gitea/workflows/unit-test.yml with: flags: -D QT_VERSION_MAJOR=5 test-qt6: name: Test LMDBAL with qt6 - uses: ./.gitea/workflows/test.yml + uses: ./.gitea/workflows/unit-test.yml with: flags: -D QT_VERSION_MAJOR=6 From 75010c0bc6e75b5666efe31e5b2c1458821e4ac3 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 21:19:50 +0200 Subject: [PATCH 105/125] CI try 9 --- .gitea/workflows/main.yml | 22 ++-------------------- .gitea/workflows/test.yml | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 .gitea/workflows/test.yml diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 4f52da0..066f206 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -6,33 +6,15 @@ on: - master jobs: - debug-path: - name: Debug Repository State - runs-on: archlinux - steps: - - name: Check out repository code - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Ensure all files and full history are fetched - path: . # Clone into the root workspace directory - - - name: List all files in the workspace - run: | - echo "Listing files in $(pwd):" - ls -la - echo "Checking .gitea contents:" - ls -la .gitea/workflows || echo ".gitea/workflows not found!" - - test-qt5: name: Test LMDBAL with qt5 - uses: ./.gitea/workflows/unit-test.yml + uses: ./.gitea/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=5 test-qt6: name: Test LMDBAL with qt6 - uses: ./.gitea/workflows/unit-test.yml + uses: ./.gitea/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=6 diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..922e361 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,36 @@ +name: Build and run unit tests for LMDBAL +on: + workflow_call: + inputs: + flags: + required: true + type: string + workflow_dispatch: + inputs: + flags: + description: "Flags for CMake configure stage" + type: string + default: "lmdbal" + +jobs: + test: + name: Building and rinning unit tests + runs-on: archlinux + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Make a build directory + run: mkdir build + + - name: Configure + working-directory: ./build + run: cmake .. -D BUILD_TESTS=True -D QT_VERSION_MAJOR= ${{ inputs.flags }} + + - name: Build + working-directory: ./build + run: cmake --build . + + - name: Run tests + working-directory: ./build/test + run: ./runUnitTests From 50a13d72908085b35c148e7d9a2371e0a6f63753 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 22:28:15 +0200 Subject: [PATCH 106/125] CI try 10 --- .gitea/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 21c263b..d20a949 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -13,6 +13,10 @@ jobs: description: LMDB Abstraction Layer, qt5 version depends: 'qt5-base' flags: -D QT_VERSION_MAJOR=5 + secrets: + DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} + DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} + DEPLOY_TO_AUR_EMAIL: ${{ secrets.DEPLOY_TO_AUR_EMAIL }} qt6: name: Release qt6 build to AUR @@ -22,4 +26,8 @@ jobs: description: LMDB Abstraction Layer, qt6 version depends: 'qt6-base' flags: -D QT_VERSION_MAJOR=6 + secrets: + DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} + DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} + DEPLOY_TO_AUR_EMAIL: ${{ secrets.DEPLOY_TO_AUR_EMAIL }} From 2add0b1ae2c4d79e98b66308c24a8d4655b9b704 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 22:46:09 +0200 Subject: [PATCH 107/125] CI try 11 --- .gitea/workflows/aur.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml index a1d08cb..d4a8ec6 100644 --- a/.gitea/workflows/aur.yml +++ b/.gitea/workflows/aur.yml @@ -65,10 +65,10 @@ jobs: working-directory: aur run: | sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD - sed -i "/pkgname=lmdbal/c\pkgname=(${{ inputs.package-name }})" PKGBUILD - sed -i "/pkgver=1.0.0/c\pkgver=(${{ gitea.event.release.tag_name }})" PKGBUILD - sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=(\"${{ inputs.description }}\")" PKGBUILD - sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} ))" PKGBUILD + sed -i "/pkgname=lmdbal/c\pkgname=${{ inputs.package-name }}" PKGBUILD + sed -i "/pkgver=1.0.0/c\pkgver=${{ gitea.event.release.tag_name }}" PKGBUILD + sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=\"${{ inputs.description }}\"" PKGBUILD + sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} )" PKGBUILD sed -i '/cmake . -D/s/$/ ${{ input.flags }}/' PKGBUILD sudo -u build makepkg --printsrcinfo > .SRCINFO From 567045331460f59a34597b5c7ead9b5eca7649e5 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 23:07:49 +0200 Subject: [PATCH 108/125] CI try 12 --- .gitea/workflows/aur.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml index d4a8ec6..df05943 100644 --- a/.gitea/workflows/aur.yml +++ b/.gitea/workflows/aur.yml @@ -69,7 +69,7 @@ jobs: sed -i "/pkgver=1.0.0/c\pkgver=${{ gitea.event.release.tag_name }}" PKGBUILD sed -i "/pkgdesc=\"LMDB Abstraction Layer\"/c\pkgdesc=\"${{ inputs.description }}\"" PKGBUILD sed -i "/depends=( 'lmdb' )/c\depends=( 'lmdb' ${{ inputs.depends }} )" PKGBUILD - sed -i '/cmake . -D/s/$/ ${{ input.flags }}/' PKGBUILD + sed -i '/cmake . -D/s/$/ ${{ inputs.flags }}/' PKGBUILD sudo -u build makepkg --printsrcinfo > .SRCINFO - name: Commit package to aur diff --git a/README.md b/README.md index 73302ef..36b620e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### Prerequisites - a compiler (c++ would do) -- Qt 5 or higher (qt5-base would do) +- Qt 5 or 6 or higher (qt5-base or qt6-base would do) - lmdb - CMake 3.16 or higher - Doxygen (optional, for documentation) From 649d967963c7963a6296c49a65bd945873b9ecc1 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Nov 2024 23:17:52 +0200 Subject: [PATCH 109/125] CI try 13 --- .gitea/workflows/aur.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/aur.yml b/.gitea/workflows/aur.yml index df05943..ef1df58 100644 --- a/.gitea/workflows/aur.yml +++ b/.gitea/workflows/aur.yml @@ -56,7 +56,7 @@ jobs: cd aur git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }} - + git checkout -b master || git checkout master - name: Copy PKGBUILD to the directory run: cp lmdbal/packaging/Archlinux/PKGBUILD aur/ @@ -77,4 +77,4 @@ jobs: run: | git add PKGBUILD .SRCINFO git commit -m "${{ gitea.event.release.body }}" - GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push + GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push origin master From 7aaab5e80726b48e15c88c2788babe95f37d0278 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 3 Dec 2024 19:36:07 +0200 Subject: [PATCH 110/125] builds with different names to coexist --- .gitea/workflows/release.yml | 4 +-- CMakeLists.txt | 67 ++++++++++++++++++----------------- packaging/Archlinux/PKGBUILD | 8 ++--- src/CMakeLists.txt | 4 +-- src/serializer/CMakeLists.txt | 2 +- test/CMakeLists.txt | 2 +- 6 files changed, 45 insertions(+), 42 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index d20a949..ad68d3f 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -12,7 +12,7 @@ jobs: package-name: lmdbal-qt5 description: LMDB Abstraction Layer, qt5 version depends: 'qt5-base' - flags: -D QT_VERSION_MAJOR=5 + flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 secrets: DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} @@ -25,7 +25,7 @@ jobs: package-name: lmdbal-qt6 description: LMDB Abstraction Layer, qt6 version depends: 'qt6-base' - flags: -D QT_VERSION_MAJOR=6 + flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT5 secrets: DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} diff --git a/CMakeLists.txt b/CMakeLists.txt index dcafaa1..a518d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,12 @@ project(LMDBAL DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) -string(TOLOWER ${PROJECT_NAME} PROJECT_LOW) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) +set(LMDBAL_NAME ${PROJECT_NAME} CACHE STRING "Override for library name and install path") + option(BUILD_STATIC "Builds library as static library" OFF) option(BUILD_TESTS "Builds tests" OFF) option(BUILD_DOC_MAN "Builds man page documentation" OFF) @@ -25,6 +26,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +string(TOLOWER ${LMDBAL_NAME} LMDBAL_NAME_LOW) + if (NOT DEFINED QT_VERSION_MAJOR) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) endif() @@ -38,9 +41,9 @@ if (NOT CMAKE_BUILD_TYPE) endif () if (BUILD_STATIC) - add_library(${PROJECT_NAME} STATIC) + add_library(${LMDBAL_NAME} STATIC) else () - add_library(${PROJECT_NAME} SHARED) + add_library(${LMDBAL_NAME} SHARED) endif() if (CMAKE_BUILD_TYPE STREQUAL "Release") @@ -52,21 +55,21 @@ elseif (CMAKE_BUILD_TYPE STREQUAL "Debug") endif() message("Compilation options: " ${COMPILE_OPTIONS}) -target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) +target_compile_options(${LMDBAL_NAME} PRIVATE ${COMPILE_OPTIONS}) -set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${version}) -set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION 1) -set_property(TARGET ${PROJECT_NAME} PROPERTY EXPORT_NAME ${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY INTERFACE_${PROJECT_NAME}_MAJOR_VERSION 1) -set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY - COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION +set_property(TARGET ${LMDBAL_NAME} PROPERTY VERSION ${version}) +set_property(TARGET ${LMDBAL_NAME} PROPERTY SOVERSION 1) +set_property(TARGET ${LMDBAL_NAME} PROPERTY EXPORT_NAME ${LMDBAL_NAME}) +set_property(TARGET ${LMDBAL_NAME} PROPERTY INTERFACE_${LMDBAL_NAME}_MAJOR_VERSION 1) +set_property(TARGET ${LMDBAL_NAME} APPEND PROPERTY + COMPATIBLE_INTERFACE_STRING ${LMDBAL_NAME}_MAJOR_VERSION ) if (UNIX) - set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${PROJECT_LOW}") - set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) - set_property(TARGET ${PROJECT_NAME} PROPERTY SKIP_BUILD_RPATH FALSE) - set_property(TARGET ${PROJECT_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) + set_property(TARGET ${LMDBAL_NAME} PROPERTY INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${LMDBAL_NAME_LOW}") + set_property(TARGET ${LMDBAL_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) + set_property(TARGET ${LMDBAL_NAME} PROPERTY SKIP_BUILD_RPATH FALSE) + set_property(TARGET ${LMDBAL_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) endif() add_subdirectory(src) @@ -84,47 +87,47 @@ if (BUILD_TESTS) endif () target_include_directories( - ${PROJECT_NAME} + ${LMDBAL_NAME} PUBLIC - $ + $ $ $ ) -target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) -target_include_directories(${PROJECT_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) +target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) target_link_libraries( - ${PROJECT_NAME} + ${LMDBAL_NAME} Qt${QT_VERSION_MAJOR}::Core lmdb ) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake" - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW} ) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake" VERSION "${version}" COMPATIBILITY AnyNewerVersion ) -install(TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_LOW}Targets +install(TARGETS ${LMDBAL_NAME} + EXPORT ${LMDBAL_NAME_LOW}Targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW} ) -install(EXPORT ${PROJECT_LOW}Targets - FILE ${PROJECT_LOW}Targets.cmake - NAMESPACE ${PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} +install(EXPORT ${LMDBAL_NAME_LOW}Targets + FILE ${LMDBAL_NAME_LOW}Targets.cmake + NAMESPACE ${LMDBAL_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW} ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}Config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LOW}ConfigVersion.cmake" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_LOW} + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW} ) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index f1dba99..28ef6d6 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Yury Gubich pkgname=lmdbal pkgver=1.0.0 -pkgrel=1 +pkgrel=2 pkgdesc="LMDB Abstraction Layer" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/lmdbal" @@ -10,14 +10,14 @@ depends=( 'lmdb' ) makedepends=('cmake>=3.16' 'gcc') optdepends=() -source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz") +source=("lmdbal-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") sha256sums=('SKIP') build() { - cd "$srcdir/$pkgname" + cd "$srcdir/lmdbal" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release cmake --build . } package() { - cd "$srcdir/$pkgname" + cd "$srcdir/lmdbal" DESTDIR="$pkgdir/" cmake --install . } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 956e6b0..9332fd8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,8 +18,8 @@ set(HEADERS transaction.h ) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES}) add_subdirectory(serializer) -install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW}) diff --git a/src/serializer/CMakeLists.txt b/src/serializer/CMakeLists.txt index 0c0a633..2df8e47 100644 --- a/src/serializer/CMakeLists.txt +++ b/src/serializer/CMakeLists.txt @@ -16,4 +16,4 @@ set(HEADERS serializer_qbytearray.hpp ) -install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_LOW}) +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LMDBAL_NAME_LOW}) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 93d54e3..63e0701 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,7 +20,7 @@ target_include_directories(runUnitTests PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCL target_link_libraries( runUnitTests GTest::gtest_main - ${PROJECT_NAME} + ${LMDBAL_NAME} Qt${QT_VERSION_MAJOR}::Core ) include(GoogleTest) From 02946bbe984c98038f5581bb78be58d08ebb756f Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 3 Dec 2024 19:45:15 +0200 Subject: [PATCH 111/125] Typo in the build CI --- .gitea/workflows/main.yml | 4 ++-- .gitea/workflows/release.yml | 2 +- CHANGELOG.md | 1 + packaging/Archlinux/PKGBUILD | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 066f206..ed68bb0 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -10,13 +10,13 @@ jobs: name: Test LMDBAL with qt5 uses: ./.gitea/workflows/test.yml with: - flags: -D QT_VERSION_MAJOR=5 + flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 test-qt6: name: Test LMDBAL with qt6 uses: ./.gitea/workflows/test.yml with: - flags: -D QT_VERSION_MAJOR=6 + flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 docs: name: Release documentation diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index ad68d3f..8cdc744 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -25,7 +25,7 @@ jobs: package-name: lmdbal-qt6 description: LMDB Abstraction Layer, qt6 version depends: 'qt6-base' - flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT5 + flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 secrets: DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ea9a4..8194b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # LMDBAL 0.5.4 (November 30, 2024) ### Improvements - Technical release just to resolve build issues +- Now different flavours of the package can coexist in the system # LMDBAL 0.5.3 (November 14, 2023) ### Improvements diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 28ef6d6..6c80d5a 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Yury Gubich pkgname=lmdbal pkgver=1.0.0 -pkgrel=2 +pkgrel=3 pkgdesc="LMDB Abstraction Layer" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/lmdbal" From 43d490080952bfe3ad07611da48ee8d8d455eac3 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 13 Dec 2024 20:27:47 +0200 Subject: [PATCH 112/125] Some more build fixes --- .gitea/workflows/release.yml | 4 ++-- README.md | 1 + cmake/Config.cmake.in | 4 ++-- packaging/Archlinux/PKGBUILD | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 8cdc744..733957a 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -12,7 +12,7 @@ jobs: package-name: lmdbal-qt5 description: LMDB Abstraction Layer, qt5 version depends: 'qt5-base' - flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 + flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBALQT5 secrets: DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} @@ -25,7 +25,7 @@ jobs: package-name: lmdbal-qt6 description: LMDB Abstraction Layer, qt6 version depends: 'qt6-base' - flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 + flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBALQT6 secrets: DEPLOY_TO_AUR_PRIVATE_KEY: ${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }} DEPLOY_TO_AUR_USER_NAME: ${{ secrets.DEPLOY_TO_AUR_USER_NAME }} diff --git a/README.md b/README.md index 36b620e..9c6a152 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`: - `BUILD_DOC` - `True` build doxygen documentation, `False` does not (default is `False`); - `BUILD_DOXYGEN_AWESOME` - `True` build doxygen awesome theme if `BUILD_DOC` is also `True` (default is `False`); - `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically; +- `LMDBAL_NAME` - `LMDBAL` builds main target with this name, also install path witll be this name lowercase, usefull if you want to build `LMDBAL-QT6` not to conflict with `LMDBAL-QT5`; #### Running tests diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in index 8fea032..f470d3d 100644 --- a/cmake/Config.cmake.in +++ b/cmake/Config.cmake.in @@ -1,5 +1,5 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/lmdbalTargets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@LMDBAL_NAME_LOW@Targets.cmake") -check_required_components(lmdbal) +check_required_components(@LMDBAL_NAME_LOW@) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 6c80d5a..38b3e55 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Yury Gubich pkgname=lmdbal pkgver=1.0.0 -pkgrel=3 +pkgrel=4 pkgdesc="LMDB Abstraction Layer" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/lmdbal" From 56d35d4832b0ddd1a48dea5e602eb87619252f8d Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 17 Dec 2024 20:03:48 +0200 Subject: [PATCH 113/125] pragma once and minor change in transactions --- CHANGELOG.md | 4 ++++ packaging/Archlinux/PKGBUILD | 2 +- src/base.cpp | 24 +++++++++++------------- src/base.h | 7 ++----- src/cache.h | 5 +---- src/cache.hpp | 5 +---- src/cursor.h | 5 +---- src/cursor.hpp | 7 ++----- src/exceptions.h | 5 +---- src/operators.hpp | 5 +---- src/serializer/serializer.h | 6 ++---- src/serializer/serializer.hpp | 5 +---- src/serializer/serializer_double.hpp | 5 +---- src/serializer/serializer_float.hpp | 8 +------- src/serializer/serializer_int16.hpp | 5 +---- src/serializer/serializer_int32.hpp | 8 +------- src/serializer/serializer_int64.hpp | 8 +------- src/serializer/serializer_int8.hpp | 8 +------- src/serializer/serializer_qbytearray.hpp | 9 +-------- src/serializer/serializer_qstring.hpp | 10 +--------- src/serializer/serializer_stdstring.hpp | 9 +-------- src/serializer/serializer_uint16.hpp | 7 +------ src/serializer/serializer_uint32.hpp | 5 +---- src/serializer/serializer_uint64.hpp | 6 +----- src/serializer/serializer_uint8.hpp | 8 +------- src/storage.h | 5 +---- src/storage.hpp | 5 +---- src/transaction.cpp | 18 ++++++++++++++++++ src/transaction.h | 18 ++++++++++++++++++ 29 files changed, 79 insertions(+), 143 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8194b28..879f52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# LMDBAL 0.6.0 (UNRELEASED) +### Improvements +- Less dereferencing + # LMDBAL 0.5.4 (November 30, 2024) ### Improvements - Technical release just to resolve build issues diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 38b3e55..c83a648 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -10,7 +10,7 @@ depends=( 'lmdb' ) makedepends=('cmake>=3.16' 'gcc') optdepends=() -source=("lmdbal-$pkgver.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") +source=("lmdbal-$pkgver-$pkgrel.tar.gz::https://git.macaw.me/blue/lmdbal/archive/$pkgver.tar.gz") sha256sums=('SKIP') build() { cd "$srcdir/lmdbal" diff --git a/src/base.cpp b/src/base.cpp index 149343b..ef932fd 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -43,7 +43,7 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): size(_mapSize), environment(), storages(), - transactions(new Transactions()) + transactions() {} /** @@ -52,8 +52,6 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): LMDBAL::Base::~Base() { close(); - delete transactions; - for (const std::pair& pair : storages) delete pair.second; } @@ -68,14 +66,14 @@ LMDBAL::Base::~Base() { */ void LMDBAL::Base::close() { if (opened) { - for (const LMDBAL::TransactionID id : *transactions) + for (const LMDBAL::TransactionID id : transactions) abortTransaction(id, emptyName); for (const std::pair& pair : storages) pair.second->close(); mdb_env_close(environment); - transactions->clear(); + transactions.clear(); opened = false; } } @@ -301,7 +299,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); - transactions->emplace(txn); + transactions.emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, true); @@ -325,7 +323,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); - transactions->emplace(txn); + transactions.emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, false); @@ -349,15 +347,15 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& if (!opened) throw Closed("abortTransaction", name, storageName); - Transactions::iterator itr = transactions->find(id); - if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this + Transactions::iterator itr = transactions.find(id); + if (itr == transactions.end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to abort transaction: transaction was not found", storageName); abortPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionAborted(id); - transactions->erase(itr); + transactions.erase(itr); } /** @@ -376,15 +374,15 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string if (!opened) throw Closed("abortTransaction", name, storageName); - Transactions::iterator itr = transactions->find(id); - if (itr == transactions->end()) //TODO may be it's a good idea to make an exception class for this + Transactions::iterator itr = transactions.find(id); + if (itr == transactions.end()) //TODO may be it's a good idea to make an exception class for this throw Unknown(name, "unable to commit transaction: transaction was not found", storageName); commitPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionCommited(id); - transactions->erase(itr); + transactions.erase(itr); } /** diff --git a/src/base.h b/src/base.h index c99bf21..749683f 100644 --- a/src/base.h +++ b/src/base.h @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_BASE_H -#define LMDBAL_BASE_H +#pragma once #include #include @@ -106,7 +105,7 @@ private: uint16_t size; /**<\brief lmdb map size in MiB*/ MDB_env* environment; /**<\brief lmdb environment handle*/ Storages storages; /**<\brief Registered storages and caches*/ - Transactions* transactions; /**<\brief Active public transactions*/ + mutable Transactions transactions; /**<\brief Active public transactions*/ inline static const std::string emptyName = ""; /**<\brief Empty string for general fallback purposes*/ }; @@ -215,5 +214,3 @@ template LMDBAL::Cache* LMDBAL::Base::getCache(const std::string& storageName) { return static_cast*>(storages.at(storageName)); } - -#endif //LMDBAL_BASE_H diff --git a/src/cache.h b/src/cache.h index 0883e65..0084984 100644 --- a/src/cache.h +++ b/src/cache.h @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_CACHE_H -#define LMDBAL_CACHE_H +#pragma once #include #include @@ -134,5 +133,3 @@ protected: } #include "cache.hpp" - -#endif // LMDBAL_CACHE_H diff --git a/src/cache.hpp b/src/cache.hpp index e3c6d60..29af6d0 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_CACHE_HPP -#define LMDBAL_CACHE_HPP +#pragma once #include "cache.h" #include "exceptions.h" @@ -898,5 +897,3 @@ void LMDBAL::Cache::destroyTransactionEntry(const Entry& entry) const { break; } } - -#endif //LMDBAL_CACHE_HPP diff --git a/src/cursor.h b/src/cursor.h index c3ff362..c77d4e9 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_CURSOR_H -#define LMDBAL_CURSOR_H +#pragma once #include @@ -105,5 +104,3 @@ private: #include "cursor.hpp" - -#endif //LMDBAL_CURSOR_H diff --git a/src/cursor.hpp b/src/cursor.hpp index 519bd41..0e0dc7b 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_CURSOR_HPP -#define LMDBAL_CURSOR_HPP +#pragma once #include "cursor.h" #include @@ -482,7 +481,7 @@ void LMDBAL::Cursor::prev (K& key, V& value) { * \exception LMDBAL::Unknown thrown if there was some unexpected problem with lmdb */ template -void LMDBAL::Cursor::current (K& key, V& value) const { +void LMDBAL::Cursor::current (K& key, V& value) const { operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName); } @@ -657,5 +656,3 @@ void LMDBAL::Cursor::operateCursorRead( else storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor)); } - -#endif //LMDBAL_CURSOR_HPP diff --git a/src/exceptions.h b/src/exceptions.h index 58bbea4..6f03bd8 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_EXCEPTIONS_H -#define LMDBAL_EXCEPTIONS_H +#pragma once #include #include @@ -237,5 +236,3 @@ private: }; } - -#endif //LMDBAL_EXCEPTIONS_H diff --git a/src/operators.hpp b/src/operators.hpp index 80353ac..9abb2d0 100644 --- a/src/operators.hpp +++ b/src/operators.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_OPERATORS_HPP -#define LMDBAL_OPERATORS_HPP +#pragma once #include #include @@ -209,5 +208,3 @@ QDataStream& operator >> (QDataStream &in, std::list& container) { return in; } - -#endif //LMDBAL_OPERATORS_HPP diff --git a/src/serializer/serializer.h b/src/serializer/serializer.h index 734ac51..bd7496a 100644 --- a/src/serializer/serializer.h +++ b/src/serializer/serializer.h @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_H -#define LMDBAL_SERIALIZER_H +#pragma once + #include @@ -68,5 +68,3 @@ private: #include "serializer_stdstring.hpp" #include "serializer_qstring.hpp" #include "serializer_qbytearray.hpp" - -#endif // LMDBAL_SERIALIZER_H diff --git a/src/serializer/serializer.hpp b/src/serializer/serializer.hpp index 80f4d90..b3353f5 100644 --- a/src/serializer/serializer.hpp +++ b/src/serializer/serializer.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_HPP -#define LMDBAL_SERIALIZER_HPP +#pragma once #include "serializer.h" @@ -159,5 +158,3 @@ MDB_val LMDBAL::Serializer::getData() { return val; } - -#endif //LMDBAL_SERIALIZER_HPP diff --git a/src/serializer/serializer_double.hpp b/src/serializer/serializer_double.hpp index 4582459..be943b8 100644 --- a/src/serializer/serializer_double.hpp +++ b/src/serializer/serializer_double.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_DOUBLE_HPP -#define LMDBAL_SERIALIZER_DOUBLE_HPP +#pragma once namespace LMDBAL { @@ -53,7 +52,5 @@ private: } -#endif //LMDBAL_SERIALIZER_DOUBLE_HPP - diff --git a/src/serializer/serializer_float.hpp b/src/serializer/serializer_float.hpp index be2c72d..f4b84a9 100644 --- a/src/serializer/serializer_float.hpp +++ b/src/serializer/serializer_float.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_FLOAT_HPP -#define LMDBAL_SERIALIZER_FLOAT_HPP +#pragma once namespace LMDBAL { @@ -52,8 +51,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_FLOAT_HPP - - - diff --git a/src/serializer/serializer_int16.hpp b/src/serializer/serializer_int16.hpp index 17e5ea2..0baaaad 100644 --- a/src/serializer/serializer_int16.hpp +++ b/src/serializer/serializer_int16.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_INT16_HPP -#define LMDBAL_SERIALIZER_INT16_HPP +#pragma once #include @@ -54,5 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_INT16_HPP diff --git a/src/serializer/serializer_int32.hpp b/src/serializer/serializer_int32.hpp index 78405c5..339dfea 100644 --- a/src/serializer/serializer_int32.hpp +++ b/src/serializer/serializer_int32.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_INT32_HPP -#define LMDBAL_SERIALIZER_INT32_HPP +#pragma once #include @@ -54,8 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_INT32_HPP - - - diff --git a/src/serializer/serializer_int64.hpp b/src/serializer/serializer_int64.hpp index 4992ae1..107d102 100644 --- a/src/serializer/serializer_int64.hpp +++ b/src/serializer/serializer_int64.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_INT64_HPP -#define LMDBAL_SERIALIZER_INT64_HPP +#pragma once #include @@ -54,8 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_INT64_HPP - - - diff --git a/src/serializer/serializer_int8.hpp b/src/serializer/serializer_int8.hpp index eafd5c6..6f68f18 100644 --- a/src/serializer/serializer_int8.hpp +++ b/src/serializer/serializer_int8.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_INT8_HPP -#define LMDBAL_SERIALIZER_INT8_HPP +#pragma once #include @@ -54,8 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_INT8_HPP - - - diff --git a/src/serializer/serializer_qbytearray.hpp b/src/serializer/serializer_qbytearray.hpp index 6f1c5d1..daf3027 100644 --- a/src/serializer/serializer_qbytearray.hpp +++ b/src/serializer/serializer_qbytearray.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_QBYTEARRAY_HPP -#define LMDBAL_SERIALIZER_QBYTEARRAY_HPP +#pragma once #include @@ -56,9 +55,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_QBYTEARRAY_HPP - - - - diff --git a/src/serializer/serializer_qstring.hpp b/src/serializer/serializer_qstring.hpp index 1a36fcb..fb9310e 100644 --- a/src/serializer/serializer_qstring.hpp +++ b/src/serializer/serializer_qstring.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_QSTRING_HPP -#define LMDBAL_SERIALIZER_QSTRING_HPP +#pragma once #include #include @@ -56,10 +55,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_QSTRING_HPP - - - - - diff --git a/src/serializer/serializer_stdstring.hpp b/src/serializer/serializer_stdstring.hpp index 416cfd6..1856e8c 100644 --- a/src/serializer/serializer_stdstring.hpp +++ b/src/serializer/serializer_stdstring.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_STDSTRING_HPP -#define LMDBAL_SERIALIZER_STDSTRING_HPP +#pragma once #include @@ -54,9 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_STDSTRING_HPP - - - - diff --git a/src/serializer/serializer_uint16.hpp b/src/serializer/serializer_uint16.hpp index d648695..8284f27 100644 --- a/src/serializer/serializer_uint16.hpp +++ b/src/serializer/serializer_uint16.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_UINT16_HPP -#define LMDBAL_SERIALIZER_UINT16_HPP +#pragma once #include @@ -54,7 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_UINT16_HPP - - diff --git a/src/serializer/serializer_uint32.hpp b/src/serializer/serializer_uint32.hpp index 211f151..83e9d35 100644 --- a/src/serializer/serializer_uint32.hpp +++ b/src/serializer/serializer_uint32.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_UINT32_HPP -#define LMDBAL_SERIALIZER_UINT32_HPP +#pragma once #include @@ -54,5 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_UINT32_HPP diff --git a/src/serializer/serializer_uint64.hpp b/src/serializer/serializer_uint64.hpp index 250ec03..f486082 100644 --- a/src/serializer/serializer_uint64.hpp +++ b/src/serializer/serializer_uint64.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_UINT64_HPP -#define LMDBAL_SERIALIZER_UINT64_HPP +#pragma once #include @@ -54,6 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_UINT64_HPP - diff --git a/src/serializer/serializer_uint8.hpp b/src/serializer/serializer_uint8.hpp index 31daca6..0bc73ab 100644 --- a/src/serializer/serializer_uint8.hpp +++ b/src/serializer/serializer_uint8.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_SERIALIZER_UINT8_HPP -#define LMDBAL_SERIALIZER_UINT8_HPP +#pragma once #include @@ -54,8 +53,3 @@ private: }; } - -#endif //LMDBAL_SERIALIZER_UINT8_HPP - - - diff --git a/src/storage.h b/src/storage.h index 82735a7..c077500 100644 --- a/src/storage.h +++ b/src/storage.h @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_STORAGE_H -#define LMDBAL_STORAGE_H +#pragma once #include #include @@ -199,5 +198,3 @@ protected: } #include "storage.hpp" - -#endif //LMDBAL_STORAGE_H diff --git a/src/storage.hpp b/src/storage.hpp index 91756b3..e56e91a 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -#ifndef LMDBAL_STORAGE_HPP -#define LMDBAL_STORAGE_HPP +#pragma once #include "storage.h" #include "exceptions.h" @@ -1182,5 +1181,3 @@ template<> inline std::string LMDBAL::iStorage::toString(const std::string& value) { return value; } - -#endif //LMDBAL_STORAGE_HPP diff --git a/src/transaction.cpp b/src/transaction.cpp index 3632ad6..3d7e3c0 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -1,3 +1,21 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "transaction.h" /** diff --git a/src/transaction.h b/src/transaction.h index 43bb69e..59b6ae9 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -1,3 +1,21 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include "base.h" From 68ea7df6a9fc515487d1ee9a98165b3bbf1aa7e8 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 22 Dec 2024 19:39:35 +0200 Subject: [PATCH 114/125] Transactions now get closed with the database --- CHANGELOG.md | 4 +++ CMakeLists.txt | 2 +- src/base.cpp | 50 ++++++++++++++++++------------------- src/base.h | 2 +- src/exceptions.h | 2 +- src/transaction.cpp | 43 +++++++++++++++++++++++++++---- src/transaction.h | 2 ++ test/cachetransaction.cpp | 23 +++++++++++++++++ test/storagetransaction.cpp | 24 ++++++++++++++++++ 9 files changed, 118 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879f52d..608c121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ # LMDBAL 0.6.0 (UNRELEASED) ### Improvements - Less dereferencing +- Transactions are now closed with the database + +### Bug fixes +- SIGSEGV on closing database with opened transactions # LMDBAL 0.5.4 (November 30, 2024) ### Improvements diff --git a/CMakeLists.txt b/CMakeLists.txt index a518d7f..97b21ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.5.4 + VERSION 0.6.0 DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" LANGUAGES CXX ) diff --git a/src/base.cpp b/src/base.cpp index ef932fd..ce28975 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -66,8 +66,10 @@ LMDBAL::Base::~Base() { */ void LMDBAL::Base::close() { if (opened) { - for (const LMDBAL::TransactionID id : transactions) - abortTransaction(id, emptyName); + for (const std::pair pair : transactions) { + abortTransaction(pair.first, emptyName); + pair.second->reset(); + } for (const std::pair& pair : storages) pair.second->close(); @@ -259,12 +261,13 @@ LMDBAL::WriteTransaction LMDBAL::Base::beginTransaction() { * \brief Aborts transaction * * Terminates transaction cancelling changes. - * This is an optimal way to terminate read-only transactions + * Every storage receives notification about this transaction being aborted. + * This is an optimal way to abort public transactions * * \param[in] id - transaction ID you want to abort * * \exception LMDBAL::Closed - thrown if the database is closed - * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { return abortTransaction(id, emptyName);} @@ -273,11 +276,13 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id) const { * \brief Commits transaction * * Terminates transaction applying changes. + * Every storage receives notification about this transaction being committed. + * This is an optimal way to commit public transactions * * \param[in] id - transaction ID you want to commit * * \exception LMDBAL::Closed - thrown if the database is closed - * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) { return commitTransaction(id, emptyName);} @@ -285,7 +290,9 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) { /** * \brief Begins read-only transaction * - * This function is intended to be called from subordinate storage or cache + * This function is intended to be called from subordinate storage, cache, transaction or cursor. + * Every storage receives notification about this transaction being started. + * This is an optimal way to begin read-only public transactions. * * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong * @@ -299,7 +306,6 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); - transactions.emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, true); @@ -309,7 +315,9 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& /** * \brief Begins writable transaction * - * This function is intended to be called from subordinate storage or cache + * This function is intended to be called from subordinate storage, cache, transaction or cursor. + * Every storage receives notification about this transaction being started. + * This is an optimal way to begin public transactions. * * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong * @@ -323,7 +331,6 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); - transactions.emplace(txn); for (const std::pair& pair : storages) pair.second->transactionStarted(txn, false); @@ -334,55 +341,46 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN * \brief Aborts transaction * * Terminates transaction cancelling changes. - * This is an optimal way to terminate read-only transactions. - * This function is intended to be called from subordinate storage or cache + * Every storage receives notification about this transaction being aborted. + * This is an optimal way to abort public transactions. + * This function is intended to be called from subordinate storage, cache, transaction or cursor * * \param[in] id - transaction ID you want to abort * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong * * \exception LMDBAL::Closed - thrown if the database is closed - * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { if (!opened) throw Closed("abortTransaction", name, storageName); - Transactions::iterator itr = transactions.find(id); - if (itr == transactions.end()) //TODO may be it's a good idea to make an exception class for this - throw Unknown(name, "unable to abort transaction: transaction was not found", storageName); - abortPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionAborted(id); - - transactions.erase(itr); } /** * \brief Commits transaction * * Terminates transaction applying changes. - * This function is intended to be called from subordinate storage or cache + * Every storage receives notification about this transaction being committed. + * This is an optimal way to commit public transactions + * This function is intended to be called from subordinate storage, cache, transaction or cursor * * \param[in] id - transaction ID you want to commit * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong * * \exception LMDBAL::Closed - thrown if the database is closed - * \exception LMDBAL::Unknown - thrown if transaction with given ID was not found or if something unexpected happened + * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) { if (!opened) throw Closed("abortTransaction", name, storageName); - Transactions::iterator itr = transactions.find(id); - if (itr == transactions.end()) //TODO may be it's a good idea to make an exception class for this - throw Unknown(name, "unable to commit transaction: transaction was not found", storageName); - commitPrivateTransaction(id, storageName); for (const std::pair& pair : storages) pair.second->transactionCommited(id); - - transactions.erase(itr); } /** diff --git a/src/base.h b/src/base.h index 749683f..de61ad7 100644 --- a/src/base.h +++ b/src/base.h @@ -85,7 +85,7 @@ public: private: typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ - typedef std::set Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ + typedef std::map Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ void commitTransaction(TransactionID id); void abortTransaction(TransactionID id) const; diff --git a/src/exceptions.h b/src/exceptions.h index 6f03bd8..d5f252b 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -40,7 +40,7 @@ public: /** * \brief Thrown if LMDBAL had issues creating or opening database directory */ -class Directory: public Exception { +class Directory : public Exception { public: /** * \brief Creates exception diff --git a/src/transaction.cpp b/src/transaction.cpp index 3d7e3c0..52e3933 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -50,7 +50,9 @@ LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : txn(txn), active(true), parent(parent) -{} +{ + parent->transactions[txn] = this; +} /** * \brief Moves transaction to a new object @@ -60,7 +62,11 @@ LMDBAL::Transaction::Transaction(Transaction&& other): active(other.active), parent(other.parent) { - other.active = false; + if (active) { + parent->transactions[txn] = this; + + other.reset(); + } } /** @@ -74,13 +80,20 @@ LMDBAL::Transaction::~Transaction() { * \brief Move-assigns transaction to the new object */ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { + if (this == &other) + return *this; + terminate(); txn = other.txn; active = other.active; parent = other.parent; - other.active = false; + if (active) { + parent->transactions[txn] = this; + + other.reset(); + } return *this; } @@ -93,10 +106,22 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { void LMDBAL::Transaction::terminate() { if (active) { parent->abortTransaction(txn); - active = false; + + parent->transactions.erase(txn); + reset(); } } +/** + * \brief Resets inner transaction properties to inactive state + */ +void LMDBAL::Transaction::reset() { + active = false; + txn = nullptr; + parent = nullptr; +} + + /** * \brief Returns transaction states * @@ -150,6 +175,12 @@ LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other): Transaction(std::move(other)) {} +LMDBAL::WriteTransaction& LMDBAL::WriteTransaction::operator=(WriteTransaction&& other) { + Transaction::operator=(std::move(other)); + + return *this; +} + /** * \brief Aborts transaction cancelling all changes * @@ -167,6 +198,8 @@ void LMDBAL::WriteTransaction::abort() { void LMDBAL::WriteTransaction::commit() { if (active) { const_cast(parent)->commitTransaction(txn); - active = false; + + parent->transactions.erase(txn); + reset(); } } diff --git a/src/transaction.h b/src/transaction.h index 59b6ae9..81acad6 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -39,6 +39,7 @@ public: protected: Transaction(TransactionID txn, const Base* parent); + void reset(); protected: TransactionID txn; /**<\brief Transaction inner handler*/ @@ -53,6 +54,7 @@ public: explicit WriteTransaction(WriteTransaction&& other); WriteTransaction(const WriteTransaction& other) = delete; WriteTransaction& operator = (const WriteTransaction& other) = delete; + WriteTransaction& operator = (WriteTransaction&& other); void commit(); void abort(); diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index e0bac76..0fc259d 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -279,3 +279,26 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { EXPECT_EQ(c1->getRecord(221), 14); } +TEST_F(CacheTransactionsTest, TransactionTerminationOnClose) { + LMDBAL::WriteTransaction txn = db->beginTransaction(); + + c1->addRecord(578, 4552, txn); + + EXPECT_EQ(c1->getRecord(578, txn), 4552); + EXPECT_EQ(c1->checkRecord(578), false); + + db->close(); + db->open(); + + EXPECT_EQ(txn.isActive(), false); + EXPECT_THROW(c1->getRecord(578, txn), LMDBAL::TransactionTerminated); + EXPECT_NO_THROW(txn.commit()); + + EXPECT_EQ(c1->checkRecord(578), false); + + txn = db->beginTransaction(); + c1->addRecord(578, 4552, txn); + txn.commit(); + + EXPECT_EQ(c1->getRecord(578), 4552); +} diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 4f04931..237a7c6 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -277,3 +277,27 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { std::cout << "checking the final result" << std::endl; EXPECT_EQ(t1->getRecord(221), 14); } + +TEST_F(StorageTransactionsTest, TransactionTerminationOnClose) { + LMDBAL::WriteTransaction txn = db->beginTransaction(); + + t1->addRecord(543, 229, txn); + + EXPECT_EQ(t1->getRecord(543, txn), 229); + EXPECT_EQ(t1->checkRecord(543), false); + + db->close(); + db->open(); + + EXPECT_EQ(txn.isActive(), false); + EXPECT_THROW(t1->getRecord(543, txn), LMDBAL::TransactionTerminated); + EXPECT_NO_THROW(txn.commit()); + + EXPECT_EQ(t1->checkRecord(543), false); + + txn = db->beginTransaction(); + t1->addRecord(543, 229, txn); + txn.commit(); + + EXPECT_EQ(t1->getRecord(543), 229); +} From e88efb458ffd8ef035837bdb71113f71d52614a4 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 24 Dec 2024 14:59:58 +0200 Subject: [PATCH 115/125] Cursors get closed after transaction that open them --- CHANGELOG.md | 1 + README.md | 5 +-- src/CMakeLists.txt | 1 + src/cursor.h | 5 +-- src/cursor.hpp | 80 +++++++++++++++++++++++++++++------------- src/icursor.h | 32 +++++++++++++++++ src/storage.cpp | 69 +++++++++++++++++++++++++++++++++++- src/storage.h | 38 +++++++++++--------- src/transaction.cpp | 23 +++++++++--- src/transaction.h | 9 +++-- test/cachecursor.cpp | 2 +- test/storagecursor.cpp | 16 ++++++++- 12 files changed, 225 insertions(+), 56 deletions(-) create mode 100644 src/icursor.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 608c121..356decc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Improvements - Less dereferencing - Transactions are now closed with the database +- Cursors are now closed if the public transaction that opened them got closed ### Bug fixes - SIGSEGV on closing database with opened transactions diff --git a/README.md b/README.md index 9c6a152..6046d65 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # LMDBAL - Lightning Memory Data Base Abstraction Level [![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) -[![AUR version](https://img.shields.io/aur/version/lmdbal?style=flat-square)](https://aur.archlinux.org/packages/lmdbal/) +[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt5/) +[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt6/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) [![Documentation](https://img.shields.io/badge/Documentation-HTML-green)](https://macaw.me/lmdbal/doc/html) ### Prerequisites -- a compiler (c++ would do) +- a c++ compiler (g++ would do) - Qt 5 or 6 or higher (qt5-base or qt6-base would do) - lmdb - CMake 3.16 or higher diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9332fd8..a717e80 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ set(HEADERS cache.hpp operators.hpp transaction.h + icursor.h ) target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES}) diff --git a/src/cursor.h b/src/cursor.h index c77d4e9..65a947d 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -24,11 +24,12 @@ #include "base.h" #include "storage.h" #include "transaction.h" +#include "icursor.h" namespace LMDBAL { template -class Cursor { +class Cursor : public iCursor { friend class Storage; private: enum State { /** /** @@ -84,6 +85,10 @@ LMDBAL::Cursor::Cursor(Cursor&& other): if (id != 0) storage->cursors[id] = this; + if (state == openedPublic) + storage->attachCursorToTransaction(id, cursor, this); + + other.freed(); } @@ -110,6 +115,9 @@ LMDBAL::Cursor& LMDBAL::Cursor::operator = (Cursor&& other) { other.state = closed; storage->cursors[id] = this; + + if (state == openedPublic) + storage->attachCursorToTransaction(id, cursor, this); } return *this; @@ -184,7 +192,20 @@ bool LMDBAL::Cursor::empty () const { */ template void LMDBAL::Cursor::terminated () { - close(); //for now it's the same, but if I ever going to make writable cursor - here is where it's gonna be different + switch (state) { + case openedPublic: + storage->_mdbCursorClose(cursor); + + state = closed; + break; + case openedPrivate: + storage->closeCursorTransaction(cursor); + + state = closed; + break; + default: + break; + } } /** @@ -205,17 +226,11 @@ void LMDBAL::Cursor::open () { if (empty()) throw CursorEmpty(openCursorMethodName); - storage->ensureOpened(openCursorMethodName); switch (state) { - case closed: { - TransactionID txn = storage->beginReadOnlyTransaction(); - int result = storage->_mdbCursorOpen(txn, &cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result, txn); - - storage->transactionStarted(txn, true); + case closed: + storage->openCursorTransaction(&cursor); state = openedPrivate; - } break; + break; default: break; } @@ -250,6 +265,7 @@ void LMDBAL::Cursor::open (const Transaction& transaction) { if (result != MDB_SUCCESS) storage->throwUnknown(result); + storage->attachCursorToTransaction(id, cursor, this); state = openedPublic; } break; default: @@ -285,15 +301,24 @@ void LMDBAL::Cursor::renew () { TransactionID txn = storage->_mdbCursorTxn(cursor); storage->abortTransaction(txn); storage->transactionAborted(txn); - [[fallthrough]]; + + txn = storage->beginReadOnlyTransaction(); + int result = storage->_mdbCursorRenew(txn, cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result, txn); + + storage->transactionStarted(txn, true); + state = openedPrivate; } case openedPublic: { + storage->disconnectCursorFromTransaction(id, cursor); TransactionID txn = storage->beginReadOnlyTransaction(); int result = storage->_mdbCursorRenew(txn, cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result, txn); storage->transactionStarted(txn, true); + storage->attachCursorToTransaction(id, cursor, this); state = openedPrivate; } break; default: @@ -330,17 +355,24 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) { TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); switch (state) { case openedPrivate: { - TransactionID txn = storage->_mdbCursorTxn(cursor); - storage->abortTransaction(txn); - storage->transactionAborted(txn); - [[fallthrough]]; - } - case openedPublic: { + TransactionID txnOld = storage->_mdbCursorTxn(cursor); + storage->abortTransaction(txnOld); + storage->transactionAborted(txnOld); + int result = storage->_mdbCursorRenew(txn, cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result); state = openedPublic; + } + case openedPublic: { + storage->disconnectCursorFromTransaction(id, cursor); + int result = storage->_mdbCursorRenew(txn, cursor); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, cursor, this); + state = openedPublic; } break; default: break; @@ -360,19 +392,17 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) { template void LMDBAL::Cursor::close () { switch (state) { - case openedPublic: { + case openedPublic: + storage->disconnectCursorFromTransaction(id, cursor); storage->_mdbCursorClose(cursor); state = closed; - } break; - case openedPrivate: { - TransactionID txn = storage->_mdbCursorTxn(cursor); - storage->_mdbCursorClose(cursor); - storage->abortTransaction(txn); - storage->transactionAborted(txn); + break; + case openedPrivate: + storage->closeCursorTransaction(cursor); state = closed; - } break; + break; default: break; } diff --git a/src/icursor.h b/src/icursor.h new file mode 100644 index 0000000..04bc811 --- /dev/null +++ b/src/icursor.h @@ -0,0 +1,32 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +namespace LMDBAL { + +class Transaction; + +class iCursor { + friend class Transaction; +protected: + virtual ~iCursor() {} + virtual void terminated() = 0; +}; + +} diff --git a/src/storage.cpp b/src/storage.cpp index cea474d..4847760 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -50,7 +50,7 @@ LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicate LMDBAL::iStorage::~iStorage() {} /** - * \brief A private virtual function I need to close each storage in the database + * \brief A private virtual function to close each storage in the database */ void LMDBAL::iStorage::close() { mdb_dbi_close(db->environment, dbi); @@ -159,6 +159,73 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { return amount; } +/** + * \brief Links cursor to the transaction it has been opened with + * + * This a service function is designed to be called by from LMDBAL::Cursor + * Cursor must be opened by a public transaction + * + * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base + */ + +void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const { + TransactionID txnID = _mdbCursorTxn(cursorHandle); + Transaction* txn = db->transactions.at(txnID); + txn->cursors[cursorId] = pointer; +} + + +/** + * \brief Opens a transaction that is ment to be private to LMDBAL::Cursor, but public to the LMDBAL::Storage + * + * This method is ment to be called from LMDBAL::Cursor + * + * \param[out] cursor - cursor handle + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + */ +void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor) const { + ensureOpened(openCursorTransactionMethodName); + + TransactionID txn = beginReadOnlyTransaction(); + int result = _mdbCursorOpen(txn, cursor); + if (result != MDB_SUCCESS) + throwUnknown(result, txn); + + transactionStarted(txn, true); +} + +/** + * \brief Closes transaction that is private to LMDBAL::Cursor, but public to the LMDBAL::Storage + * + * This method is ment to be called from LMDBAL::Cursor + * + * \param[in] cursor - cursor handle + */ +void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor) const { + TransactionID txn = _mdbCursorTxn(cursor); + _mdbCursorClose(cursor); + abortTransaction(txn); + transactionAborted(txn); +} + +/** + * \brief Disconnects cursor from the transaction it has been opened with + * + * This a service function is designed to be called by from LMDBAL::Cursor + * Cursor must be still opened by a public transaction + * + * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base + */ + +void LMDBAL::iStorage::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { + TransactionID txnID = _mdbCursorTxn(cursorHandle); + Transaction* txn = db->transactions.at(txnID); + txn->cursors.erase(cursorId); +} + + /** * \brief Storage size (private transaction variant) * diff --git a/src/storage.h b/src/storage.h index c077500..08c37ba 100644 --- a/src/storage.h +++ b/src/storage.h @@ -76,6 +76,11 @@ protected: virtual int drop(TransactionID transaction); virtual SizeType count(TransactionID txn) const; + void openCursorTransaction(MDB_cursor** cursor) const; + void closeCursorTransaction(MDB_cursor* cursor) const; + void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const; + void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; + int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); @@ -103,24 +108,25 @@ public: virtual SizeType count(const Transaction& txn) const; protected: - MDB_dbi dbi; /**<\brief lmdb storage handle*/ - Base* db; /**<\brief parent database pointer (borrowed)*/ - const std::string name; /**<\brief this storage name*/ - const bool duplicates; /**<\brief true if storage supports duplicates*/ + MDB_dbi dbi; /**<\brief lmdb storage handle*/ + Base* db; /**<\brief parent database pointer (borrowed)*/ + const std::string name; /**<\brief this storage name*/ + const bool duplicates; /**<\brief true if storage supports duplicates*/ - inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ - inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ - inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ + inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ + inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ + inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ - inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ - inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ + inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ + inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ + inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/ protected: template diff --git a/src/transaction.cpp b/src/transaction.cpp index 52e3933..d2e6a05 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -40,7 +40,8 @@ LMDBAL::Transaction::Transaction(): txn(nullptr), active(false), - parent(nullptr) + parent(nullptr), + cursors() {} /** @@ -49,7 +50,8 @@ LMDBAL::Transaction::Transaction(): LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : txn(txn), active(true), - parent(parent) + parent(parent), + cursors() { parent->transactions[txn] = this; } @@ -60,7 +62,8 @@ LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : LMDBAL::Transaction::Transaction(Transaction&& other): txn(other.txn), active(other.active), - parent(other.parent) + parent(other.parent), + cursors(other.cursors) { if (active) { parent->transactions[txn] = this; @@ -88,6 +91,7 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { txn = other.txn; active = other.active; parent = other.parent; + cursors = other.cursors; if (active) { parent->transactions[txn] = this; @@ -105,13 +109,14 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { */ void LMDBAL::Transaction::terminate() { if (active) { + closeCursors(); parent->abortTransaction(txn); - parent->transactions.erase(txn); reset(); } } + /** * \brief Resets inner transaction properties to inactive state */ @@ -119,8 +124,16 @@ void LMDBAL::Transaction::reset() { active = false; txn = nullptr; parent = nullptr; + cursors.clear(); } +/** + * \brief Closes attached curors; + */ +void LMDBAL::Transaction::closeCursors () { + for (const std::pair& pair : cursors) + pair.second->terminated(); +} /** * \brief Returns transaction states @@ -197,8 +210,8 @@ void LMDBAL::WriteTransaction::abort() { */ void LMDBAL::WriteTransaction::commit() { if (active) { + closeCursors(); const_cast(parent)->commitTransaction(txn); - parent->transactions.erase(txn); reset(); } diff --git a/src/transaction.h b/src/transaction.h index 81acad6..f37dab0 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -19,6 +19,7 @@ #pragma once #include "base.h" +#include "icursor.h" namespace LMDBAL { class iStorage; @@ -40,11 +41,13 @@ public: protected: Transaction(TransactionID txn, const Base* parent); void reset(); + void closeCursors(); protected: - TransactionID txn; /**<\brief Transaction inner handler*/ - bool active; /**<\brief Transaction state*/ - const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ + std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ }; class WriteTransaction : public Transaction { diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index 7d9d440..ec15b46 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -402,7 +402,7 @@ TEST_F(CacheCursorTest, CursorRAIIBehaviour) { TEST_F(CacheCursorTest, CornerCases) { transaction.terminate(); - EXPECT_THROW(cursor.current(), LMDBAL::Unknown); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); cursor.close(); LMDBAL::Cursor emptyCursor = emptyCache->createCursor(); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index dde8d82..7e332fb 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -380,7 +380,7 @@ TEST_F(StorageCursorTest, CursorRAIIBehaviour) { TEST_F(StorageCursorTest, CornerCases) { EXPECT_EQ(getTableCursorsSize(), 1); transaction.terminate(); - EXPECT_THROW(cursor.current(), LMDBAL::Unknown); + EXPECT_THROW(cursor.current(), LMDBAL::CursorNotReady); cursor.close(); LMDBAL::Cursor emptyCursor = emptyTable->createCursor(); @@ -414,4 +414,18 @@ TEST_F(StorageCursorTest, CornerCases) { EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); + + cursor.close(); +} + +TEST_F(StorageCursorTest, TerminatedTransaction) { + LMDBAL::Cursor emptyCursor = table->createCursor(); + + { + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); + cursor.open(txn); + EXPECT_NO_THROW(cursor.first()); + } + + EXPECT_FALSE(cursor.opened()); } From ef86d0adf97abc071f71106a187ca02a39a98e50 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 24 Dec 2024 18:35:56 +0200 Subject: [PATCH 116/125] Some reformation, test cases and first bugfixes --- src/cursor.hpp | 50 +++++++++++++----------------------------- src/storage.cpp | 21 +++++++++++++----- src/storage.h | 4 ++-- test/cachecursor.cpp | 35 +++++++++++++++++++++++++++++ test/storagecursor.cpp | 30 +++++++++++++++++++++---- 5 files changed, 93 insertions(+), 47 deletions(-) diff --git a/src/cursor.hpp b/src/cursor.hpp index 79fcff0..875b764 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -195,14 +195,12 @@ void LMDBAL::Cursor::terminated () { switch (state) { case openedPublic: storage->_mdbCursorClose(cursor); - state = closed; - break; + break; case openedPrivate: - storage->closeCursorTransaction(cursor); - + storage->closeCursorTransaction(cursor, true); state = closed; - break; + break; default: break; } @@ -228,7 +226,7 @@ void LMDBAL::Cursor::open () { switch (state) { case closed: - storage->openCursorTransaction(&cursor); + storage->openCursorTransaction(&cursor, false); state = openedPrivate; break; default: @@ -297,30 +295,15 @@ void LMDBAL::Cursor::renew () { storage->ensureOpened(renewCursorMethodName); switch (state) { - case openedPrivate: { - TransactionID txn = storage->_mdbCursorTxn(cursor); - storage->abortTransaction(txn); - storage->transactionAborted(txn); - - txn = storage->beginReadOnlyTransaction(); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result, txn); - - storage->transactionStarted(txn, true); - state = openedPrivate; - } - case openedPublic: { + case openedPrivate: + storage->closeCursorTransaction(cursor, false); + storage->openCursorTransaction(&cursor, true); + break; + case openedPublic: storage->disconnectCursorFromTransaction(id, cursor); - TransactionID txn = storage->beginReadOnlyTransaction(); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result, txn); - - storage->transactionStarted(txn, true); - storage->attachCursorToTransaction(id, cursor, this); + storage->openCursorTransaction(&cursor, true); state = openedPrivate; - } break; + break; default: break; } @@ -355,16 +338,14 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) { TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); switch (state) { case openedPrivate: { - TransactionID txnOld = storage->_mdbCursorTxn(cursor); - storage->abortTransaction(txnOld); - storage->transactionAborted(txnOld); - + storage->closeCursorTransaction(cursor, false); int result = storage->_mdbCursorRenew(txn, cursor); if (result != MDB_SUCCESS) storage->throwUnknown(result); + storage->attachCursorToTransaction(id, cursor, this); state = openedPublic; - } + } break; case openedPublic: { storage->disconnectCursorFromTransaction(id, cursor); int result = storage->_mdbCursorRenew(txn, cursor); @@ -372,7 +353,6 @@ void LMDBAL::Cursor::renew (const Transaction& transaction) { storage->throwUnknown(result); storage->attachCursorToTransaction(id, cursor, this); - state = openedPublic; } break; default: break; @@ -399,7 +379,7 @@ void LMDBAL::Cursor::close () { state = closed; break; case openedPrivate: - storage->closeCursorTransaction(cursor); + storage->closeCursorTransaction(cursor, true); state = closed; break; diff --git a/src/storage.cpp b/src/storage.cpp index 4847760..4359987 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -180,16 +180,22 @@ void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* * * This method is ment to be called from LMDBAL::Cursor * - * \param[out] cursor - cursor handle + * \param[out] cursor - cursor handle + * \param[in] renew - true if instead of opening cursor should be renewed * * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction */ -void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor) const { +void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor, bool renew) const { ensureOpened(openCursorTransactionMethodName); TransactionID txn = beginReadOnlyTransaction(); - int result = _mdbCursorOpen(txn, cursor); + int result; + if (renew) + result = _mdbCursorRenew(txn, *cursor); + else + result = _mdbCursorOpen(txn, cursor); + if (result != MDB_SUCCESS) throwUnknown(result, txn); @@ -201,11 +207,14 @@ void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor) const { * * This method is ment to be called from LMDBAL::Cursor * - * \param[in] cursor - cursor handle + * \param[in] cursor - cursor handle + * \param[in] closeCursor - true if the cursor should also get closed, false if you wish to leave it open */ -void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor) const { +void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const { TransactionID txn = _mdbCursorTxn(cursor); - _mdbCursorClose(cursor); + if (closeCursor) + _mdbCursorClose(cursor); + abortTransaction(txn); transactionAborted(txn); } diff --git a/src/storage.h b/src/storage.h index 08c37ba..99141e0 100644 --- a/src/storage.h +++ b/src/storage.h @@ -76,8 +76,8 @@ protected: virtual int drop(TransactionID transaction); virtual SizeType count(TransactionID txn) const; - void openCursorTransaction(MDB_cursor** cursor) const; - void closeCursorTransaction(MDB_cursor* cursor) const; + void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; + void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const; void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index ec15b46..b965c69 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -436,5 +436,40 @@ TEST_F(CacheCursorTest, CornerCases) { EXPECT_EQ(element.first, reference->first); EXPECT_EQ(element.second, reference->second); EXPECT_THROW(cursor.prev(), LMDBAL::NotFound); + + cursor.close(); } +TEST_F(CacheCursorTest, TerminatedTransaction) { + LMDBAL::Cursor cr = cache->createCursor(); + + { + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); + cr.open(txn); + EXPECT_NO_THROW(cr.first()); + } + + EXPECT_FALSE(cr.opened()); + + LMDBAL::Transaction txn2; + { + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); + EXPECT_TRUE(txn.isActive()); + EXPECT_FALSE(txn2.isActive()); + + cr.open(txn); + EXPECT_TRUE(cr.opened()); + + txn2 = std::move(txn); + EXPECT_FALSE(txn.isActive()); + EXPECT_TRUE(txn2.isActive()); + EXPECT_TRUE(cr.opened()); + } + + EXPECT_TRUE(txn2.isActive()); + EXPECT_TRUE(cr.opened()); + + txn2.terminate(); + EXPECT_FALSE(txn2.isActive()); + EXPECT_FALSE(cr.opened()); +} diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index 7e332fb..92279b1 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -419,13 +419,35 @@ TEST_F(StorageCursorTest, CornerCases) { } TEST_F(StorageCursorTest, TerminatedTransaction) { - LMDBAL::Cursor emptyCursor = table->createCursor(); + LMDBAL::Cursor cr = table->createCursor(); { LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); - cursor.open(txn); - EXPECT_NO_THROW(cursor.first()); + cr.open(txn); + EXPECT_NO_THROW(cr.first()); } - EXPECT_FALSE(cursor.opened()); + EXPECT_FALSE(cr.opened()); + + LMDBAL::Transaction txn2; + { + LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); + EXPECT_TRUE(txn.isActive()); + EXPECT_FALSE(txn2.isActive()); + + cr.open(txn); + EXPECT_TRUE(cr.opened()); + + txn2 = std::move(txn); + EXPECT_FALSE(txn.isActive()); + EXPECT_TRUE(txn2.isActive()); + EXPECT_TRUE(cr.opened()); + } + + EXPECT_TRUE(txn2.isActive()); + EXPECT_TRUE(cr.opened()); + + txn2.terminate(); + EXPECT_FALSE(txn2.isActive()); + EXPECT_FALSE(cr.opened()); } From bfb1d007ad8f07adbb4528fc564b9c62f0bc7ad3 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 25 Dec 2024 19:19:32 +0200 Subject: [PATCH 117/125] Cursors refactoring part one --- README.md | 4 +- src/CMakeLists.txt | 7 +- src/base.cpp | 20 +- src/base.h | 10 +- src/cache.hpp | 48 ++-- src/cursor.h | 45 +--- src/cursor.hpp | 333 ++---------------------- src/cursorcommon.cpp | 342 +++++++++++++++++++++++++ src/cursorcommon.h | 90 +++++++ src/icursor.h | 32 --- src/storage.h | 108 +------- src/storage.hpp | 84 +----- src/{storage.cpp => storagecommon.cpp} | 106 ++++---- src/storagecommon.h | 139 ++++++++++ src/storagecommon.hpp | 99 +++++++ src/transaction.cpp | 4 +- src/transaction.h | 14 +- test/cachetransaction.cpp | 8 +- test/storagetransaction.cpp | 8 +- 19 files changed, 824 insertions(+), 677 deletions(-) create mode 100644 src/cursorcommon.cpp create mode 100644 src/cursorcommon.h delete mode 100644 src/icursor.h rename src/{storage.cpp => storagecommon.cpp} (78%) create mode 100644 src/storagecommon.h create mode 100644 src/storagecommon.hpp diff --git a/README.md b/README.md index 6046d65..1226357 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # LMDBAL - Lightning Memory Data Base Abstraction Level [![AUR license](https://img.shields.io/aur/license/lmdbal?style=flat-square)](https://git.macaw.me/blue/lmdbal/raw/branch/master/LICENSE.md) -[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt5/) -[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square)](https://aur.archlinux.org/packages/lmdbal-qt6/) +[![AUR qt5 version](https://img.shields.io/aur/version/lmdbal-qt5?style=flat-square&label=lmdbal-qt5)](https://aur.archlinux.org/packages/lmdbal-qt5/) +[![AUR qt6 version](https://img.shields.io/aur/version/lmdbal-qt6?style=flat-square&label=lmdbal-qt6)](https://aur.archlinux.org/packages/lmdbal-qt6/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) [![Documentation](https://img.shields.io/badge/Documentation-HTML-green)](https://macaw.me/lmdbal/doc/html) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a717e80..9c53f6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,9 @@ set(SOURCES exceptions.cpp - storage.cpp + storagecommon.cpp base.cpp transaction.cpp + cursorcommon.cpp ) set(HEADERS @@ -10,13 +11,15 @@ set(HEADERS exceptions.h storage.h storage.hpp + storagecommon.h + storagecommon.hpp cursor.h cursor.hpp + cursorcommon.h cache.h cache.hpp operators.hpp transaction.h - icursor.h ) target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES}) diff --git a/src/base.cpp b/src/base.cpp index ce28975..cac914b 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -52,7 +52,7 @@ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): LMDBAL::Base::~Base() { close(); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) delete pair.second; } @@ -71,7 +71,7 @@ void LMDBAL::Base::close() { pair.second->reset(); } - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->close(); mdb_env_close(environment); @@ -99,8 +99,8 @@ void LMDBAL::Base::open() { mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); TransactionID txn = beginPrivateTransaction(emptyName); - for (const std::pair& pair : storages) { - iStorage* storage = pair.second; + for (const std::pair& pair : storages) { + StorageCommon* storage = pair.second; int rc = storage->open(txn); if (rc) throw Unknown(name, mdb_strerror(rc)); @@ -200,7 +200,7 @@ void LMDBAL::Base::drop() { throw Closed("drop", name); TransactionID txn = beginPrivateTransaction(emptyName); - for (const std::pair& pair : storages) { + for (const std::pair& pair : storages) { int rc = pair.second->drop(txn); if (rc != MDB_SUCCESS) { abortPrivateTransaction(txn, emptyName); @@ -209,7 +209,7 @@ void LMDBAL::Base::drop() { } commitPrivateTransaction(txn, emptyName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->handleDrop(); } @@ -306,7 +306,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionStarted(txn, true); return txn; @@ -331,7 +331,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionStarted(txn, false); return txn; @@ -356,7 +356,7 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& throw Closed("abortTransaction", name, storageName); abortPrivateTransaction(id, storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionAborted(id); } @@ -379,7 +379,7 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string throw Closed("abortTransaction", name, storageName); commitPrivateTransaction(id, storageName); - for (const std::pair& pair : storages) + for (const std::pair& pair : storages) pair.second->transactionCommited(id); } diff --git a/src/base.h b/src/base.h index de61ad7..a4b60a7 100644 --- a/src/base.h +++ b/src/base.h @@ -34,7 +34,7 @@ namespace LMDBAL { -class iStorage; +class StorageCommon; class Transaction; class WriteTransaction; @@ -51,7 +51,7 @@ typedef MDB_txn* TransactionID; /**<\brief I'm going to typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32_t*/ class Base { - friend class iStorage; + friend class StorageCommon; friend class Transaction; friend class WriteTransaction; public: @@ -84,7 +84,7 @@ public: LMDBAL::Cache* getCache(const std::string& storageName); private: - typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ + typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ typedef std::map Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ void commitTransaction(TransactionID id); @@ -136,7 +136,7 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, throw Opened(name, "add storage " + storageName); Storage* storage = new Storage(this, storageName, duplicates); - std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)storage)); + std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)storage)); if (!pair.second) throw StorageDuplicate(name, storageName); @@ -164,7 +164,7 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { throw Opened(name, "add cache " + storageName); Cache* cache = new Cache(this, storageName, false); - std::pair pair = storages.insert(std::make_pair(storageName, (iStorage*)cache)); + std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)cache)); if (!pair.second) throw StorageDuplicate(name, storageName); diff --git a/src/cache.hpp b/src/cache.hpp index 29af6d0..6dd8c54 100644 --- a/src/cache.hpp +++ b/src/cache.hpp @@ -63,10 +63,10 @@ LMDBAL::Cache::~Cache() { template void LMDBAL::Cache::addRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::addRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::addRecordMethodName); if (cache->count(key) > 0) - iStorage::throwDuplicate(iStorage::toString(key)); + StorageCommon::throwDuplicate(StorageCommon::toString(key)); Storage::addRecord(key, value); handleAddRecord(key, value); @@ -75,7 +75,7 @@ void LMDBAL::Cache::addRecord(const K& key, const V& value) { template void LMDBAL::Cache::addRecord(const K& key, const V& value, TransactionID txn) { if (cache->count(key) > 0) - iStorage::throwDuplicate(iStorage::toString(key)); + StorageCommon::throwDuplicate(StorageCommon::toString(key)); Storage::addRecord(key, value, txn); @@ -96,7 +96,7 @@ void LMDBAL::Cache::handleAddRecord(const K& key, const V& value) { template bool LMDBAL::Cache::forceRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::forceRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::forceRecordMethodName); bool added = Storage::forceRecord(key, value); handleForceRecord(key, value, added); @@ -136,18 +136,18 @@ void LMDBAL::Cache::handleForceRecord(const K& key, const V& value, bool a template void LMDBAL::Cache::changeRecord(const K& key, const V& value) { - iStorage::ensureOpened(iStorage::changeRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::changeRecordMethodName); if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::changeRecord(key, value); itr->second = value; } else { if (abscent->count(key) > 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::changeRecord(key, value); @@ -169,12 +169,12 @@ void LMDBAL::Cache::changeRecord(const K& key, const V& value, Transaction if (mode == Mode::full) { typename std::map::iterator itr = cache->find(key); if (itr == cache->end()) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::changeRecord(key, value, txn); } else { if (abscent->count(key) > 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::changeRecord(key, value, txn); @@ -207,7 +207,7 @@ void LMDBAL::Cache::handleChangeRecord(const K& key, const V& value) { template V LMDBAL::Cache::getRecord(const K& key) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::getRecordMethodName); V value; Cache::getRecord(key, value); @@ -216,7 +216,7 @@ V LMDBAL::Cache::getRecord(const K& key) const { template void LMDBAL::Cache::getRecord(const K& key, V& out) const { - iStorage::ensureOpened(iStorage::getRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::getRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) { @@ -225,7 +225,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out) const { } if (mode == Mode::full || abscent->count(key) != 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::getRecord(key, out); @@ -269,7 +269,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } break; case Operation::remove: - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); break; case Operation::change: if (static_cast*>(entry.second)->first == key) { @@ -285,7 +285,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } break; case Operation::drop: - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); break; case Operation::replace: { std::map* newMap = static_cast*>(entry.second); @@ -294,7 +294,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con out = vitr->second; return; } else { - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); } } break; @@ -322,7 +322,7 @@ void LMDBAL::Cache::getRecord(const K& key, V& out, TransactionID txn) con } if (mode == Mode::full || abscent->count(key) != 0) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); try { Storage::getRecord(key, out, txn); @@ -352,7 +352,7 @@ void LMDBAL::Cache::discoveredRecord(const K& key, const V& value, Transac template bool LMDBAL::Cache::checkRecord(const K& key) const { - iStorage::ensureOpened(iStorage::checkRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::checkRecordMethodName); typename std::map::const_iterator itr = cache->find(key); if (itr != cache->end()) @@ -457,7 +457,7 @@ void LMDBAL::Cache::appendToCache(const K& key, const V& value) const { template std::map LMDBAL::Cache::readAll() const { - iStorage::ensureOpened(iStorage::readAllMethodName); + StorageCommon::ensureOpened(StorageCommon::readAllMethodName); if (mode != Mode::full) { //there is a room for optimization mode = Mode::full; //I can read and deserialize only those values @@ -471,7 +471,7 @@ std::map LMDBAL::Cache::readAll() const { template void LMDBAL::Cache::readAll(std::map& out) const { - iStorage::ensureOpened(iStorage::readAllMethodName); + StorageCommon::ensureOpened(StorageCommon::readAllMethodName); if (mode != Mode::full) { //there is a room for optimization mode = Mode::full; //I can read and deserialize only those values @@ -642,7 +642,7 @@ void LMDBAL::Cache::handleAddRecords(const std::map& data, bool over template void LMDBAL::Cache::removeRecord(const K& key) { - iStorage::ensureOpened(iStorage::removeRecordMethodName); + StorageCommon::ensureOpened(StorageCommon::removeRecordMethodName); bool noKey = false; if (mode != Mode::full) @@ -651,7 +651,7 @@ void LMDBAL::Cache::removeRecord(const K& key) { noKey = abscent->count(key) > 0; if (noKey) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::removeRecord(key); handleRemoveRecord(key); @@ -666,7 +666,7 @@ void LMDBAL::Cache::removeRecord(const K& key, TransactionID txn) { noKey = abscent->count(key) > 0; if (noKey) - iStorage::throwNotFound(iStorage::toString(key)); + StorageCommon::throwNotFound(StorageCommon::toString(key)); Storage::removeRecord(key, txn); @@ -781,8 +781,8 @@ void LMDBAL::Cache::handleMode() const { template int LMDBAL::Cache::drop(const WriteTransaction& transaction) { - iStorage::ensureOpened(iStorage::dropMethodName); - TransactionID txn = iStorage::extractTransactionId(transaction, iStorage::dropMethodName); + StorageCommon::ensureOpened(StorageCommon::dropMethodName); + TransactionID txn = StorageCommon::extractTransactionId(transaction, StorageCommon::dropMethodName); int res = Storage::drop(txn); if (res != MDB_SUCCESS) diff --git a/src/cursor.h b/src/cursor.h index 65a947d..eef7cf1 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -24,20 +24,13 @@ #include "base.h" #include "storage.h" #include "transaction.h" -#include "icursor.h" +#include "cursorcommon.h" namespace LMDBAL { template -class Cursor : public iCursor { +class Cursor : public CursorCommon { friend class Storage; -private: - enum State { /*** parent); @@ -48,14 +41,6 @@ public: Cursor& operator = (const Cursor& other) = delete; Cursor& operator = (Cursor&& other); - void open(); - void open(const Transaction& transaction); - void renew(); - void renew(const Transaction& transaction); - void close(); - bool opened() const; - bool empty() const; - void drop(); std::pair first(); @@ -72,33 +57,7 @@ public: void current(K& key, V& value) const; private: - virtual void terminated() override; - void dropped(); - void freed(); void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; - -private: - Storage* storage; - MDB_cursor* cursor; - State state; - uint32_t id; - - inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ - inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ - inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions*/ - inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions*/ - inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/ - inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/ - inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/ - inline static const std::string setMethodName = "set"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/ - inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/ - inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions*/ - inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions*/ - inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions*/ }; }; diff --git a/src/cursor.hpp b/src/cursor.hpp index 875b764..ed68e8f 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -41,8 +41,6 @@ * You are not supposed to instantiate or destory instances of this class yourself! */ -static uint32_t idCounter = 0; - /** * \brief Creates a cursor * @@ -50,12 +48,9 @@ static uint32_t idCounter = 0; */ template LMDBAL::Cursor::Cursor(Storage* parent): - storage(parent), - cursor(nullptr), - state(closed), - id(++idCounter) + CursorCommon(parent) { - storage->cursors[id] = this; + parent->cursors[id] = this; } /** @@ -65,10 +60,7 @@ LMDBAL::Cursor::Cursor(Storage* parent): */ template LMDBAL::Cursor::Cursor(): - storage(nullptr), - cursor(nullptr), - state(closed), - id(0) + CursorCommon() {} /** @@ -76,64 +68,39 @@ LMDBAL::Cursor::Cursor(): */ template LMDBAL::Cursor::Cursor(Cursor&& other): - storage(other.storage), - cursor(other.cursor), - state(other.state), - id(other.id) + CursorCommon(std::move(other)) { - other.terminated(); - if (id != 0) - storage->cursors[id] = this; - - if (state == openedPublic) - storage->attachCursorToTransaction(id, cursor, this); - - - other.freed(); + if (!empty()) + static_cast*>(storage)->cursors[id] = this; } /** - * \brief A private function that turns cursor into an empty one + * \brief Move assignment operator * - * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid. - * Those cursors will become empty, and can't be used anymore + * Transfers other cursor into this one */ template LMDBAL::Cursor& LMDBAL::Cursor::operator = (Cursor&& other) { - terminated(); + if (!empty() && other.empty()) + static_cast*>(storage)->cursors.erase(id); - if (id != 0) - storage->cursors.erase(id); + CursorCommon::operator=(std::move(other)); - storage = other.storage; - cursor = other.cursor; - state = other.state; - id = other.id; - - if (id != 0) { - other.freed(); - other.state = closed; - - storage->cursors[id] = this; - - if (state == openedPublic) - storage->attachCursorToTransaction(id, cursor, this); - } + if (!empty()) + static_cast*>(storage)->cursors[id] = this; return *this; } /** - * \brief Destroys a cursor + * \brief Destroys this cursor * * If the cursor wasn't properly closed - it's going to be upon destruction */ template LMDBAL::Cursor::~Cursor () { - close(); - if (id != 0) - storage->cursors.erase(id); + static_cast*>(storage)->cursors.erase(id); } /** @@ -147,253 +114,9 @@ void LMDBAL::Cursor::drop () { close(); if (id != 0) - storage->cursors.erase(id); + static_cast*>(storage)->cursors.erase(id); - freed(); -} - -/** - * \brief A private method that turns cursor into an empty one - * - * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors. - * Those cursors will become empty, and can't be used anymore - */ -template -void LMDBAL::Cursor::dropped () { - terminated(); - freed(); -} - -/** - * \brief A private method that turns cursor into an empty one (submethod) - * - * This function is called from LMDBAL::Storage, when the cursor is getting destoryed. - * Those cursors will become empty, and can't be used anymore - */ -template -void LMDBAL::Cursor::freed () { - cursor = nullptr; - storage = nullptr; - id = 0; -} - -/** - * \brief Returns true if the cursor is empty - * - * Empty cursors can't be used, they can be only targets of move operations - */ -template -bool LMDBAL::Cursor::empty () const { - return id == 0; -} - -/** - * \brief A private function the storage owning this cursor will call to inform this cursor that the thansaction needs to be aborted - */ -template -void LMDBAL::Cursor::terminated () { - switch (state) { - case openedPublic: - storage->_mdbCursorClose(cursor); - state = closed; - break; - case openedPrivate: - storage->closeCursorTransaction(cursor, true); - state = closed; - break; - default: - break; - } -} - -/** - * \brief Opens the cursor for operations. - * - * This is a normal way to start the sequence of operations with the cursor. - * This variant of the function creates a read only transaction just for this cursor - * - * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! - * It will do nothing to a cursor that was already opened (no matter what way). - * - * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::open () { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - switch (state) { - case closed: - storage->openCursorTransaction(&cursor, false); - state = openedPrivate; - break; - default: - break; - } -} - -/** - * \brief Opens the cursor for operations. - * - * This is a normal way to start the sequence of operations with the cursor. - * This variant of the function uses for queries a transaction you have obtained somewhere else. - * - * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! - * It will do nothing to a cursor that was already opened (no matter what way). - * - * \param[in] transaction - a transaction, can be read only - * - * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb - * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::open (const Transaction& transaction) { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(openCursorMethodName); - TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); - switch (state) { - case closed: { - int result = storage->_mdbCursorOpen(txn, &cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - state = openedPublic; - } break; - default: - break; - } -} - -/** - * \brief Renews a cursor - * - * This function aborts current transaction if the cursor was opened with it's own transaction - * (does not mess up if the transaction was public), - * creates new private transaction and rebinds this cursor to it. - * - * Theoretically you could call this method if your public transaction was aborted (or commited) - * but you wish to continue to keep working with your cursor. - * Or if you just want to rebind your cursor to a new private transaction. - * - * This function does nothing if the cursor is closed - * - * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::renew () { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(renewCursorMethodName); - switch (state) { - case openedPrivate: - storage->closeCursorTransaction(cursor, false); - storage->openCursorTransaction(&cursor, true); - break; - case openedPublic: - storage->disconnectCursorFromTransaction(id, cursor); - storage->openCursorTransaction(&cursor, true); - state = openedPrivate; - break; - default: - break; - } -} - -/** - * \brief Renews a cursor - * - * This function aborts current transaction if the cursor was opened with it's own transaction - * (does not mess up if the transaction was public), - * and rebinds this cursor to a passed new transaction. - * - * Theoretically you could call this method if your previous public transaction was aborted (or commited) - * but you wish to continue to keep working with your cursor. - * Or if you just want to rebind your cursor to another public transaction. - * - * This function does nothing if the cursor is closed - * - * \param[in] transaction - a transaction you wish this cursor to be bound to - * - * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database - * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb - * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error - * \exception LMDBAL::CursorEmpty thrown if the cursor was empty - */ -template -void LMDBAL::Cursor::renew (const Transaction& transaction) { - if (empty()) - throw CursorEmpty(openCursorMethodName); - - storage->ensureOpened(renewCursorMethodName); - TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); - switch (state) { - case openedPrivate: { - storage->closeCursorTransaction(cursor, false); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - state = openedPublic; - } break; - case openedPublic: { - storage->disconnectCursorFromTransaction(id, cursor); - int result = storage->_mdbCursorRenew(txn, cursor); - if (result != MDB_SUCCESS) - storage->throwUnknown(result); - - storage->attachCursorToTransaction(id, cursor, this); - } break; - default: - break; - } -} - -/** - * \brief Termiates a sequence of operations with the cursor - * - * This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries. - * The state of the cursor is lost after calling this method, some inner resorce is freed. - * - * If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction. - * - * This function does nothing on a closed cursor. - */ -template -void LMDBAL::Cursor::close () { - switch (state) { - case openedPublic: - storage->disconnectCursorFromTransaction(id, cursor); - storage->_mdbCursorClose(cursor); - - state = closed; - break; - case openedPrivate: - storage->closeCursorTransaction(cursor, true); - - state = closed; - break; - default: - break; - } -} - -/** - * \brief Tells if the cursor is open - */ -template -bool LMDBAL::Cursor::opened () const { - return state != closed; + reset(); } /** @@ -614,16 +337,16 @@ std::pair LMDBAL::Cursor::current () const { template bool LMDBAL::Cursor::set (const K& key) { if (state == closed) - storage->throwCursorNotReady(setMethodName); + static_cast*>(storage)->throwCursorNotReady(setMethodName); - MDB_val mdbKey = storage->keySerializer.setData(key); - int result = storage->_mdbCursorSet(cursor, mdbKey); + MDB_val mdbKey = static_cast*>(storage)->keySerializer.setData(key); + int result = static_cast*>(storage)->_mdbCursorSet(handle, mdbKey); if (result == MDB_SUCCESS) return true; else if (result == MDB_NOTFOUND) return false; - storage->throwUnknown(result); + static_cast*>(storage)->throwUnknown(result); return false; //unreachable, just to suppress the warning } @@ -651,18 +374,18 @@ void LMDBAL::Cursor::operateCursorRead( const std::string& operationName ) const { if (state == closed) - storage->throwCursorNotReady(methodName); + static_cast*>(storage)->throwCursorNotReady(methodName); MDB_val mdbKey, mdbValue; - int result = storage->_mdbCursorGet(cursor, mdbKey, mdbValue, operation); + int result = static_cast*>(storage)->_mdbCursorGet(handle, mdbKey, mdbValue, operation); if (result != MDB_SUCCESS) - storage->throwNotFoundOrUnknown(result, operationName); + static_cast*>(storage)->throwNotFoundOrUnknown(result, operationName); - storage->keySerializer.deserialize(mdbKey, key); - storage->valueSerializer.deserialize(mdbValue, value); + static_cast*>(storage)->keySerializer.deserialize(mdbKey, key); + static_cast*>(storage)->valueSerializer.deserialize(mdbValue, value); if (state == openedPrivate) - storage->discoveredRecord(key, value); + static_cast*>(storage)->discoveredRecord(key, value); else - storage->discoveredRecord(key, value, storage->_mdbCursorTxn(cursor)); + static_cast*>(storage)->discoveredRecord(key, value, static_cast*>(storage)->_mdbCursorTxn(handle)); } diff --git a/src/cursorcommon.cpp b/src/cursorcommon.cpp new file mode 100644 index 0000000..bfa77f0 --- /dev/null +++ b/src/cursorcommon.cpp @@ -0,0 +1,342 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "cursorcommon.h" + +/** + * \class LMDBAL::CursorCommon + * \brief An object to manage cursor internals and state. + * + * Cursors are owned by the storage, they die with the storage. + * They also get closed if the storage is closed (if you close by the database for example) + * + * You can obtain an instance of this class calling LMDBAL::Storage::createCursor() + * and destory it calling LMDBAL::Storage::destoryCursor() at any time, LMDBAL::Base doesn't necessarily need to be opened. + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + +#include "storagecommon.h" + +static uint32_t idCounter = 0; + +/** + * \brief Creates a empty class + */ +LMDBAL::CursorCommon::CursorCommon (): + id(0), + state(closed), + handle(nullptr), + storage(nullptr) +{} + +/** + * \brief Creates a cursor + * + * \param[in] _storage a storage that created this cursor + */ +LMDBAL::CursorCommon::CursorCommon (StorageCommon* _storage): + id(++idCounter), + state(closed), + handle(nullptr), + storage(_storage) +{} + +/** + * \brief Moves other cursor into this class + * + * \param[in] other other instance that is being moved + */ +LMDBAL::CursorCommon::CursorCommon (CursorCommon&& other): + id(other.id), + state(other.state), + handle(other.handle), + storage(other.storage) +{ + other.dropped(); + + if (state == openedPublic) + storage->attachCursorToTransaction(id, handle, this); +} + +/** + * \brief Destroys this cursor + * + * If the cursor wasn't properly closed - it's going to be upon destruction + */ +LMDBAL::CursorCommon::~CursorCommon () noexcept { + close(); +} + +/** + * \brief Move assignment operator + * + * Transfers other cursor into this one + */ +LMDBAL::CursorCommon& LMDBAL::CursorCommon::operator = (CursorCommon&& other) { + terminated(); + + id = other.id; + state = other.state; + handle = other.handle; + storage = other.storage; + + other.reset(); + + if (state == openedPublic) + storage->attachCursorToTransaction(id, handle, this); + + return *this; +} + +/** + * \brief A private method that turns cursor into an empty one + * + * This method is called from LMDBAL::Storage, when the cursor is getting destoryed. + * After this method cursors will become empty, and can't be used anymore + */ +void LMDBAL::CursorCommon::reset () { + id = 0; + state = closed; + handle = nullptr; + storage = nullptr; +} + +/** + * \brief A private method that turns cursor into an empty one + * + * This function is called from LMDBAL::Storage, when it gets destroyed, but still has some valid cursors. + * Those cursors will become empty, and can't be used anymore + */ +void LMDBAL::CursorCommon::dropped () { + terminated(); + reset(); +} + +/** + * \brief A private function called to inform the cursor he has been terminated + * + * Is expected to be called from transaction, database, storage or move constructor + */ +void LMDBAL::CursorCommon::terminated () { + switch (state) { + case openedPublic: + storage->_mdbCursorClose(handle); + state = closed; + break; + case openedPrivate: + storage->closeCursorTransaction(handle, true); + state = closed; + break; + default: + break; + } +} + +/** + * \brief Termiates a sequence of operations with the cursor + * + * This is a normal way to tell that you're done with the cursor and don't want to continue the sequence of queries. + * The state of the cursor is lost after calling this method, some inner resorce is freed. + * + * If the cursor was opened with the private transaction - the owner storage will be notified of the aborted transaction. + * + * This function does nothing on a closed cursor. + */ +void LMDBAL::CursorCommon::close () { + switch (state) { + case openedPublic: + storage->disconnectCursorFromTransaction(id, handle); + storage->_mdbCursorClose(handle); + + state = closed; + break; + case openedPrivate: + storage->closeCursorTransaction(handle, true); + + state = closed; + break; + default: + break; + } +} + +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function creates a read only transaction just for this cursor + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::open () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + switch (state) { + case closed: + storage->openCursorTransaction(&handle, false); + state = openedPrivate; + break; + default: + break; + } +} + +/** + * \brief Opens the cursor for operations. + * + * This is a normal way to start the sequence of operations with the cursor. + * This variant of the function uses for queries a transaction you have obtained somewhere else. + * + * This function should be called when the LMDBAL::Storage is already opened and before any query with this cursor! + * It will do nothing to a cursor that was already opened (no matter what way). + * + * \param[in] transaction - a transaction, can be read only + * + * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::open (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(openCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, openCursorMethodName); + switch (state) { + case closed: { + int result = storage->_mdbCursorOpen(txn, &handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + state = openedPublic; + } break; + default: + break; + } +} + +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * creates new private transaction and rebinds this cursor to it. + * + * Theoretically you could call this method if your public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to a new private transaction. + * + * This function does nothing if the cursor is closed + * + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem beginning new transaction or if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::renew () { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(renewCursorMethodName); + switch (state) { + case openedPrivate: + storage->closeCursorTransaction(handle, false); + storage->openCursorTransaction(&handle, true); + break; + case openedPublic: + storage->disconnectCursorFromTransaction(id, handle); + storage->openCursorTransaction(&handle, true); + state = openedPrivate; + break; + default: + break; + } +} + +/** + * \brief Renews a cursor + * + * This function aborts current transaction if the cursor was opened with it's own transaction + * (does not mess up if the transaction was public), + * and rebinds this cursor to a passed new transaction. + * + * Theoretically you could call this method if your previous public transaction was aborted (or commited) + * but you wish to continue to keep working with your cursor. + * Or if you just want to rebind your cursor to another public transaction. + * + * This function does nothing if the cursor is closed + * + * \param[in] transaction - a transaction you wish this cursor to be bound to + * + * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database + * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb + * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error + * \exception LMDBAL::CursorEmpty thrown if the cursor was empty + */ +void LMDBAL::CursorCommon::renew (const Transaction& transaction) { + if (empty()) + throw CursorEmpty(openCursorMethodName); + + storage->ensureOpened(renewCursorMethodName); + TransactionID txn = storage->extractTransactionId(transaction, renewCursorMethodName); + switch (state) { + case openedPrivate: { + storage->closeCursorTransaction(handle, false); + int result = storage->_mdbCursorRenew(txn, handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + state = openedPublic; + } break; + case openedPublic: { + storage->disconnectCursorFromTransaction(id, handle); + int result = storage->_mdbCursorRenew(txn, handle); + if (result != MDB_SUCCESS) + storage->throwUnknown(result); + + storage->attachCursorToTransaction(id, handle, this); + } break; + default: + break; + } +} + +/** + * \brief Returns true if the cursor is empty + * + * Empty cursors can't be used, they can be only targets of move operations + */ +bool LMDBAL::CursorCommon::empty () const { + return id == 0; +} + +/** + * \brief Tells if the cursor is open + */ +bool LMDBAL::CursorCommon::opened () const { + return state != closed; +} diff --git a/src/cursorcommon.h b/src/cursorcommon.h new file mode 100644 index 0000000..fee4e8b --- /dev/null +++ b/src/cursorcommon.h @@ -0,0 +1,90 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include + +namespace LMDBAL { + +class Transaction; +class StorageCommon; + +class CursorCommon { + friend class Transaction; +protected: + enum State { /** - * - * 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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -namespace LMDBAL { - -class Transaction; - -class iCursor { - friend class Transaction; -protected: - virtual ~iCursor() {} - virtual void terminated() = 0; -}; - -} diff --git a/src/storage.h b/src/storage.h index 99141e0..844beb2 100644 --- a/src/storage.h +++ b/src/storage.h @@ -25,6 +25,7 @@ #include "serializer.h" #include "cursor.h" #include "transaction.h" +#include "storagecommon.h" class BaseTest; class DuplicatesTest; @@ -33,111 +34,8 @@ class StorageCursorTest; namespace LMDBAL { -class iStorage { - friend class Base; -public: -protected: - iStorage(Base* parent, const std::string& name, bool duplicates = false); - virtual ~iStorage(); - - /** - * \brief A private virtual function I need to open each storage in the database - * - * \param[in] transaction - lmdb transaction to call mdb_dbi_open - * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code - */ - virtual int open(MDB_txn * transaction) = 0; - virtual void close(); - virtual void handleDrop(); - - bool isDBOpened() const; - const std::string& dbName() const; - - void ensureOpened(const std::string& methodName) const; - void throwDuplicateOrUnknown(int rc, const std::string& key) const; - void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; - void throwNotFoundOrUnknown(int rc, const std::string& key) const; - void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; - void throwUnknown(int rc, TransactionID txn) const; - void throwUnknown(int rc) const; - void throwUnknown(const std::string& message) const; - void throwDuplicate(const std::string& key) const; - void throwNotFound(const std::string& key) const; - void throwCursorNotReady(const std::string& method) const; - - TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const; - TransactionID beginReadOnlyTransaction() const; - TransactionID beginTransaction() const; - void commitTransaction(TransactionID id); - void abortTransaction(TransactionID id) const; - virtual void transactionStarted(TransactionID txn, bool readOnly) const; - virtual void transactionCommited(TransactionID txn); - virtual void transactionAborted(TransactionID txn) const; - virtual int drop(TransactionID transaction); - virtual SizeType count(TransactionID txn) const; - - void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; - void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; - void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const; - void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; - - int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); - - int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); - int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const; - int _mdbDel(MDB_txn* txn, MDB_val& key); - int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data); - - int _mdbStat(MDB_txn* txn, MDB_stat& stat) const; - int _mdbFlags(MDB_txn* txn, uint32_t& flags) const; - - int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const; - void _mdbCursorClose(MDB_cursor* cursor) const; - int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const; - int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const; - int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0); - int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0); - - int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const; - MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const; - -public: - virtual void drop(); - virtual int drop(const WriteTransaction& txn); - virtual SizeType count() const; - virtual SizeType count(const Transaction& txn) const; - -protected: - MDB_dbi dbi; /**<\brief lmdb storage handle*/ - Base* db; /**<\brief parent database pointer (borrowed)*/ - const std::string name; /**<\brief this storage name*/ - const bool duplicates; /**<\brief true if storage supports duplicates*/ - - inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ - inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ - inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ - - inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ - inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ - inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ - inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/ - -protected: - template - int makeStorage(MDB_txn* transaction, bool duplicates = false); - - template - static std::string toString(const T& value); -}; - template -class Storage : public iStorage { +class Storage : public StorageCommon { friend class ::BaseTest; friend class ::DuplicatesTest; friend class ::CacheCursorTest; @@ -165,7 +63,7 @@ protected: virtual uint32_t addRecords(const std::map& data, TransactionID txn, bool overwrite = false); public: - using iStorage::drop; + using StorageCommon::drop; virtual void addRecord(const K& key, const V& value); virtual void addRecord(const K& key, const V& value, const WriteTransaction& txn); virtual bool forceRecord(const K& key, const V& value); //returns true if there was addition, false if change diff --git a/src/storage.hpp b/src/storage.hpp index e56e91a..7f89fc2 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -46,7 +46,7 @@ */ template LMDBAL::Storage::Storage(Base* parent, const std::string& name, bool duplicates): - iStorage(parent, name, duplicates), + StorageCommon(parent, name, duplicates), keySerializer(), valueSerializer(), cursors() @@ -1015,7 +1015,7 @@ void LMDBAL::Storage::close() { for (const std::pair*>& pair : cursors) pair.second->terminated(); - iStorage::close(); + StorageCommon::close(); } /** @@ -1051,7 +1051,7 @@ void LMDBAL::Storage::destroyCursor(LMDBAL::Cursor& cursor) { cursor.close(); cursors.erase(itr); - cursor.freed(); + cursor.reset(); } /** @@ -1103,81 +1103,3 @@ void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, Trans UNUSED(value); UNUSED(txn); } - -/** - * \brief A functiion to actually open MDB_dbi storage - * - * \tparam K type of keys in opening storage - * - * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! - * \param[in] duplicates - true if key duplicates are allowed (false by default) - * - * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code - * - * This is a way to optimise database using MDB_INTEGERKEY flag, - * when the key is actually kind of an integer - * This infrastructure also allowes us to customize mdb_dbi_open call in the future - */ -template -inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates) { - unsigned int flags = MDB_CREATE; - if constexpr (std::is_integral::value) - flags |= MDB_INTEGERKEY; - - if (duplicates) { - flags |= MDB_DUPSORT; - - if constexpr (std::is_scalar::value) - flags |= MDB_DUPFIXED; - - if constexpr ( - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode - flags |= MDB_INTEGERDUP; - } - - return _mdbOpen(transaction, flags); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * This function is mainly used in exceptions, to report which key was duplicated or not found. - * You can define your own specializations to this function in case std::to_string doesn't cover your case - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template -inline std::string LMDBAL::iStorage::toString(const T& value) { - return std::to_string(value); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * QString spectialization - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template<> -inline std::string LMDBAL::iStorage::toString(const QString& value) { - return value.toStdString(); -} - -/** - * \brief A method to cast a value (which can be a value or a key) to string. - * - * std::string spectialization - * - * \param[in] value a value that should be converted to string - * \returns a string presentation of value - */ -template<> -inline std::string LMDBAL::iStorage::toString(const std::string& value) { - return value; -} diff --git a/src/storage.cpp b/src/storagecommon.cpp similarity index 78% rename from src/storage.cpp rename to src/storagecommon.cpp index 4359987..298f48d 100644 --- a/src/storage.cpp +++ b/src/storagecommon.cpp @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -#include "storage.h" +#include "storagecommon.h" + +#include "cursorcommon.h" #define UNUSED(x) (void)(x) @@ -37,7 +39,7 @@ * \param[in] name - the name of the storage * \param[in] duplicates - true if key duplicates are allowed (false by default) */ -LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicates): +LMDBAL::StorageCommon::StorageCommon(Base* parent, const std::string& name, bool duplicates): dbi(), db(parent), name(name), @@ -47,12 +49,12 @@ LMDBAL::iStorage::iStorage(Base* parent, const std::string& name, bool duplicate /** * \brief Destroys a storage interface */ -LMDBAL::iStorage::~iStorage() {} +LMDBAL::StorageCommon::~StorageCommon () {} /** * \brief A private virtual function to close each storage in the database */ -void LMDBAL::iStorage::close() { +void LMDBAL::StorageCommon::close() { mdb_dbi_close(db->environment, dbi); } @@ -67,7 +69,7 @@ void LMDBAL::iStorage::close() { * * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ -LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& txn, const std::string& action) const { +LMDBAL::TransactionID LMDBAL::StorageCommon::extractTransactionId(const Transaction& txn, const std::string& action) const { if (!txn.isActive()) throw TransactionTerminated(db->name, name, action); @@ -82,11 +84,11 @@ LMDBAL::TransactionID LMDBAL::iStorage::extractTransactionId(const Transaction& * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ -void LMDBAL::iStorage::drop() { +void LMDBAL::StorageCommon::drop() { ensureOpened(dropMethodName); TransactionID txn = beginTransaction(); - int rc = iStorage::drop(txn); + int rc = StorageCommon::drop(txn); if (rc != MDB_SUCCESS) { abortTransaction(txn); throw Unknown(db->name, mdb_strerror(rc), name); @@ -104,7 +106,7 @@ void LMDBAL::iStorage::drop() { * \param[in] transaction - transaction ID, must be writable transaction! * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise */ -int LMDBAL::iStorage::drop(TransactionID transaction) { +int LMDBAL::StorageCommon::drop(TransactionID transaction) { return mdb_drop(transaction, dbi, 0); } @@ -118,7 +120,7 @@ int LMDBAL::iStorage::drop(TransactionID transaction) { * * \exception LMDBAL::TransactionTerminated thrown if the transaction was not active */ -int LMDBAL::iStorage::drop(const WriteTransaction& txn) { +int LMDBAL::StorageCommon::drop(const WriteTransaction& txn) { ensureOpened(dropMethodName); return drop(extractTransactionId(txn, dropMethodName)); } @@ -130,7 +132,7 @@ int LMDBAL::iStorage::drop(const WriteTransaction& txn) { * * \exception LMDBAL::Closed thrown if the database was closed */ -void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { +void LMDBAL::StorageCommon::ensureOpened(const std::string& methodName) const { if (!isDBOpened()) throw Closed(methodName, db->name, name); } @@ -143,7 +145,7 @@ void LMDBAL::iStorage::ensureOpened(const std::string& methodName) const { * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened */ -LMDBAL::SizeType LMDBAL::iStorage::count() const { +LMDBAL::SizeType LMDBAL::StorageCommon::count() const { ensureOpened(countMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -168,7 +170,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count() const { * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ -void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, iCursor* pointer) const { +void LMDBAL::StorageCommon::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const { TransactionID txnID = _mdbCursorTxn(cursorHandle); Transaction* txn = db->transactions.at(txnID); txn->cursors[cursorId] = pointer; @@ -186,7 +188,7 @@ void LMDBAL::iStorage::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* * \exception LMDBAL::Closed thrown if you try to open the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem opening the cursor by the lmdb, or to begin a transaction */ -void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor, bool renew) const { +void LMDBAL::StorageCommon::openCursorTransaction (MDB_cursor** cursor, bool renew) const { ensureOpened(openCursorTransactionMethodName); TransactionID txn = beginReadOnlyTransaction(); @@ -210,7 +212,7 @@ void LMDBAL::iStorage::openCursorTransaction (MDB_cursor** cursor, bool renew) c * \param[in] cursor - cursor handle * \param[in] closeCursor - true if the cursor should also get closed, false if you wish to leave it open */ -void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const { +void LMDBAL::StorageCommon::closeCursorTransaction (MDB_cursor* cursor, bool closeCursor) const { TransactionID txn = _mdbCursorTxn(cursor); if (closeCursor) _mdbCursorClose(cursor); @@ -228,7 +230,7 @@ void LMDBAL::iStorage::closeCursorTransaction (MDB_cursor* cursor, bool closeCur * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ -void LMDBAL::iStorage::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { +void LMDBAL::StorageCommon::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { TransactionID txnID = _mdbCursorTxn(cursorHandle); Transaction* txn = db->transactions.at(txnID); txn->cursors.erase(cursorId); @@ -243,7 +245,7 @@ void LMDBAL::iStorage::disconnectCursorFromTransaction (uint32_t cursorId, MDB_c * * \exception LMDBAL::Unknown thrown if something unexpected happened */ -LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { +LMDBAL::SizeType LMDBAL::StorageCommon::count(TransactionID txn) const { MDB_stat stat; int rc = mdb_stat(txn, dbi, &stat); if (rc != MDB_SUCCESS) @@ -262,7 +264,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(TransactionID txn) const { * \exception LMDBAL::Unknown thrown if something unexpected happened * \exception LMDBAL::TransactionTerminated thrown if the passed transaction not active, any action with it's inner ID is an error */ -LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const { +LMDBAL::SizeType LMDBAL::StorageCommon::count(const Transaction& txn) const { ensureOpened(countMethodName); return count(extractTransactionId(txn, countMethodName)); } @@ -279,7 +281,7 @@ LMDBAL::SizeType LMDBAL::iStorage::count(const Transaction& txn) const { * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST */ -void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { +void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const { abortTransaction(txn); throwDuplicateOrUnknown(rc, key); } @@ -296,7 +298,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, TransactionID txn, const * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND */ -void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { +void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, const std::string& key) const { abortTransaction(txn); throwNotFoundOrUnknown(rc, key); } @@ -312,7 +314,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, LMDBAL::TransactionID txn, * \exception LMDBAL::Exist thrown if rc == MDB_KEYEXIST * \exception LMDBAL::Unknown thrown if rc != MDB_KEYEXIST */ -void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) const { +void LMDBAL::StorageCommon::throwDuplicateOrUnknown(int rc, const std::string& key) const { if (rc == MDB_KEYEXIST) throwDuplicate(key); else @@ -330,7 +332,7 @@ void LMDBAL::iStorage::throwDuplicateOrUnknown(int rc, const std::string& key) c * \exception LMDBAL::NotFound thrown if rc == MDB_NOTFOUND * \exception LMDBAL::Unknown thrown if rc != MDB_NOTFOUND */ -void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) const { +void LMDBAL::StorageCommon::throwNotFoundOrUnknown(int rc, const std::string& key) const { if (rc == MDB_NOTFOUND) throwNotFound(key); else @@ -347,7 +349,7 @@ void LMDBAL::iStorage::throwNotFoundOrUnknown(int rc, const std::string& key) co * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { +void LMDBAL::StorageCommon::throwUnknown(int rc, LMDBAL::TransactionID txn) const { abortTransaction(txn); throwUnknown(rc); } @@ -359,7 +361,7 @@ void LMDBAL::iStorage::throwUnknown(int rc, LMDBAL::TransactionID txn) const { * * \returns database name */ -const std::string & LMDBAL::iStorage::dbName() const { +const std::string & LMDBAL::StorageCommon::dbName() const { return db->name;} /** @@ -369,7 +371,7 @@ const std::string & LMDBAL::iStorage::dbName() const { * * \returns true if database is ipened, false otherwise */ -bool LMDBAL::iStorage::isDBOpened() const { +bool LMDBAL::StorageCommon::isDBOpened() const { return db->opened;} /** @@ -381,7 +383,7 @@ bool LMDBAL::iStorage::isDBOpened() const { * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(int rc) const { +void LMDBAL::StorageCommon::throwUnknown(int rc) const { throw Unknown(db->name, mdb_strerror(rc), name);} /** @@ -393,7 +395,7 @@ void LMDBAL::iStorage::throwUnknown(int rc) const { * * \exception LMDBAL::Unknown thrown everytime */ -void LMDBAL::iStorage::throwUnknown(const std::string& message) const { +void LMDBAL::StorageCommon::throwUnknown(const std::string& message) const { throw Unknown(db->name, message, name);} /** @@ -405,7 +407,7 @@ void LMDBAL::iStorage::throwUnknown(const std::string& message) const { * * \exception LMDBAL::Exist thrown everytime */ -void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { +void LMDBAL::StorageCommon::throwDuplicate(const std::string& key) const { throw Exist(key, db->name, name);} /** @@ -417,7 +419,7 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { * * \exception LMDBAL::NotFound thrown everytime */ -void LMDBAL::iStorage::throwNotFound(const std::string& key) const { +void LMDBAL::StorageCommon::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} /** @@ -429,7 +431,7 @@ void LMDBAL::iStorage::throwNotFound(const std::string& key) const { * * \exception LMDBAL::CursorNotReady thrown everytime */ -void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { +void LMDBAL::StorageCommon::throwCursorNotReady(const std::string& method) const { throw CursorNotReady(method, db->name, name);} /** @@ -439,7 +441,7 @@ void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { * * \returns read only transaction */ -LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { +LMDBAL::TransactionID LMDBAL::StorageCommon::beginReadOnlyTransaction() const { return db->beginPrivateReadOnlyTransaction(name);} /** @@ -449,7 +451,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginReadOnlyTransaction() const { * * \returns read only transaction */ -LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { +LMDBAL::TransactionID LMDBAL::StorageCommon::beginTransaction() const { return db->beginPrivateTransaction(name);} /** @@ -457,7 +459,7 @@ LMDBAL::TransactionID LMDBAL::iStorage::beginTransaction() const { * * Ment to be called from heirs, name is reported to the database just to be displayed in std::exception::what() message */ -void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { +void LMDBAL::StorageCommon::abortTransaction(LMDBAL::TransactionID id) const { db->abortPrivateTransaction(id, name);} /** @@ -467,7 +469,7 @@ void LMDBAL::iStorage::abortTransaction(LMDBAL::TransactionID id) const { * * \exception LMDBAL::Unknown thrown if something unexpected happened */ -void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { +void LMDBAL::StorageCommon::commitTransaction(LMDBAL::TransactionID id) { db->commitPrivateTransaction(id, name);} /** @@ -482,7 +484,7 @@ void LMDBAL::iStorage::commitTransaction(LMDBAL::TransactionID id) { * \param[in] txn - ID of started transaction * \param[in] readOnly - true if transaction is read-only, false otherwise */ -void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { +void LMDBAL::StorageCommon::transactionStarted(LMDBAL::TransactionID txn, bool readOnly) const { UNUSED(txn); UNUSED(readOnly); } @@ -498,7 +500,7 @@ void LMDBAL::iStorage::transactionStarted(LMDBAL::TransactionID txn, bool readOn * * \param[in] txn - ID of started transaction */ -void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { +void LMDBAL::StorageCommon::transactionCommited(LMDBAL::TransactionID txn) { UNUSED(txn);} /** @@ -512,7 +514,7 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { * * \param[in] txn - ID of started transaction */ -void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { +void LMDBAL::StorageCommon::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} /** @@ -521,65 +523,65 @@ void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { * It's a protected method that is called to optimise drop process * after the transaction is commited. Used just for optimisations. */ -void LMDBAL::iStorage::handleDrop() {} +void LMDBAL::StorageCommon::handleDrop() {} -int LMDBAL::iStorage::_mdbOpen(MDB_txn *txn, unsigned int flags) { +int LMDBAL::StorageCommon::_mdbOpen(MDB_txn *txn, unsigned int flags) { return mdb_dbi_open(txn, name.c_str(), flags, &dbi); } -int LMDBAL::iStorage::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) { +int LMDBAL::StorageCommon::_mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags) { return mdb_put(txn, dbi, &key, &data, flags); } -int LMDBAL::iStorage::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const { +int LMDBAL::StorageCommon::_mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const { return mdb_get(txn, dbi, &key, &data); } -int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key) { +int LMDBAL::StorageCommon::_mdbDel(MDB_txn* txn, MDB_val& key) { return mdb_del(txn, dbi, &key, NULL); } -int LMDBAL::iStorage::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) { +int LMDBAL::StorageCommon::_mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data) { return mdb_del(txn, dbi, &key, &data); } -int LMDBAL::iStorage::_mdbStat(MDB_txn* txn, MDB_stat& stat) const { +int LMDBAL::StorageCommon::_mdbStat(MDB_txn* txn, MDB_stat& stat) const { return mdb_stat(txn, dbi, &stat); } -int LMDBAL::iStorage::_mdbFlags(MDB_txn* txn, uint32_t& flags) const { +int LMDBAL::StorageCommon::_mdbFlags(MDB_txn* txn, uint32_t& flags) const { return mdb_dbi_flags(txn, dbi, &flags); } -int LMDBAL::iStorage::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { +int LMDBAL::StorageCommon::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { return mdb_cursor_open(txn, dbi, cursor); } -void LMDBAL::iStorage::_mdbCursorClose(MDB_cursor *cursor) const { +void LMDBAL::StorageCommon::_mdbCursorClose(MDB_cursor *cursor) const { mdb_cursor_close(cursor); } -int LMDBAL::iStorage::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const { +int LMDBAL::StorageCommon::_mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const { return mdb_cursor_get(cursor, &key, &data, operation); } -int LMDBAL::iStorage::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const { +int LMDBAL::StorageCommon::_mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const { return mdb_cursor_get(cursor, &key, NULL, MDB_SET); } -int LMDBAL::iStorage::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) { +int LMDBAL::StorageCommon::_mdbCursorDel(MDB_cursor* cursor, unsigned int flags) { return mdb_cursor_del(cursor, flags); } -int LMDBAL::iStorage::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) { +int LMDBAL::StorageCommon::_mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags) { return mdb_cursor_put(cursor, &key, &data, flags); } -int LMDBAL::iStorage::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const { +int LMDBAL::StorageCommon::_mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const { return mdb_cursor_renew(txn, cursor); } -MDB_txn* LMDBAL::iStorage::_mdbCursorTxn(MDB_cursor* cursor) const { +MDB_txn* LMDBAL::StorageCommon::_mdbCursorTxn(MDB_cursor* cursor) const { return mdb_cursor_txn(cursor); } diff --git a/src/storagecommon.h b/src/storagecommon.h new file mode 100644 index 0000000..367ef62 --- /dev/null +++ b/src/storagecommon.h @@ -0,0 +1,139 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include + +#include "base.h" +#include "transaction.h" + +namespace LMDBAL { + +class CursorCommon; + +class StorageCommon { + friend class Base; + friend class CursorCommon; +public: +protected: + StorageCommon(Base* parent, const std::string& name, bool duplicates = false); + virtual ~ StorageCommon(); + + /** + * \brief A private virtual function I need to open each storage in the database + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + */ + virtual int open(MDB_txn * transaction) = 0; + virtual void close(); + virtual void handleDrop(); + + bool isDBOpened() const; + const std::string& dbName() const; + + void ensureOpened(const std::string& methodName) const; + void throwDuplicateOrUnknown(int rc, const std::string& key) const; + void throwDuplicateOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, const std::string& key) const; + void throwNotFoundOrUnknown(int rc, TransactionID txn, const std::string& key) const; + void throwUnknown(int rc, TransactionID txn) const; + void throwUnknown(int rc) const; + void throwUnknown(const std::string& message) const; + void throwDuplicate(const std::string& key) const; + void throwNotFound(const std::string& key) const; + void throwCursorNotReady(const std::string& method) const; + + TransactionID extractTransactionId(const Transaction& txn, const std::string& action = "") const; + TransactionID beginReadOnlyTransaction() const; + TransactionID beginTransaction() const; + void commitTransaction(TransactionID id); + void abortTransaction(TransactionID id) const; + virtual void transactionStarted(TransactionID txn, bool readOnly) const; + virtual void transactionCommited(TransactionID txn); + virtual void transactionAborted(TransactionID txn) const; + virtual int drop(TransactionID transaction); + virtual SizeType count(TransactionID txn) const; + + void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; + void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; + void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const; + void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; + + int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); + + int _mdbPut(MDB_txn* txn, MDB_val& key, MDB_val& data, unsigned int flags = 0); + int _mdbGet(MDB_txn* txn, MDB_val& key, MDB_val& data) const; + int _mdbDel(MDB_txn* txn, MDB_val& key); + int _mdbDel(MDB_txn* txn, MDB_val& key, MDB_val& data); + + int _mdbStat(MDB_txn* txn, MDB_stat& stat) const; + int _mdbFlags(MDB_txn* txn, uint32_t& flags) const; + + int _mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const; + void _mdbCursorClose(MDB_cursor* cursor) const; + int _mdbCursorGet(MDB_cursor* cursor, MDB_val& key, MDB_val& data, MDB_cursor_op operation) const; + int _mdbCursorSet(MDB_cursor* cursor, MDB_val& key) const; + int _mdbCursorDel(MDB_cursor* cursor, unsigned int flags = 0); + int _mdbCursorPut(MDB_cursor* cursor, MDB_val& key, MDB_val& data, unsigned int flags = 0); + + int _mdbCursorRenew(MDB_txn* txn, MDB_cursor* cursor) const; + MDB_txn* _mdbCursorTxn(MDB_cursor* cursor) const; + +public: + virtual void drop(); + virtual int drop(const WriteTransaction& txn); + virtual SizeType count() const; + virtual SizeType count(const Transaction& txn) const; + +protected: + MDB_dbi dbi; /**<\brief lmdb storage handle*/ + Base* db; /**<\brief parent database pointer (borrowed)*/ + const std::string name; /**<\brief this storage name*/ + const bool duplicates; /**<\brief true if storage supports duplicates*/ + + inline static const std::string dropMethodName = "drop"; /**<\brief member function name, just for exceptions*/ + inline static const std::string countMethodName = "count"; /**<\brief member function name, just for exceptions*/ + inline static const std::string flagsMethodName = "flags"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string addRecordMethodName = "addRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string forceRecordMethodName = "forceRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string changeRecordMethodName = "changeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string removeRecordMethodName = "removeRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string checkRecordMethodName = "checkRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string getRecordMethodName = "getRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string readAllMethodName = "readAllRecord"; /**<\brief member function name, just for exceptions*/ + inline static const std::string replaceAllMethodName = "replaceAll"; /**<\brief member function name, just for exceptions*/ + inline static const std::string addRecordsMethodName = "addRecords"; /**<\brief member function name, just for exceptions*/ + inline static const std::string openCursorTransactionMethodName = "openCursorTransaction"; /**<\brief member function name, just for exceptions*/ + +protected: + template + int makeStorage(MDB_txn* transaction, bool duplicates = false); + + template + static std::string toString(const T& value); +}; + +} + +#include "storagecommon.hpp" diff --git a/src/storagecommon.hpp b/src/storagecommon.hpp new file mode 100644 index 0000000..c32ae6f --- /dev/null +++ b/src/storagecommon.hpp @@ -0,0 +1,99 @@ +/* + * LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "storagecommon.h" + +/** + * \brief A functiion to actually open MDB_dbi storage + * + * \tparam K type of keys in opening storage + * + * \param[in] transaction - lmdb transaction to call mdb_dbi_open, must be a writable transaction! + * \param[in] duplicates - true if key duplicates are allowed (false by default) + * + * \returns MDB_SUCCESS if everything went smooth or MDB_ -like error code + * + * This is a way to optimise database using MDB_INTEGERKEY flag, + * when the key is actually kind of an integer + * This infrastructure also allowes us to customize mdb_dbi_open call in the future + */ +template +inline int LMDBAL::StorageCommon::makeStorage(MDB_txn* transaction, bool duplicates) { + unsigned int flags = MDB_CREATE; + if constexpr (std::is_integral::value) + flags |= MDB_INTEGERKEY; + + if (duplicates) { + flags |= MDB_DUPSORT; + + if constexpr (std::is_scalar::value) + flags |= MDB_DUPFIXED; + + if constexpr ( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + ) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode + flags |= MDB_INTEGERDUP; + } + + return _mdbOpen(transaction, flags); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * This function is mainly used in exceptions, to report which key was duplicated or not found. + * You can define your own specializations to this function in case std::to_string doesn't cover your case + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template +inline std::string LMDBAL::StorageCommon::toString(const T& value) { + return std::to_string(value); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * QString spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template<> +inline std::string LMDBAL::StorageCommon::toString(const QString& value) { + return value.toStdString(); +} + +/** + * \brief A method to cast a value (which can be a value or a key) to string. + * + * std::string spectialization + * + * \param[in] value a value that should be converted to string + * \returns a string presentation of value + */ +template<> +inline std::string LMDBAL::StorageCommon::toString(const std::string& value) { + return value; +} diff --git a/src/transaction.cpp b/src/transaction.cpp index d2e6a05..f92e7e8 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -18,6 +18,8 @@ #include "transaction.h" +#include "cursorcommon.h" + /** * \class LMDBAL::Transaction * \brief Public read only transaction @@ -131,7 +133,7 @@ void LMDBAL::Transaction::reset() { * \brief Closes attached curors; */ void LMDBAL::Transaction::closeCursors () { - for (const std::pair& pair : cursors) + for (const std::pair& pair : cursors) pair.second->terminated(); } diff --git a/src/transaction.h b/src/transaction.h index f37dab0..0fb28e3 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -19,14 +19,14 @@ #pragma once #include "base.h" -#include "icursor.h" namespace LMDBAL { -class iStorage; +class StorageCommon; +class CursorCommon; class Transaction { friend class Base; - friend class iStorage; + friend class StorageCommon; public: explicit Transaction(); explicit Transaction(Transaction&& other); @@ -44,10 +44,10 @@ protected: void closeCursors(); protected: - TransactionID txn; /**<\brief Transaction inner handler*/ - bool active; /**<\brief Transaction state*/ - const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ - std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ + std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ }; class WriteTransaction : public Transaction { diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index 0fc259d..a2c0e42 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -182,7 +182,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -208,7 +208,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "adding first transaction value" << std::endl; c1->addRecord(5, 812, txn1); @@ -235,7 +235,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -256,7 +256,7 @@ TEST_F(CacheTransactionsTest, RAIIResourceFree) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "parent thread woke up" << std::endl; std::cout << "adding value from parent thread" << std::endl; diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 237a7c6..d4b9089 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -181,7 +181,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining second transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -207,7 +207,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "adding first transaction value" << std::endl; t1->addRecord(5, 812, txn1); @@ -234,7 +234,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { int pid = fork(); if (pid == 0) { // I am the child - usleep(1); + usleep(5); std::cout << "beggining child transaction" << std::endl; LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause //and wait for the first transaction to get finished @@ -255,7 +255,7 @@ TEST_F(StorageTransactionsTest, RAIIResourceFree) { LMDBAL::WriteTransaction txn1 = db->beginTransaction(); std::cout << "putting parent thread to sleep for 5 ms" << std::endl; - usleep(5); + usleep(10); std::cout << "parent thread woke up" << std::endl; std::cout << "adding value from parent thread" << std::endl; From 3ae1fd15c0f4f753227d6fd5bafa4968c7310b92 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 5 Jan 2025 18:39:36 +0200 Subject: [PATCH 118/125] Some refactor --- src/cursorcommon.cpp | 40 ++++++++++++++++++++++++++++++++-------- src/cursorcommon.h | 4 ++++ src/storagecommon.cpp | 33 +++++++-------------------------- src/storagecommon.h | 4 ++-- src/transaction.h | 9 +++++---- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/cursorcommon.cpp b/src/cursorcommon.cpp index bfa77f0..b8f1564 100644 --- a/src/cursorcommon.cpp +++ b/src/cursorcommon.cpp @@ -71,7 +71,7 @@ LMDBAL::CursorCommon::CursorCommon (CursorCommon&& other): other.dropped(); if (state == openedPublic) - storage->attachCursorToTransaction(id, handle, this); + attachToTransaction(); } /** @@ -99,7 +99,7 @@ LMDBAL::CursorCommon& LMDBAL::CursorCommon::operator = (CursorCommon&& other) { other.reset(); if (state == openedPublic) - storage->attachCursorToTransaction(id, handle, this); + attachToTransaction(); return *this; } @@ -161,7 +161,7 @@ void LMDBAL::CursorCommon::terminated () { void LMDBAL::CursorCommon::close () { switch (state) { case openedPublic: - storage->disconnectCursorFromTransaction(id, handle); + disconnectFromTransaction(); storage->_mdbCursorClose(handle); state = closed; @@ -231,7 +231,7 @@ void LMDBAL::CursorCommon::open (const Transaction& transaction) { if (result != MDB_SUCCESS) storage->throwUnknown(result); - storage->attachCursorToTransaction(id, handle, this); + transaction.cursors[id] = this; state = openedPublic; } break; default: @@ -267,7 +267,7 @@ void LMDBAL::CursorCommon::renew () { storage->openCursorTransaction(&handle, true); break; case openedPublic: - storage->disconnectCursorFromTransaction(id, handle); + disconnectFromTransaction(); storage->openCursorTransaction(&handle, true); state = openedPrivate; break; @@ -309,16 +309,16 @@ void LMDBAL::CursorCommon::renew (const Transaction& transaction) { if (result != MDB_SUCCESS) storage->throwUnknown(result); - storage->attachCursorToTransaction(id, handle, this); + transaction.cursors[id] = this; state = openedPublic; } break; case openedPublic: { - storage->disconnectCursorFromTransaction(id, handle); + disconnectFromTransaction(); int result = storage->_mdbCursorRenew(txn, handle); if (result != MDB_SUCCESS) storage->throwUnknown(result); - storage->attachCursorToTransaction(id, handle, this); + transaction.cursors[id] = this; } break; default: break; @@ -340,3 +340,27 @@ bool LMDBAL::CursorCommon::empty () const { bool LMDBAL::CursorCommon::opened () const { return state != closed; } + +/** + * \brief Links cursor to the transaction it has been opened with + * + * Cursor must be opened by a public transaction + * + * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base + */ +void LMDBAL::CursorCommon::attachToTransaction () { + Transaction* txn = storage->getTransactionForCursor(handle); + txn->cursors[id] = this; +} + +/** + * \brief Disconnects cursor from the transaction it has been opened with + * + * Cursor must be still opened by a public transaction + * + * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base + */ +void LMDBAL::CursorCommon::disconnectFromTransaction () { + Transaction* txn = storage->getTransactionForCursor(handle); + txn->cursors.erase(id); +} diff --git a/src/cursorcommon.h b/src/cursorcommon.h index fee4e8b..bf9b312 100644 --- a/src/cursorcommon.h +++ b/src/cursorcommon.h @@ -61,6 +61,10 @@ protected: void dropped(); void reset(); +private: + void attachToTransaction(); + void disconnectFromTransaction(); + protected: uint32_t id; State state; diff --git a/src/storagecommon.cpp b/src/storagecommon.cpp index 298f48d..3d7e92c 100644 --- a/src/storagecommon.cpp +++ b/src/storagecommon.cpp @@ -162,21 +162,19 @@ LMDBAL::SizeType LMDBAL::StorageCommon::count() const { } /** - * \brief Links cursor to the transaction it has been opened with + * \brief Retrieves public transaction object for a cursor handle * - * This a service function is designed to be called by from LMDBAL::Cursor - * Cursor must be opened by a public transaction + * Cursor must be still opened by a public transaction + * + * \param[out] cursor - cursor handle * * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base */ - -void LMDBAL::StorageCommon::attachCursorToTransaction (uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const { - TransactionID txnID = _mdbCursorTxn(cursorHandle); - Transaction* txn = db->transactions.at(txnID); - txn->cursors[cursorId] = pointer; +LMDBAL::Transaction* LMDBAL::StorageCommon::getTransactionForCursor (MDB_cursor* cursor) const { + TransactionID txnID = _mdbCursorTxn(cursor); + return db->transactions.at(txnID); } - /** * \brief Opens a transaction that is ment to be private to LMDBAL::Cursor, but public to the LMDBAL::Storage * @@ -221,22 +219,6 @@ void LMDBAL::StorageCommon::closeCursorTransaction (MDB_cursor* cursor, bool clo transactionAborted(txn); } -/** - * \brief Disconnects cursor from the transaction it has been opened with - * - * This a service function is designed to be called by from LMDBAL::Cursor - * Cursor must be still opened by a public transaction - * - * \exception std::out_of_range thrown if LMDBAL::Transaction pointer wasn't found in the LMDBAL::Base - */ - -void LMDBAL::StorageCommon::disconnectCursorFromTransaction (uint32_t cursorId, MDB_cursor* cursorHandle) const { - TransactionID txnID = _mdbCursorTxn(cursorHandle); - Transaction* txn = db->transactions.at(txnID); - txn->cursors.erase(cursorId); -} - - /** * \brief Storage size (private transaction variant) * @@ -553,7 +535,6 @@ int LMDBAL::StorageCommon::_mdbFlags(MDB_txn* txn, uint32_t& flags) const { return mdb_dbi_flags(txn, dbi, &flags); } - int LMDBAL::StorageCommon::_mdbCursorOpen(MDB_txn* txn, MDB_cursor** cursor) const { return mdb_cursor_open(txn, dbi, cursor); } diff --git a/src/storagecommon.h b/src/storagecommon.h index 367ef62..77a56d2 100644 --- a/src/storagecommon.h +++ b/src/storagecommon.h @@ -74,10 +74,10 @@ protected: virtual int drop(TransactionID transaction); virtual SizeType count(TransactionID txn) const; + Transaction* getTransactionForCursor(MDB_cursor* cursor) const; + void openCursorTransaction(MDB_cursor** cursor, bool renew = false) const; void closeCursorTransaction(MDB_cursor* cursor, bool closeCursor = false) const; - void attachCursorToTransaction(uint32_t cursorId, MDB_cursor* cursorHandle, CursorCommon* pointer) const; - void disconnectCursorFromTransaction(uint32_t cursorId, MDB_cursor* cursorHandle) const; int _mdbOpen(MDB_txn* txn, unsigned int flags = 0); diff --git a/src/transaction.h b/src/transaction.h index 0fb28e3..4866f17 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -27,6 +27,7 @@ class CursorCommon; class Transaction { friend class Base; friend class StorageCommon; + friend class CursorCommon; public: explicit Transaction(); explicit Transaction(Transaction&& other); @@ -44,10 +45,10 @@ protected: void closeCursors(); protected: - TransactionID txn; /**<\brief Transaction inner handler*/ - bool active; /**<\brief Transaction state*/ - const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ - std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ + mutable std::map cursors; /**<\brief a collection of cursors curently opened under this transaction*/ }; class WriteTransaction : public Transaction { From 1585b8e4f571fb3c29457e23aa006a8201802aea Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 18:19:06 +0300 Subject: [PATCH 119/125] Fix typos, fix some warnings, added more compile options, moved to forgejo CI --- {.gitea => .forgejo}/workflows/aur.yml | 0 {.gitea => .forgejo}/workflows/main.yml | 10 +- {.gitea => .forgejo}/workflows/release.yml | 4 +- {.gitea => .forgejo}/workflows/test.yml | 4 +- CMakeLists.txt | 132 ++++++++++++++------- README.md | 35 +++--- doc/CMakeLists.txt | 16 +-- src/base.cpp | 23 ++-- src/base.h | 10 +- src/serializer/serializer.hpp | 4 +- src/serializer/serializer_qbytearray.hpp | 14 ++- src/serializer/serializer_qstring.hpp | 30 ++++- src/serializer/serializer_stdstring.hpp | 4 +- src/storagecommon.cpp | 36 +++--- src/storagecommon.h | 2 +- 15 files changed, 204 insertions(+), 120 deletions(-) rename {.gitea => .forgejo}/workflows/aur.yml (100%) rename {.gitea => .forgejo}/workflows/main.yml (78%) rename {.gitea => .forgejo}/workflows/release.yml (93%) rename {.gitea => .forgejo}/workflows/test.yml (82%) diff --git a/.gitea/workflows/aur.yml b/.forgejo/workflows/aur.yml similarity index 100% rename from .gitea/workflows/aur.yml rename to .forgejo/workflows/aur.yml diff --git a/.gitea/workflows/main.yml b/.forgejo/workflows/main.yml similarity index 78% rename from .gitea/workflows/main.yml rename to .forgejo/workflows/main.yml index ed68bb0..44dba97 100644 --- a/.gitea/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -1,4 +1,4 @@ -name: Main LMDBAL workfow +name: Main LMDBAL workflow run-name: ${{ gitea.actor }} is running LMDBAL main workflow on: push: @@ -8,13 +8,13 @@ on: jobs: test-qt5: name: Test LMDBAL with qt5 - uses: ./.gitea/workflows/test.yml + uses: ./.forgejo/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 test-qt6: name: Test LMDBAL with qt6 - uses: ./.gitea/workflows/test.yml + uses: ./.forgejo/workflows/test.yml with: flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 @@ -31,7 +31,7 @@ jobs: - name: Configure working-directory: ./build - run: cmake .. BUILD_DOC_HTML=True -D BUILD_DOC_XML=True -D BUILD_DOC_MAN=True -D BUILD_DOXYGEN_AWESOME=True + run: cmake .. LMDBAL_BUILD_DOC_HTML=True -D LMDBAL_BUILD_DOC_XML=True -D LMDBAL_BUILD_DOC_MAN=True -D LMDBAL_BUILD_DOXYGEN_AWESOME=True - name: Build working-directory: ./build @@ -39,7 +39,7 @@ jobs: - name: Copy docs via scp uses: appleboy/scp-action@master - # working-directory: ./build/doc //doesn't work + # working-directory: ./build/doc //doesn't work with: host: ${{ secrets.DOMAIN_ROOT }} username: ${{ secrets.DEPLOY_USER_NAME }} diff --git a/.gitea/workflows/release.yml b/.forgejo/workflows/release.yml similarity index 93% rename from .gitea/workflows/release.yml rename to .forgejo/workflows/release.yml index 733957a..1af4d7c 100644 --- a/.gitea/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: qt5: name: Release qt5 build to AUR - uses: ./.gitea/workflows/aur.yml + uses: ./.forgejo/workflows/aur.yml with: package-name: lmdbal-qt5 description: LMDB Abstraction Layer, qt5 version @@ -20,7 +20,7 @@ jobs: qt6: name: Release qt6 build to AUR - uses: ./.gitea/workflows/aur.yml + uses: ./.forgejo/workflows/aur.yml with: package-name: lmdbal-qt6 description: LMDB Abstraction Layer, qt6 version diff --git a/.gitea/workflows/test.yml b/.forgejo/workflows/test.yml similarity index 82% rename from .gitea/workflows/test.yml rename to .forgejo/workflows/test.yml index 922e361..a0f3867 100644 --- a/.gitea/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -14,7 +14,7 @@ on: jobs: test: - name: Building and rinning unit tests + name: Building and running unit tests runs-on: archlinux steps: - name: Check out repository code @@ -25,7 +25,7 @@ jobs: - name: Configure working-directory: ./build - run: cmake .. -D BUILD_TESTS=True -D QT_VERSION_MAJOR= ${{ inputs.flags }} + run: cmake .. -D LMDBAL_STRICT=True -D LMDBAL_BUILD_TESTS=True -D QT_VERSION_MAJOR= ${{ inputs.flags }} - name: Build working-directory: ./build diff --git a/CMakeLists.txt b/CMakeLists.txt index 97b21ff..325901b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 3.16) project(LMDBAL - VERSION 0.6.0 - DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" - LANGUAGES CXX + VERSION 0.6.0 + DESCRIPTION "LMDB (Lightning Memory-Mapped Database Manager) Abstraction Layer" + LANGUAGES CXX ) cmake_policy(SET CMP0076 NEW) @@ -11,12 +11,16 @@ cmake_policy(SET CMP0079 NEW) set(LMDBAL_NAME ${PROJECT_NAME} CACHE STRING "Override for library name and install path") -option(BUILD_STATIC "Builds library as static library" OFF) -option(BUILD_TESTS "Builds tests" OFF) -option(BUILD_DOC_MAN "Builds man page documentation" OFF) -option(BUILD_DOC_HTML "Builds html documentation" OFF) -option(BUILD_DOC_XML "Builds xml documentation" OFF) -option(BUILD_DOXYGEN_AWESOME "Builds documentation alternative style" OFF) +option(LMDBAL_BUILD_STATIC "Builds library as static library" OFF) +option(LMDBAL_BUILD_TESTS "Builds tests" OFF) +option(LMDBAL_BUILD_DOC_MAN "Builds man page documentation" OFF) +option(LMDBAL_BUILD_DOC_HTML "Builds html documentation" OFF) +option(LMDBAL_BUILD_DOC_XML "Builds xml documentation" OFF) +option(LMDBAL_BUILD_DOXYGEN_AWESOME "Builds documentation alternative style" OFF) +option(LMDBAL_STRICT "Builds with extra compiler warnings" OFF) +option(LMDBAL_ASAN "Enables Address Sanitizer" OFF) +option(LMDBAL_UBSAN "Enables Undefined Behavior Sanitizer" OFF) +option(LMDBAL_TSAN "Enables Thread Sanitizer" OFF) include(GNUInstallDirs) include(CMakePackageConfigHelpers) @@ -29,40 +33,80 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") string(TOLOWER ${LMDBAL_NAME} LMDBAL_NAME_LOW) if (NOT DEFINED QT_VERSION_MAJOR) - find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) -endif() + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +endif () find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) find_package(LMDB REQUIRED) # Build type if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) + set(CMAKE_BUILD_TYPE Debug) endif () -if (BUILD_STATIC) - add_library(${LMDBAL_NAME} STATIC) +if (LMDBAL_BUILD_STATIC) + add_library(${LMDBAL_NAME} STATIC) else () - add_library(${LMDBAL_NAME} SHARED) + add_library(${LMDBAL_NAME} SHARED) endif() -if (CMAKE_BUILD_TYPE STREQUAL "Release") - list(APPEND COMPILE_OPTIONS -O3) -elseif (CMAKE_BUILD_TYPE STREQUAL "Debug") - list(APPEND COMPILE_OPTIONS -g) - list(APPEND COMPILE_OPTIONS -Wall) - list(APPEND COMPILE_OPTIONS -Wextra) -endif() + +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + list(APPEND COMPILE_OPTIONS -Wall) + list(APPEND COMPILE_OPTIONS -Wextra) + list(APPEND COMPILE_OPTIONS -Wpedantic) + + if (CMAKE_BUILD_TYPE STREQUAL "Release") + list(APPEND COMPILE_OPTIONS -O3) + list(APPEND COMPILE_OPTIONS -DNDEBUG) + elseif (CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND COMPILE_OPTIONS -O0) + list(APPEND COMPILE_OPTIONS -g3) + list(APPEND COMPILE_OPTIONS -ggdb) + endif () + + if (LMDBAL_STRICT) + list(APPEND COMPILE_OPTIONS -Wall) + list(APPEND COMPILE_OPTIONS -Werror) + list(APPEND COMPILE_OPTIONS -Wconversion) + list(APPEND COMPILE_OPTIONS -Wnon-virtual-dtor) + list(APPEND COMPILE_OPTIONS -Wold-style-cast) + list(APPEND COMPILE_OPTIONS -Wcast-align) + list(APPEND COMPILE_OPTIONS -Wunused) + list(APPEND COMPILE_OPTIONS -Woverloaded-virtual) + list(APPEND COMPILE_OPTIONS -Wsign-conversion) + list(APPEND COMPILE_OPTIONS -Wnull-dereference) + endif () + + if(JAY_ENABLE_ASAN) + list(APPEND COMPILE_OPTIONS -fsanitize=address) + list(APPEND COMPILE_OPTIONS -fno-omit-frame-pointer) + list(APPEND LINK_OPTIONS -fsanitize=address) + add_link_options() + endif() + + if(JAY_ENABLE_UBSAN) + list(APPEND COMPILE_OPTIONS -fsanitize=undefined) + list(APPEND LINK_OPTIONS -fsanitize=undefined) + endif() + + if(JAY_ENABLE_TSAN) + list(APPEND COMPILE_OPTIONS -fsanitize=thread) + list(APPEND LINK_OPTIONS -fsanitize=thread) + endif() +endif () message("Compilation options: " ${COMPILE_OPTIONS}) +message("Linking options: " ${LINK_OPTIONS}) target_compile_options(${LMDBAL_NAME} PRIVATE ${COMPILE_OPTIONS}) +target_link_options(${LMDBAL_NAME} PRIVATE ${LINK_OPTIONS}) set_property(TARGET ${LMDBAL_NAME} PROPERTY VERSION ${version}) set_property(TARGET ${LMDBAL_NAME} PROPERTY SOVERSION 1) set_property(TARGET ${LMDBAL_NAME} PROPERTY EXPORT_NAME ${LMDBAL_NAME}) set_property(TARGET ${LMDBAL_NAME} PROPERTY INTERFACE_${LMDBAL_NAME}_MAJOR_VERSION 1) set_property(TARGET ${LMDBAL_NAME} APPEND PROPERTY - COMPATIBLE_INTERFACE_STRING ${LMDBAL_NAME}_MAJOR_VERSION + COMPATIBLE_INTERFACE_STRING ${LMDBAL_NAME}_MAJOR_VERSION ) if (UNIX) @@ -70,36 +114,36 @@ if (UNIX) set_property(TARGET ${LMDBAL_NAME} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) set_property(TARGET ${LMDBAL_NAME} PROPERTY SKIP_BUILD_RPATH FALSE) set_property(TARGET ${LMDBAL_NAME} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) -endif() +endif () add_subdirectory(src) -if (BUILD_DOC_MAN OR BUILD_DOC_HTML OR BUILD_DOC_XML) - find_package(Doxygen) - if (DOXYGEN_FOUND) - add_subdirectory(doc) - else() - message("Was trying to build documentation, but Doxygen was not found, skipping documentation") - endif() -endif() +if (LMDBAL_BUILD_DOC_MAN OR LMDBAL_BUILD_DOC_HTML OR LMDBAL_BUILD_DOC_XML) + find_package(Doxygen) + if (DOXYGEN_FOUND) + add_subdirectory(doc) + else () + message("Was trying to build documentation, but Doxygen was not found, skipping documentation") + endif () +endif () -if (BUILD_TESTS) - add_subdirectory(test) +if (LMDBAL_BUILD_TESTS) + add_subdirectory(test) endif () target_include_directories( - ${LMDBAL_NAME} - PUBLIC - $ - $ - $ + ${LMDBAL_NAME} + PUBLIC + $ + $ + $ ) target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) target_include_directories(${LMDBAL_NAME} PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) target_link_libraries( - ${LMDBAL_NAME} - Qt${QT_VERSION_MAJOR}::Core - lmdb + ${LMDBAL_NAME} + Qt${QT_VERSION_MAJOR}::Core + lmdb ) configure_package_config_file( @@ -127,7 +171,7 @@ install(EXPORT ${LMDBAL_NAME_LOW}Targets ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${LMDBAL_NAME_LOW}ConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LMDBAL_NAME_LOW} ) diff --git a/README.md b/README.md index 1226357..3c5d671 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ #### As a system library -If you're using LMDBAL as a system library you probably have no control over it's build options. The easiest way to include the project is to add following +If you're using LMDBAL as a system library, you probably have no control over its build options. +The easiest way to include the project is to add the following ``` find_package(lmdbal) @@ -29,14 +30,14 @@ if (LMDBAL_FOUND) endif() ``` -#### As an embeded subproject +#### As an embedded subproject -If you're using LMDBAL as a embeded library you might want to control it's build options, for example you can run +If you're using LMDBAL as a embedded library, you might want to control its build options, for example, you can run ``` -set(BUILD_STATIC ON) +set(LMDBAL_BUILD_STATIC ON) ``` -before including the library in your project. This will set the library to be build in a static mode. +... before including the library in your project. This will set the library to be build in a static mode. Then you want to run something like this ``` @@ -67,9 +68,11 @@ $ cmake --build . $ cmake --install . --prefix install ``` -This way will create you a `lmdbal/build` directory with temporary files, and `lmdbal/build/install` with all the export files for installation to the system. +This way will create you a `lmdbal/build` directory with temporary files, and `lmdbal/build/install` +with all the export files for installation to the system. -After `cmake ..` you can specify keys to alter the building process. In this context building keys are transfered like so +After `cmake ..` you can specify keys to alter the building process. +In this context building keys are transferred like so ``` cmake .. -D KEY1=VALUE1 -D KEY2=VALUE2 ... @@ -80,22 +83,26 @@ cmake .. -D KEY1=VALUE1 -D KEY2=VALUE2 ... Here is the list of keys you can pass to configuration phase of `cmake ..`: - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`); -- `BUILD_STATIC` - `True` builds project as a static library, `False` builds as dynamic (default is `False`); -- `BUILD_TESTS` - `True` build unit tests, `False` does not (default is `False`); +- `LMDBAL_BUILD_STATIC` - `True` builds project as a static library, `False` builds as dynamic (default is `False`); +- `LMDBAL_BUILD_TESTS` - `True` build unit tests, `False` does not (default is `False`); - `BUILD_DOC` - `True` build doxygen documentation, `False` does not (default is `False`); -- `BUILD_DOXYGEN_AWESOME` - `True` build doxygen awesome theme if `BUILD_DOC` is also `True` (default is `False`); -- `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links agains Qt6, there is no default, so, if you didn't specify it the project will chose automatically; -- `LMDBAL_NAME` - `LMDBAL` builds main target with this name, also install path witll be this name lowercase, usefull if you want to build `LMDBAL-QT6` not to conflict with `LMDBAL-QT5`; +- `LMDBAL_BUILD_DOXYGEN_AWESOME` - `True` build doxygen awesome theme if `BUILD_DOC` is also `True` (default is `False`); +- `QT_VERSION_MAJOR` - `5` links against Qt5, `6` links against Qt6, there is no default, so, if you didn't specify it, the project will choose automatically; +- `LMDBAL_NAME` - `LMDBAL` builds the main target with this name, also the installation path will be this name lowercase, useful if you want to build `LMDBAL-QT6` not to conflict with `LMDBAL-QT5`; +- `LMDBAL_STRICT` - `True` builds with extra warnings and considers them errors (default is `False`); +- `LMDBAL_ASAN` - `True` builds with address sanitizer (default is `False`); +- `LMDBAL_TSAN` - `True` builds with thread sanitizer (default is `False`); +- `LMDBAL_UBSAN` - `True` builds with undefined behavior sanitizer (default is `False`); #### Running tests -If you built the library with `-D BUILD_TESTS=True`, then there will be `lmdbal/build/tests/runUnitTests` executable file. You can simply run it as +If you built the library with `-D LMDBAL_BUILD_TESTS=True`, then there will be `lmdbal/build/tests/runUnitTests` executable file. You can run it as ``` ./runUnitTests ``` -if you're in the same directory with it +... if you're in the same directory with it ## License diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 0757340..66f3b76 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,14 +1,14 @@ -if (BUILD_DOC_HTML) +if (LMDBAL_BUILD_DOC_HTML) set(DOXYGEN_GENERATE_HTML YES) endif() -if (BUILD_DOC_MAN) +if (LMDBAL_BUILD_DOC_MAN) set(DOXYGEN_GENERATE_MAN YES) endif() -if (BUILD_DOC_XML) +if (LMDBAL_BUILD_DOC_XML) set(DOXYGEN_GENERATE_XML YES) endif() -if (BUILD_DOXYGEN_AWESOME) +if (LMDBAL_BUILD_DOXYGEN_AWESOME) include(ExternalProject) ExternalProject_Add(doxygen-awesome-css GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css.git @@ -42,25 +42,25 @@ doxygen_add_docs( ALL COMMENT "Generate man and html pages" ) -if (BUILD_DOC_MAN) +if (LMDBAL_BUILD_DOC_MAN) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man TYPE DOC ) endif() -if (BUILD_DOC_HTML) +if (LMDBAL_BUILD_DOC_HTML) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html TYPE DOC ) endif() -if (BUILD_DOC_XML) +if (LMDBAL_BUILD_DOC_XML) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xml TYPE DOC ) endif() -if (BUILD_DOXYGEN_AWESOME) +if (LMDBAL_BUILD_DOXYGEN_AWESOME) add_dependencies(documentation doxygen-awesome-css) endif() \ No newline at end of file diff --git a/src/base.cpp b/src/base.cpp index cac914b..5100c83 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -28,13 +28,13 @@ * \brief Database abstraction * * This is a basic class that represents the database as a collection of storages. - * Storages is something key-value database has instead of tables in classic SQL databases. + * Storages are something that key-value databases have instead of tables in classic SQL databases. */ /** * \brief Creates the database * - * \param[in] _name - name of the database, it is going to affect folder name that is created to store data + * \param[in] _name - name of the database, it is going to affect the folder name that is created to store data * \param[in] _mapSize - LMDB map size (MiB), multiplied by 1024^2 and passed to mdb_env_set_mapsize during the call of LMDBAL::Base::open() */ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): @@ -60,13 +60,13 @@ LMDBAL::Base::~Base() { * \brief Closes the database * * Closes all lmdb handles, aborts all public transactions. - * This function will do nothing on closed database + * This function will do nothing on a closed database * * \exception LMDBAL::Unknown - thrown if something went wrong aborting transactions */ void LMDBAL::Base::close() { if (opened) { - for (const std::pair pair : transactions) { + for (const std::pair pair : transactions) { abortTransaction(pair.first, emptyName); pair.second->reset(); } @@ -84,8 +84,8 @@ void LMDBAL::Base::close() { * \brief Opens the database * * Almost every LMDBAL::Base require it to be opened, this function does it. - * It laso creates the directory for the database if it was an initial launch. - * This function will do nothing on opened database + * It also creates the directory for the database if it was an initial launch. + * This function will do nothing on an opened database * * \exception LMDBAL::Unknown - thrown if something went wrong opening storages and caches */ @@ -94,15 +94,14 @@ void LMDBAL::Base::open() { mdb_env_create(&environment); QString path = createDirectory(); - mdb_env_set_maxdbs(environment, storages.size()); + mdb_env_set_maxdbs(environment, static_cast(storages.size())); mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); TransactionID txn = beginPrivateTransaction(emptyName); for (const std::pair& pair : storages) { StorageCommon* storage = pair.second; - int rc = storage->open(txn); - if (rc) + if (const int rc = storage->open(txn)) throw Unknown(name, mdb_strerror(rc)); } commitPrivateTransaction(txn, emptyName); @@ -113,9 +112,9 @@ void LMDBAL::Base::open() { /** * \brief Removes database directory * - * \returns true if removal was successfull of if no directory was created where it's expected to be, false otherwise + * \returns true if removal was successful of if no directory was created where it's expected to be, false otherwise * - * \exception LMDBAL::Opened - thrown if this function was called on opened database + * \exception LMDBAL::Opened - thrown if this function was called on an opened database */ bool LMDBAL::Base::removeDirectory() { if (opened) @@ -141,7 +140,7 @@ bool LMDBAL::Base::removeDirectory() { * * \returns the path of the created directory * - * \exception LMDBAL::Opened - thrown if called on opened database + * \exception LMDBAL::Opened - thrown if called on an opened database * \exception LMDBAL::Directory - if the database couldn't create the folder */ QString LMDBAL::Base::createDirectory() { diff --git a/src/base.h b/src/base.h index a4b60a7..4ccf45d 100644 --- a/src/base.h +++ b/src/base.h @@ -48,7 +48,7 @@ template class Cache; typedef MDB_txn* TransactionID; /**<\brief I'm going to use transaction pointers as transaction IDs*/ -typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32_t*/ +typedef uint32_t SizeType; /**<\brief All LMDBAL sizes are uint32*/ class Base { friend class StorageCommon; @@ -135,8 +135,8 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, if (opened) throw Opened(name, "add storage " + storageName); - Storage* storage = new Storage(this, storageName, duplicates); - std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)storage)); + auto storage = new Storage(this, storageName, duplicates); + std::pair pair = storages.emplace(storageName, static_cast(storage)); if (!pair.second) throw StorageDuplicate(name, storageName); @@ -163,8 +163,8 @@ LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { if (opened) throw Opened(name, "add cache " + storageName); - Cache* cache = new Cache(this, storageName, false); - std::pair pair = storages.insert(std::make_pair(storageName, (StorageCommon *)cache)); + auto cache = new Cache(this, storageName, false); + std::pair pair = storages.emplace(storageName, static_cast(cache)); if (!pair.second) throw StorageDuplicate(name, storageName); diff --git a/src/serializer/serializer.hpp b/src/serializer/serializer.hpp index b3353f5..03bfaed 100644 --- a/src/serializer/serializer.hpp +++ b/src/serializer/serializer.hpp @@ -114,7 +114,7 @@ T LMDBAL::Serializer::deserialize(const MDB_val& value) { template void LMDBAL::Serializer::deserialize(const MDB_val& value, T& result) { clear(); - bytes.setRawData((char*)value.mv_data, value.mv_size); + bytes.setRawData(static_cast(value.mv_data), value.mv_size); stream >> result; } @@ -154,7 +154,7 @@ MDB_val LMDBAL::Serializer::getData() { MDB_val val; val.mv_size = buffer.pos(); - val.mv_data = (char*)bytes.data(); + val.mv_data = bytes.data(); return val; } diff --git a/src/serializer/serializer_qbytearray.hpp b/src/serializer/serializer_qbytearray.hpp index daf3027..2d3f494 100644 --- a/src/serializer/serializer_qbytearray.hpp +++ b/src/serializer/serializer_qbytearray.hpp @@ -34,7 +34,17 @@ public: return value; }; void deserialize(const MDB_val& data, QByteArray& result) { - result.setRawData((char*)data.mv_data, data.mv_size); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + result.setRawData(static_cast(data.mv_data), static_cast(data.mv_size)); +#else + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + result.setRawData(static_cast(data.mv_data), static_cast(data.mv_size)); +#endif } MDB_val setData(const QByteArray& data) { value = data; @@ -43,7 +53,7 @@ public: MDB_val getData() { MDB_val result; result.mv_data = value.data(); - result.mv_size = value.size(); + result.mv_size = static_cast(value.size()); return result; }; void clear() { diff --git a/src/serializer/serializer_qstring.hpp b/src/serializer/serializer_qstring.hpp index fb9310e..a00aea5 100644 --- a/src/serializer/serializer_qstring.hpp +++ b/src/serializer/serializer_qstring.hpp @@ -31,11 +31,33 @@ public: ~Serializer() {}; QString deserialize(const MDB_val& data) { - value = QByteArray((char*)data.mv_data, data.mv_size); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + value = QByteArray(static_cast(data.mv_data), static_cast(data.mv_size)); +#else + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + value = QByteArray(static_cast(data.mv_data), static_cast(data.mv_size)); +#endif + return QString::fromUtf8(value); }; void deserialize(const MDB_val& data, QString& result) { - value = QByteArray((char*)data.mv_data, data.mv_size); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + value = QByteArray(static_cast(data.mv_data), static_cast(data.mv_size)); +#else + if (data.mv_size > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Data size exceeds QByteArray capacity"); + + value = QByteArray(static_cast(data.mv_data), static_cast(data.mv_size)); +#endif + result = QString::fromUtf8(value); } MDB_val setData(const QString& data) { @@ -45,7 +67,7 @@ public: MDB_val getData() { MDB_val result; result.mv_data = value.data(); - result.mv_size = value.size(); + result.mv_size = static_cast(value.size()); return result; }; void clear() {}; //not possible; @@ -54,4 +76,4 @@ private: QByteArray value; }; -} +} \ No newline at end of file diff --git a/src/serializer/serializer_stdstring.hpp b/src/serializer/serializer_stdstring.hpp index 1856e8c..659cb89 100644 --- a/src/serializer/serializer_stdstring.hpp +++ b/src/serializer/serializer_stdstring.hpp @@ -34,7 +34,7 @@ public: return value; }; void deserialize(const MDB_val& data, std::string& result) { - result.assign((char*)data.mv_data, data.mv_size); + result.assign(static_cast(data.mv_data), data.mv_size); } MDB_val setData(const std::string& data) { value = data; @@ -42,7 +42,7 @@ public: }; MDB_val getData() { MDB_val result; - result.mv_data = (char*)value.c_str(); + result.mv_data = const_cast(value.c_str()); result.mv_size = value.size(); return result; }; diff --git a/src/storagecommon.cpp b/src/storagecommon.cpp index 3d7e92c..7202d23 100644 --- a/src/storagecommon.cpp +++ b/src/storagecommon.cpp @@ -23,13 +23,13 @@ #define UNUSED(x) (void)(x) /** - * \class LMDBAL::iStorage + * \class LMDBAL::StorageCommon * * \brief Storage interface * - * This is a interface-like class, it's designed to be an inner database interface to - * be used as a polymorphic entity, and provide protected interaction with the database - * from the heirs code + * This is an interface-like class, it's designed to be an inner database interface to + * be used as a polymorphic entity and provide protected interaction with the database + * from the heir code */ /** @@ -49,7 +49,7 @@ LMDBAL::StorageCommon::StorageCommon(Base* parent, const std::string& name, bool /** * \brief Destroys a storage interface */ -LMDBAL::StorageCommon::~StorageCommon () {} +LMDBAL::StorageCommon::~StorageCommon () = default; /** * \brief A private virtual function to close each storage in the database @@ -103,7 +103,7 @@ void LMDBAL::StorageCommon::drop() { * * Just performs content drop * - * \param[in] transaction - transaction ID, must be writable transaction! + * \param[in] transaction - transaction ID; must be writable transaction! * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise */ int LMDBAL::StorageCommon::drop(TransactionID transaction) { @@ -115,7 +115,7 @@ int LMDBAL::StorageCommon::drop(TransactionID transaction) { * * Just performs content drop * - * \param[in] txn - transaction ID, must be writable transaction! + * \param[in] txn - transaction ID; must be writable transaction! * \returns MDB_SUCCESS if everything went fine, MDB_ code otherwise * * \exception LMDBAL::TransactionTerminated thrown if the transaction was not active @@ -126,7 +126,7 @@ int LMDBAL::StorageCommon::drop(const WriteTransaction& txn) { } /** - * \brief Helper function, thows exception if the database is not opened + * \brief Helper function; throws an exception if the database is not opened * * \param[in] methodName - name of the method this function is called from, just for display in std::exception::what() message * @@ -140,7 +140,7 @@ void LMDBAL::StorageCommon::ensureOpened(const std::string& methodName) const { /** * \brief Storage size * - * \returns amount of records in the storage + * \returns number of records in the storage * * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened @@ -162,7 +162,7 @@ LMDBAL::SizeType LMDBAL::StorageCommon::count() const { } /** - * \brief Retrieves public transaction object for a cursor handle + * \brief Retrieves a public transaction object for a cursor handle * * Cursor must be still opened by a public transaction * @@ -222,25 +222,27 @@ void LMDBAL::StorageCommon::closeCursorTransaction (MDB_cursor* cursor, bool clo /** * \brief Storage size (private transaction variant) * - * \param[in] txn - transaction ID, can be read-only transaction - * \returns amount of records in the storage + * \param[in] txn - transaction ID; can be read-only transaction + * \returns number of records in the storage * * \exception LMDBAL::Unknown thrown if something unexpected happened */ LMDBAL::SizeType LMDBAL::StorageCommon::count(TransactionID txn) const { MDB_stat stat; - int rc = mdb_stat(txn, dbi, &stat); - if (rc != MDB_SUCCESS) + if (const int rc = mdb_stat(txn, dbi, &stat); rc != MDB_SUCCESS) throw Unknown(db->name, mdb_strerror(rc), name); - return stat.ms_entries; + if (stat.ms_entries > std::numeric_limits::max()) + throw Unknown(db->name, "Storage size exceeds it's limits", name); + + return static_cast(stat.ms_entries); } /** * \brief Storage size (public transaction variant) * - * \param[in] txn - transaction, can be read-only transaction - * \returns amount of records in the storage + * \param[in] txn - transaction; can be read-only transaction + * \returns number of records in the storage * * \exception LMDBAL::Closed thrown if the database was closed * \exception LMDBAL::Unknown thrown if something unexpected happened diff --git a/src/storagecommon.h b/src/storagecommon.h index 77a56d2..d54c8d2 100644 --- a/src/storagecommon.h +++ b/src/storagecommon.h @@ -19,7 +19,7 @@ #pragma once #include -#include +#include #include From a6ac974438c075ccb51ab5be1f3e83f8d42a1390 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 20:44:23 +0300 Subject: [PATCH 120/125] CI 1 --- .forgejo/workflows/main.yml | 18 ++++++------------ .forgejo/workflows/test.yml | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.forgejo/workflows/main.yml b/.forgejo/workflows/main.yml index 44dba97..f99b54c 100644 --- a/.forgejo/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -20,11 +20,11 @@ jobs: docs: name: Release documentation - runs-on: archlinux + runs-on: baremetall_peppersurprise needs: [test-qt5, test-qt6] steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Make a build directory run: mkdir build @@ -37,13 +37,7 @@ jobs: working-directory: ./build run: cmake --build . - - name: Copy docs via scp - uses: appleboy/scp-action@master - # working-directory: ./build/doc //doesn't work - with: - host: ${{ secrets.DOMAIN_ROOT }} - username: ${{ secrets.DEPLOY_USER_NAME }} - key: ${{ secrets.DEPLOY_PRIVATE_KEY }} - source: "build/doc/html/*,build/doc/xml/*,build/doc/man/*" - target: "/srv/lmdbal/doc" - strip_components: 2 + - name: Deploy docs + run: | + rm -rf /srv/lmdbal/doc/* + cp -r build/doc/html/* /srv/lmdbal/doc/ diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index a0f3867..a3a8881 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -18,14 +18,14 @@ jobs: runs-on: archlinux steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Make a build directory run: mkdir build - name: Configure working-directory: ./build - run: cmake .. -D LMDBAL_STRICT=True -D LMDBAL_BUILD_TESTS=True -D QT_VERSION_MAJOR= ${{ inputs.flags }} + run: cmake .. -D LMDBAL_STRICT=True -D LMDBAL_BUILD_TESTS=True ${{ inputs.flags }} - name: Build working-directory: ./build From 3e275265170b9634fab22b363320c3469b22dc4b Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 20:47:45 +0300 Subject: [PATCH 121/125] CI 2 --- .forgejo/workflows/main.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.forgejo/workflows/main.yml b/.forgejo/workflows/main.yml index f99b54c..a0aedbf 100644 --- a/.forgejo/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -6,22 +6,22 @@ on: - master jobs: - test-qt5: - name: Test LMDBAL with qt5 - uses: ./.forgejo/workflows/test.yml - with: - flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 - - test-qt6: - name: Test LMDBAL with qt6 - uses: ./.forgejo/workflows/test.yml - with: - flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 +# test-qt5: +# name: Test LMDBAL with qt5 +# uses: ./.forgejo/workflows/test.yml +# with: +# flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 +# +# test-qt6: +# name: Test LMDBAL with qt6 +# uses: ./.forgejo/workflows/test.yml +# with: +# flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 docs: name: Release documentation runs-on: baremetall_peppersurprise - needs: [test-qt5, test-qt6] +# needs: [test-qt5, test-qt6] steps: - name: Check out repository code uses: actions/checkout@v4 From ad1a13c323faa22ae147678e359ab5015db39b6e Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 21:35:09 +0300 Subject: [PATCH 122/125] CI 3 --- .forgejo/workflows/main.yml | 24 +++++++++++++----------- .forgejo/workflows/test.yml | 3 +++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.forgejo/workflows/main.yml b/.forgejo/workflows/main.yml index a0aedbf..a69fd84 100644 --- a/.forgejo/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -6,17 +6,19 @@ on: - master jobs: -# test-qt5: -# name: Test LMDBAL with qt5 -# uses: ./.forgejo/workflows/test.yml -# with: -# flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 -# -# test-qt6: -# name: Test LMDBAL with qt6 -# uses: ./.forgejo/workflows/test.yml -# with: -# flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 + test-qt5: + name: Test LMDBAL with qt5 + uses: ./.forgejo/workflows/test.yml + runs-on: archlinux + with: + flags: -D QT_VERSION_MAJOR=5 -D LMDBAL_NAME=LMDBAL-QT5 + + test-qt6: + name: Test LMDBAL with qt6 + uses: ./.forgejo/workflows/test.yml + runs-on: archlinux + with: + flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 docs: name: Release documentation diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index a3a8881..b8b3e07 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -5,6 +5,9 @@ on: flags: required: true type: string + + secrets: + workflow_dispatch: inputs: flags: From da9f935b148c961cd53c71856fcc563b365e981a Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 21:48:22 +0300 Subject: [PATCH 123/125] CI 4 --- .forgejo/workflows/main.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/main.yml b/.forgejo/workflows/main.yml index a69fd84..c446a81 100644 --- a/.forgejo/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -20,10 +20,10 @@ jobs: with: flags: -D QT_VERSION_MAJOR=6 -D LMDBAL_NAME=LMDBAL-QT6 - docs: - name: Release documentation - runs-on: baremetall_peppersurprise -# needs: [test-qt5, test-qt6] + build-documentation: + name: Builds documentation + runs-on: archlinux + needs: [test-qt5, test-qt6] steps: - name: Check out repository code uses: actions/checkout@v4 @@ -39,6 +39,24 @@ jobs: working-directory: ./build run: cmake --build . + - name: Upload docs + uses: actions/upload-artifact@v3 + with: + name: lmdbal-doc + path: build/doc + retention-days: 1 + + deploy-doc: + name: Deploys documentation + runs-on: archlinux + needs: [build-documentation] + steps: + - name: Download docs + uses: actions/download-artifact@v3 + with: + name: lmdbal-doc + path: build/doc + - name: Deploy docs run: | rm -rf /srv/lmdbal/doc/* From 3701fb92a1498bd737828d8d1df63d4c4d8f02c7 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 2 May 2025 22:08:08 +0300 Subject: [PATCH 124/125] CI 5 --- .forgejo/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/main.yml b/.forgejo/workflows/main.yml index c446a81..6d1cffb 100644 --- a/.forgejo/workflows/main.yml +++ b/.forgejo/workflows/main.yml @@ -48,7 +48,7 @@ jobs: deploy-doc: name: Deploys documentation - runs-on: archlinux + runs-on: baremetall_peppersurprise needs: [build-documentation] steps: - name: Download docs From f9902bc0b1e99434a711d490a80ef8594b45f085 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 6 May 2025 00:22:03 +0300 Subject: [PATCH 125/125] Sessions to manage db open state --- CHANGELOG.md | 3 + src/CMakeLists.txt | 2 + src/base.cpp | 193 ++++++++++++++++++++++++------------ src/base.h | 30 ++++-- src/session.cpp | 73 ++++++++++++++ src/session.h | 47 +++++++++ src/storagecommon.cpp | 2 +- test/basic.cpp | 49 ++++----- test/cachecursor.cpp | 7 +- test/cachetransaction.cpp | 23 +++-- test/duplicates.cpp | 7 +- test/storagecursor.cpp | 7 +- test/storagetransaction.cpp | 23 +++-- 13 files changed, 342 insertions(+), 124 deletions(-) create mode 100644 src/session.cpp create mode 100644 src/session.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 356decc..7987afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - Less dereferencing - Transactions are now closed with the database - Cursors are now closed if the public transaction that opened them got closed +- New primitive to manage database state - session +- Stricter warnings +- Sanitizer builds ### Bug fixes - SIGSEGV on closing database with opened transactions diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c53f6a..9f9469e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES base.cpp transaction.cpp cursorcommon.cpp + session.cpp ) set(HEADERS @@ -20,6 +21,7 @@ set(HEADERS cache.hpp operators.hpp transaction.h + session.h ) target_sources(${LMDBAL_NAME} PRIVATE ${SOURCES}) diff --git a/src/base.cpp b/src/base.cpp index 5100c83..ec4524c 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -18,8 +18,10 @@ #include "base.h" #include "exceptions.h" +#include "session.h" #include "storage.h" #include "transaction.h" +#include "session.h" #define UNUSED(x) (void)(x) @@ -39,75 +41,34 @@ */ LMDBAL::Base::Base(const QString& _name, uint16_t _mapSize): name(_name.toStdString()), - opened(false), + sessions(), size(_mapSize), environment(), storages(), - transactions() + transactions(), + mutex() {} /** * \brief Destroys the database */ LMDBAL::Base::~Base() { - close(); + std::lock_guard lock(mutex); + + for (Session* session : sessions) + session->terminate(); + + if (opened()) + deactivate(); for (const std::pair& pair : storages) delete pair.second; } -/** - * \brief Closes the database - * - * Closes all lmdb handles, aborts all public transactions. - * This function will do nothing on a closed database - * - * \exception LMDBAL::Unknown - thrown if something went wrong aborting transactions - */ -void LMDBAL::Base::close() { - if (opened) { - for (const std::pair pair : transactions) { - abortTransaction(pair.first, emptyName); - pair.second->reset(); - } - - for (const std::pair& pair : storages) - pair.second->close(); - - mdb_env_close(environment); - transactions.clear(); - opened = false; - } +LMDBAL::Session LMDBAL::Base::open() { + return {this}; } -/** - * \brief Opens the database - * - * Almost every LMDBAL::Base require it to be opened, this function does it. - * It also creates the directory for the database if it was an initial launch. - * This function will do nothing on an opened database - * - * \exception LMDBAL::Unknown - thrown if something went wrong opening storages and caches - */ -void LMDBAL::Base::open() { - if (!opened) { - mdb_env_create(&environment); - QString path = createDirectory(); - - mdb_env_set_maxdbs(environment, static_cast(storages.size())); - mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); - mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); - - TransactionID txn = beginPrivateTransaction(emptyName); - for (const std::pair& pair : storages) { - StorageCommon* storage = pair.second; - if (const int rc = storage->open(txn)) - throw Unknown(name, mdb_strerror(rc)); - } - commitPrivateTransaction(txn, emptyName); - opened = true; - } -} /** * \brief Removes database directory @@ -117,7 +78,7 @@ void LMDBAL::Base::open() { * \exception LMDBAL::Opened - thrown if this function was called on an opened database */ bool LMDBAL::Base::removeDirectory() { - if (opened) + if (opened()) throw Opened(name, "remove database directory"); QString path = getPath(); @@ -144,7 +105,7 @@ bool LMDBAL::Base::removeDirectory() { * \exception LMDBAL::Directory - if the database couldn't create the folder */ QString LMDBAL::Base::createDirectory() { - if (opened) + if (opened()) throw Opened(name, "create database directory"); QString path = getPath(); @@ -183,8 +144,9 @@ QString LMDBAL::Base::getPath() const { * * \returns true if the database is opened and ready for work, false otherwise */ -bool LMDBAL::Base::ready() const { - return opened;} +bool LMDBAL::Base::opened() const { + return !sessions.empty(); +} /** * \brief Drops the database @@ -195,7 +157,7 @@ bool LMDBAL::Base::ready() const { * \exception LMDBAL::Unknown - thrown if something unexpected happend */ void LMDBAL::Base::drop() { - if (!opened) + if (!opened()) throw Closed("drop", name); TransactionID txn = beginPrivateTransaction(emptyName); @@ -301,7 +263,7 @@ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id) { * \exception LMDBAL::Unknown - thrown if something unexpected happened */ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& storageName) const { - if (!opened) + if (!opened()) throw Closed("beginReadOnlyTransaction", name, storageName); TransactionID txn = beginPrivateReadOnlyTransaction(storageName); @@ -326,7 +288,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginReadOnlyTransaction(const std::string& * \exception LMDBAL::Unknown - thrown if something unexpected happened */ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageName) const { - if (!opened) + if (!opened()) throw Closed("beginTransaction", name, storageName); TransactionID txn = beginPrivateTransaction(storageName); @@ -351,7 +313,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginTransaction(const std::string& storageN * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& storageName) const { - if (!opened) + if (!opened()) throw Closed("abortTransaction", name, storageName); abortPrivateTransaction(id, storageName); @@ -374,7 +336,7 @@ void LMDBAL::Base::abortTransaction(LMDBAL::TransactionID id, const std::string& * \exception LMDBAL::Unknown - thrown if something unexpected happened */ void LMDBAL::Base::commitTransaction(LMDBAL::TransactionID id, const std::string& storageName) { - if (!opened) + if (!opened()) throw Closed("abortTransaction", name, storageName); commitPrivateTransaction(id, storageName); @@ -407,7 +369,7 @@ LMDBAL::TransactionID LMDBAL::Base::beginPrivateReadOnlyTransaction(const std::s * \brief Begins writable transaction * * This function is intended to be called from subordinate storage or cache, - * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * it's not accounted in the transaction collection, other storages and caches are not notified about this kind of transaction * * \param[in] storageName - name of the storage/cache that you begin transaction from, needed just to inform if something went wrong * @@ -428,10 +390,10 @@ LMDBAL::TransactionID LMDBAL::Base::beginPrivateTransaction(const std::string& s /** * \brief Aborts transaction * - * Terminates transaction cancelling changes. + * Terminates transaction, cancelling changes. * This is an optimal way to terminate read-only transactions. * This function is intended to be called from subordinate storage or cache, - * it's not accounted in transaction collection, other storages and caches are not notified about this kind of transaction + * it's not accounted in the transaction collection, other storages and caches are not notified about this kind of transaction * * \param[in] id - transaction ID you want to abort * \param[in] storageName - name of the storage/cache that you begin transaction from, unused here @@ -458,3 +420,104 @@ void LMDBAL::Base::commitPrivateTransaction(LMDBAL::TransactionID id, const std: if (rc != MDB_SUCCESS) throw Unknown(name, mdb_strerror(rc), storageName); } + +/** + * \brief Registers session + * + * Registers the session in the session collection. + * If it was the first accounted session, it activates the database + * + * \exception LMDBAL::Unknown - thrown if this session was already registered + */ +void LMDBAL::Base::registerSession(Session* session) { + std::lock_guard lock(mutex); + + if (sessions.empty()) + activate(); + + if (!sessions.insert(session).second) + throw Unknown(name, "session already registered"); +} + +/** + * \brief Unregisters session + * + * Unregisters the session from the session collection. + * If it was the last accounted session, it deactivates the database + * + * \exception LMDBAL::Unknown - thrown if this session was not registered + */ +void LMDBAL::Base::unregisterSession(Session* session) { + std::lock_guard lock(mutex); + + if (sessions.size() == 1) + deactivate(); + + if (sessions.erase(session) != 1) + throw Unknown(name, "session was not registered"); +} + +/** + * \brief Swaps sessions + * + * Replaces one session by another in the session collection. + * + * \exception LMDBAL::Unknown - thrown if there is some unexpected state with sessions, it means the database is in the wrong state + */ +void LMDBAL::Base::replaceSession(Session* closing, Session* opening) { + std::lock_guard lock(mutex); + + if (sessions.erase(closing) != 1) + throw Unknown(name, "session was not registered"); + + if (!sessions.insert(opening).second) + throw Unknown(name, "session already registered"); +} + +/** + * \brief Deactivates the database, + * + * Closes all lmdb handles, aborts all public transactions. + * This function will emit SIGSEGV on a deactivated database + * + * \exception LMDBAL::Unknown - thrown if something went wrong aborting transactions + */ +void LMDBAL::Base::deactivate() { + for (const std::pair pair : transactions) { + abortTransaction(pair.first, emptyName); + pair.second->reset(); + } + + for (const std::pair& pair : storages) + pair.second->close(); + + mdb_env_close(environment); + transactions.clear(); +} + +/** + * \brief Activates the database + * + * Almost every LMDBAL::Base require it to be opened, this function does it. + * It also creates the directory for the database if it was an initial launch. + * This function will behave unpredictably on an activated database, possible data corruption. + * + * \exception LMDBAL::Unknown - thrown if something went wrong opening storages and caches + */ +void LMDBAL::Base::activate() { + mdb_env_create(&environment); + QString path = createDirectory(); + + mdb_env_set_maxdbs(environment, static_cast(storages.size())); + mdb_env_set_mapsize(environment, size * 1024UL * 1024UL); + mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); + + TransactionID txn = beginPrivateTransaction(emptyName); + for (const std::pair& pair : storages) { + StorageCommon* storage = pair.second; + if (const int rc = storage->open(txn)) + throw Unknown(name, mdb_strerror(rc)); + } + + commitPrivateTransaction(txn, emptyName); +} \ No newline at end of file diff --git a/src/base.h b/src/base.h index 4ccf45d..230cf14 100644 --- a/src/base.h +++ b/src/base.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -37,6 +39,7 @@ namespace LMDBAL { class StorageCommon; class Transaction; class WriteTransaction; +class Session; template class Serializer; @@ -54,14 +57,14 @@ class Base { friend class StorageCommon; friend class Transaction; friend class WriteTransaction; -public: + friend class Session; +public: Base(const QString& name, uint16_t mapSize = 10); ~Base(); - void open(); - void close(); - bool ready() const; + Session open(); + bool opened() const; bool removeDirectory(); QString createDirectory(); QString getName() const; @@ -84,8 +87,9 @@ public: LMDBAL::Cache* getCache(const std::string& storageName); private: - typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ - typedef std::map Transactions; /**<\brief Piblic transaction IDs are saved in the std::set*/ + typedef std::map Storages; /**<\brief Storage and Cache pointers are saved in the std::map*/ + typedef std::map Transactions; /**<\brief Public transaction IDs are saved in the std::map*/ + typedef std::set Sessions; /**<\brief Sessions are saved in the std::set*/ void commitTransaction(TransactionID id); void abortTransaction(TransactionID id) const; @@ -99,13 +103,21 @@ private: void commitPrivateTransaction(TransactionID id, const std::string& storageName); void abortPrivateTransaction(TransactionID id, const std::string& storageName) const; + void registerSession(Session* session); + void unregisterSession(Session* session); + void replaceSession(Session* closing, Session* opening); + + void activate(); + void deactivate(); + private: std::string name; /**<\brief Name of this database*/ - bool opened; /**<\brief State of this database*/ + Sessions sessions; /**<\brief Opened session pointers*/ uint16_t size; /**<\brief lmdb map size in MiB*/ MDB_env* environment; /**<\brief lmdb environment handle*/ Storages storages; /**<\brief Registered storages and caches*/ mutable Transactions transactions; /**<\brief Active public transactions*/ + std::mutex mutex; /**<\brief Mutex for thread safety*/ inline static const std::string emptyName = ""; /**<\brief Empty string for general fallback purposes*/ }; @@ -132,7 +144,7 @@ private: */ template LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, bool duplicates) { - if (opened) + if (opened()) throw Opened(name, "add storage " + storageName); auto storage = new Storage(this, storageName, duplicates); @@ -160,7 +172,7 @@ LMDBAL::Storage* LMDBAL::Base::addStorage(const std::string& storageName, */ template LMDBAL::Cache * LMDBAL::Base::addCache(const std::string& storageName) { - if (opened) + if (opened()) throw Opened(name, "add cache " + storageName); auto cache = new Cache(this, storageName, false); diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..a02a745 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,73 @@ +/* +* LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "session.h" + +LMDBAL::Session::Session(): + parent(nullptr) {} + +LMDBAL::Session::Session(Base* parent): + parent(parent) +{ + parent->registerSession(this); +} + +LMDBAL::Session::Session(Session&& other): + parent(other.parent) +{ + if (parent) + parent->replaceSession(&other, this); +} + +LMDBAL::Session::~Session() { + if (parent) + parent->unregisterSession(this); +} + +LMDBAL::Session& LMDBAL::Session::operator = (Session&& other) { + if (parent) + if (other.parent) + parent->unregisterSession(&other); + else + parent->unregisterSession(this); + else + if (other.parent) + other.parent->replaceSession(&other, this); + + parent = other.parent; + other.terminate(); + + return *this; +} + +void LMDBAL::Session::close() { + if (!parent) + return; + + parent->unregisterSession(this); + terminate(); +} + +bool LMDBAL::Session::opened() const { + return parent != nullptr; +} + +void LMDBAL::Session::terminate() { + parent = nullptr; +} + diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..c0f8da8 --- /dev/null +++ b/src/session.h @@ -0,0 +1,47 @@ +/* +* LMDB Abstraction Layer. + * Copyright (C) 2023 Yury Gubich + * + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "base.h" + +namespace LMDBAL { + +class Session { + friend class Base; +public: + explicit Session(); + ~Session(); + Session(const Session&) = delete; + Session(Session&&); + + Session& operator = (const Session&) = delete; + Session& operator = (Session&&); + + void close(); + bool opened() const; + +private: + Base* parent; + +private: + Session(Base* parent); + void terminate(); +}; + +} diff --git a/src/storagecommon.cpp b/src/storagecommon.cpp index 7202d23..4feaaa7 100644 --- a/src/storagecommon.cpp +++ b/src/storagecommon.cpp @@ -356,7 +356,7 @@ const std::string & LMDBAL::StorageCommon::dbName() const { * \returns true if database is ipened, false otherwise */ bool LMDBAL::StorageCommon::isDBOpened() const { - return db->opened;} + return db->opened();} /** * \brief Throws LMDBAL::Unknown diff --git a/test/basic.cpp b/test/basic.cpp index fb6b0c5..36880b5 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -3,6 +3,7 @@ #include "base.h" #include "storage.h" #include "cache.h" +#include "session.h" #include #include @@ -34,13 +35,14 @@ protected: } static void TearDownTestSuite() { - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; + static LMDBAL::Session session; LMDBAL::Storage* t1; LMDBAL::Storage* t2; @@ -50,19 +52,20 @@ protected: LMDBAL::Base* BaseTest::db = nullptr; +LMDBAL::Session BaseTest::session; TEST_F(BaseTest, RemovingDirectory) { EXPECT_EQ(db->removeDirectory(), true); } TEST_F(BaseTest, OpeningClosingDatabase) { - EXPECT_EQ(db->ready(), false); - db->open(); - EXPECT_EQ(db->ready(), true); - db->close(); - EXPECT_EQ(db->ready(), false); - db->open(); - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), false); + session = db->open(); + EXPECT_EQ(db->opened(), true); + session.close(); + EXPECT_EQ(db->opened(), false); + session = db->open(); + EXPECT_EQ(db->opened(), true); } TEST_F(BaseTest, Flags) { @@ -93,7 +96,7 @@ TEST_F(BaseTest, Flags) { } TEST_F(BaseTest, AddingIntegerKey) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); t1->addRecord(1, 2); t1->addRecord(2, 2); t1->addRecord(3, 15); @@ -101,7 +104,7 @@ TEST_F(BaseTest, AddingIntegerKey) { } TEST_F(BaseTest, AddingQStringKey) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); t2->addRecord("hello", "world"); t2->addRecord("aaa", "gagdfsdf"); t2->addRecord("sdfhga", "DSFFDG"); @@ -110,7 +113,7 @@ TEST_F(BaseTest, AddingQStringKey) { } TEST_F(BaseTest, AddingKeysToCache) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); c1->addRecord(2, "blah balah"); c1->addRecord(-4, "testing goes brrr"); c1->addRecord(40, "whatever"); @@ -119,7 +122,7 @@ TEST_F(BaseTest, AddingKeysToCache) { } TEST_F(BaseTest, AddingKeysToVariableCache) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); c2->addRecord("regrets", "blah balah"); c2->addRecord("fossil fingers", 842); c2->addRecord("preloaded cut", 539.75); @@ -132,7 +135,7 @@ TEST_F(BaseTest, AddingKeysToVariableCache) { } TEST_F(BaseTest, AddingRepeatingKey) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_THROW(t1->addRecord(3, 24), LMDBAL::Exist); EXPECT_EQ(t1->getRecord(3), 15); @@ -148,7 +151,7 @@ TEST_F(BaseTest, AddingRepeatingKey) { } TEST_F(BaseTest, GettingNotExistingKeys) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound); EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound); @@ -157,21 +160,21 @@ TEST_F(BaseTest, GettingNotExistingKeys) { } TEST_F(BaseTest, Persistence) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); uint32_t t1Size = t1->count(); uint32_t t2Size = t2->count(); uint32_t c1Size = c1->count(); uint32_t c2Size = c2->count(); - db->close(); delete db; + EXPECT_EQ(session.opened(), false); db = new LMDBAL::Base("testBase"); t1 = db->addStorage("table1"); t2 = db->addStorage("table2"); c1 = db->addCache("cache1"); c2 = db->addCache("cache2"); - db->open(); + session = db->open(); EXPECT_EQ(t1->count(), t1Size); EXPECT_EQ(t1->getRecord(3), 15); @@ -212,7 +215,7 @@ TEST_F(BaseTest, Persistence) { } TEST_F(BaseTest, CountAndDrop) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_EQ(t1->count(), 3); EXPECT_EQ(t2->count(), 4); EXPECT_EQ(c1->count(), 4); @@ -237,7 +240,7 @@ TEST_F(BaseTest, CountAndDrop) { } TEST_F(BaseTest, Change) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_EQ(t1->count(), 1); EXPECT_EQ(t2->count(), 1); EXPECT_EQ(c1->count(), 2); @@ -283,7 +286,7 @@ TEST_F(BaseTest, Change) { } TEST_F(BaseTest, Force) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_EQ(t1->forceRecord(58, 35), false); //changing EXPECT_EQ(t1->forceRecord(68, 36), true); //adding @@ -318,7 +321,7 @@ TEST_F(BaseTest, Force) { } TEST_F(BaseTest, ReadAll) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); std::map m1 = t1->readAll(); std::map m2 = t2->readAll(); @@ -349,7 +352,7 @@ TEST_F(BaseTest, ReadAll) { } TEST_F(BaseTest, ReplaceAll) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); t1->replaceAll({ {7, 48}, @@ -417,7 +420,7 @@ TEST_F(BaseTest, ReplaceAll) { } TEST_F(BaseTest, AddRecords) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::SizeType s1 = t1->addRecords({ {5, 3}, diff --git a/test/cachecursor.cpp b/test/cachecursor.cpp index b965c69..64b4f42 100644 --- a/test/cachecursor.cpp +++ b/test/cachecursor.cpp @@ -4,6 +4,7 @@ #include "storage.h" #include "cache.h" #include "cursor.h" +#include "session.h" class CacheCursorTest : public ::testing::Test { protected: @@ -19,7 +20,7 @@ protected: db = new LMDBAL::Base("testBase"); db->addCache("table1"); db->addCache("empty"); - db->open(); + session = db->open(); } } @@ -30,7 +31,7 @@ protected: static void TearDownTestSuite() { cursor.drop(); transaction.terminate(); - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; @@ -39,6 +40,7 @@ protected: static LMDBAL::Base* db; static LMDBAL::Cursor cursor; static LMDBAL::Transaction transaction; + static LMDBAL::Session session; LMDBAL::Cache* cache; LMDBAL::Cache* emptyCache; @@ -47,6 +49,7 @@ protected: LMDBAL::Base* CacheCursorTest::db = nullptr; LMDBAL::Cursor CacheCursorTest::cursor; LMDBAL::Transaction CacheCursorTest::transaction; +LMDBAL::Session CacheCursorTest::session; static const std::map data({ {245665783, "bothering nerds"}, diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index a2c0e42..5222852 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -4,6 +4,7 @@ #include "base.h" #include "cache.h" +#include "session.h" class CacheTransactionsTest : public testing::Test { protected: @@ -39,18 +40,19 @@ protected: db->addStorage("cache2"); } - db->open(); + session = db->open(); db->drop(); } static void TearDownTestSuite() { - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; + static LMDBAL::Session session; LMDBAL::Cache* c1; LMDBAL::Cache* c2; @@ -58,9 +60,10 @@ protected: LMDBAL::Base* CacheTransactionsTest::db = nullptr; +LMDBAL::Session CacheTransactionsTest::session; TEST_F(CacheTransactionsTest, Adding) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_EQ(c1->count(), 0); EXPECT_EQ(c2->count(), 0); @@ -90,7 +93,7 @@ TEST_F(CacheTransactionsTest, Adding) { } TEST_F(CacheTransactionsTest, Aborting) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::SizeType s1 = c1->count(); LMDBAL::SizeType s2 = c2->count(); @@ -114,7 +117,7 @@ TEST_F(CacheTransactionsTest, Aborting) { } TEST_F(CacheTransactionsTest, Reading) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); @@ -132,7 +135,7 @@ TEST_F(CacheTransactionsTest, Reading) { } TEST_F(CacheTransactionsTest, ConcurentReading) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::SizeType size = c1->count(); LMDBAL::WriteTransaction txn = db->beginTransaction(); @@ -168,7 +171,7 @@ TEST_F(CacheTransactionsTest, ConcurentReading) { TEST_F(CacheTransactionsTest, ConcurentModification) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); //if you start one writable transaction after another //in a single thread like so: @@ -231,7 +234,7 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { } TEST_F(CacheTransactionsTest, RAIIResourceFree) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); int pid = fork(); if (pid == 0) { // I am the child @@ -287,8 +290,8 @@ TEST_F(CacheTransactionsTest, TransactionTerminationOnClose) { EXPECT_EQ(c1->getRecord(578, txn), 4552); EXPECT_EQ(c1->checkRecord(578), false); - db->close(); - db->open(); + session.close(); + session = db->open(); EXPECT_EQ(txn.isActive(), false); EXPECT_THROW(c1->getRecord(578, txn), LMDBAL::TransactionTerminated); diff --git a/test/duplicates.cpp b/test/duplicates.cpp index 0bc6b56..4bb77db 100644 --- a/test/duplicates.cpp +++ b/test/duplicates.cpp @@ -7,6 +7,7 @@ #include "base.h" #include "storage.h" #include "cursor.h" +#include "session.h" class DuplicatesTest : public ::testing::Test { protected: @@ -35,18 +36,19 @@ protected: db->addStorage("intDouble", true); db->addStorage("floatLong", true); - db->open(); + session = db->open(); } } static void TearDownTestSuite() { - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; + static LMDBAL::Session session; LMDBAL::Storage* tu1; LMDBAL::Storage* tu2; @@ -56,6 +58,7 @@ protected: }; LMDBAL::Base* DuplicatesTest::db = nullptr; +LMDBAL::Session DuplicatesTest::session; TEST_F(DuplicatesTest, Flags) { uint32_t tu1Flags = getTU1Flags(); diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp index 92279b1..d44e657 100644 --- a/test/storagecursor.cpp +++ b/test/storagecursor.cpp @@ -3,6 +3,7 @@ #include "base.h" #include "storage.h" #include "cursor.h" +#include "session.h" class StorageCursorTest : public ::testing::Test { protected: @@ -18,7 +19,7 @@ protected: db = new LMDBAL::Base("testBase"); db->addStorage("table1"); db->addStorage("empty"); - db->open(); + session = db->open(); } } @@ -29,7 +30,7 @@ protected: static void TearDownTestSuite() { cursor.drop(); transaction.terminate(); - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; @@ -38,6 +39,7 @@ protected: static LMDBAL::Base* db; static LMDBAL::Cursor cursor; static LMDBAL::Transaction transaction; + static LMDBAL::Session session; LMDBAL::Storage* table; LMDBAL::Storage* emptyTable; @@ -46,6 +48,7 @@ protected: LMDBAL::Base* StorageCursorTest::db = nullptr; LMDBAL::Cursor StorageCursorTest::cursor; LMDBAL::Transaction StorageCursorTest::transaction = LMDBAL::Transaction(); +LMDBAL::Session StorageCursorTest::session = LMDBAL::Session(); static const std::map data({ {245665783, "bothering nerds"}, diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index d4b9089..be0fc1d 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -4,6 +4,7 @@ #include "base.h" #include "storage.h" +#include "session.h" class StorageTransactionsTest : public testing::Test { protected: @@ -39,18 +40,19 @@ protected: db->addStorage("table2"); } - db->open(); + session = db->open(); db->drop(); } static void TearDownTestSuite() { - db->close(); + session.close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; + static LMDBAL::Session session; LMDBAL::Storage* t1; LMDBAL::Storage* t2; @@ -58,9 +60,10 @@ protected: LMDBAL::Base* StorageTransactionsTest::db = nullptr; +LMDBAL::Session StorageTransactionsTest::session; TEST_F(StorageTransactionsTest, Adding) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); EXPECT_EQ(t1->count(), 0); EXPECT_EQ(t2->count(), 0); @@ -90,7 +93,7 @@ TEST_F(StorageTransactionsTest, Adding) { } TEST_F(StorageTransactionsTest, Aborting) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::SizeType s1 = t1->count(); LMDBAL::SizeType s2 = t2->count(); @@ -114,7 +117,7 @@ TEST_F(StorageTransactionsTest, Aborting) { } TEST_F(StorageTransactionsTest, Reading) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); @@ -132,7 +135,7 @@ TEST_F(StorageTransactionsTest, Reading) { } TEST_F(StorageTransactionsTest, ConcurentReading) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); LMDBAL::SizeType size = t1->count(); LMDBAL::WriteTransaction txn = db->beginTransaction(); @@ -167,7 +170,7 @@ TEST_F(StorageTransactionsTest, ConcurentReading) { } TEST_F(StorageTransactionsTest, ConcurentModification) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); //if you start one writable transaction after another //in a single thread like so: @@ -230,7 +233,7 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { } TEST_F(StorageTransactionsTest, RAIIResourceFree) { - EXPECT_EQ(db->ready(), true); + EXPECT_EQ(db->opened(), true); int pid = fork(); if (pid == 0) { // I am the child @@ -286,8 +289,8 @@ TEST_F(StorageTransactionsTest, TransactionTerminationOnClose) { EXPECT_EQ(t1->getRecord(543, txn), 229); EXPECT_EQ(t1->checkRecord(543), false); - db->close(); - db->open(); + session.close(); + session = db->open(); EXPECT_EQ(txn.isActive(), false); EXPECT_THROW(t1->getRecord(543, txn), LMDBAL::TransactionTerminated);