├── .gitignore ├── LICENSE ├── README.md ├── bookmanagement ├── README.md └── jpg │ ├── 1.png │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.png │ ├── 19.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.png │ ├── 8.jpg │ ├── 9.jpg │ ├── IMG_9281.jpg │ ├── IMG_9359.jpg │ ├── IMG_9360.jpg │ ├── IMG_9362.jpg │ ├── IMG_9363.jpg │ ├── IMG_9365.jpg │ └── IMG_9366.jpg └── miniSQL ├── README.md ├── jpg ├── 1 2.jpg ├── 1.jpg ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png └── miniSQL ├── APIManager └── api.py ├── CatalogManager └── catalog.py ├── IndexManager └── index.py ├── MiniSQL.py └── dbfiles ├── catalog_files ├── indexs_catalog.msql └── tables_catalog.msql └── index_files └── tables_B-plus_tree.msql /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | .DS_Store 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniSQL 2 | MiniSQL数据库以及图书管理系统,基于python和UWP设计。 3 | 4 | MiniSQL:https://github.com/AlanShaw-GitHub/MiniSQL/tree/master/miniSQL 5 | 6 | 图书管理系统:https://github.com/AlanShaw-GitHub/MiniSQL/tree/master/bookmanagement/ 7 | -------------------------------------------------------------------------------- /bookmanagement/README.md: -------------------------------------------------------------------------------- 1 | # 数据库程序设计 2 | 3 | | 姓名 | 学号 | 指导老师 | 日期 | 4 | | ------ | ---- | -------- | --------- | 5 | | 肖振新 | * | 高云君 | 2018.6.10 | 6 | 7 | 8 | ## 一、实验目的 9 | 10 | 1、 设计并实现一个精简的图书管理系统,具有入库、查询、借书、还书、借书证管理 等基本功能。 11 | 12 | 2、 通过本次设计来加深对数据库的了解和使用,同时提高自身的系统编程能力。 13 | 14 | ## 二、实验平台开发工具: 15 | 16 | MySQL、Visual Studio 2017 、UWP、Pyhton、C#、Xaml、telerik UI for ASP.NET 17 | 18 | Jetbrains DataGrip、Debian Linux OS 9(MySQL server)、MySQL Plugin For .NET framework 19 | 20 | ## 三、总体设计 21 | 22 | ### 系统架构概述 23 | 24 | 本程序是在Windows平台下开发的简易图书管理系统,我也不太擅长花很多时间在写长篇大论的报告上,所以本报告也会写的比较简洁,主要以贴图为主。。 25 | 26 | 27 | 28 | 前端采用Windows UWP开发包进行设计,主窗口采用了Windows 2017 fall creator update(Windows秋季创意者更新)中新引入的navigation bar控件进行导航,这也是Windows内置应用(如设置)和众多Windows应用商店所采用的窗口导航方案,总体背景使用了transparent的透明亚克力,使得程序使用更加流畅。安装卸载都如同Windows应用商店的程序那样便捷。数据表的展示使用了telerik UI for ASP.NET,数据展示十分自然完整。 29 | 30 | 本程序使用了大量的异步编程方案,使得在执行存取数据等与后端数据库交互的耗时操作时程序不至于无响应。 31 | 32 | 本程序后端提供了以下三种解决方案:(值得一提的是,不管使用哪一种方案,用户的操作逻辑是完全相同的,只需要在第一次打开本程序时在设置中初始化即可,之后配置就会被保存在Windows系统管理的缓存中,这也符合了前后端逻辑分离的原则) 33 | 34 | 1. 使用本地的MySQL数据库服务器,这需要在本地电脑安装MySQL服务器。 35 | 2. 使用云端的测试服务器,此服务器是作者自行租用的服务器,为了让没有下载MySQL数据库的用户也可以使用,缺点是延迟较高。 36 | 3. 使用MiniSQL数据库引擎,这是为期末大程设计的数据库,正好集成到了此图书管理系统应用中,作为数据库后端引擎,是本程序的一大亮点。该数据库引擎随程序一起打包发布,无需另外下载。 37 | 38 | ### 数据库表设计 39 | 40 | - 图书信息表(books) 41 | 42 | | 字段名 | 类型 | 含义 | 43 | | --------- | -------- | ------------------ | 44 | | BookNo | char(20) | 图书编号,**主码** | 45 | | BookName | char(20) | 图书名 | 46 | | Publisher | char(20) | 出版商 | 47 | | Date | char(20) | 入库时间 | 48 | | Author | char(20) | 作者 | 49 | | Price | char(20) | 价格 | 50 | | Storage | char(20) | 库存量 | 51 | 52 | - 图书借阅信息表(records) 53 | 54 | | 字段名 | 类型 | 含义 | 55 | | ---------------------- | -------- | ------------------------------- | 56 | | UserID | char(20) | 用户名,**来自user表的外键** | 57 | | BookNo | char(20) | 图书编号,**来自books表的外键** | 58 | | LentDate | char(20) | 出借日期 | 59 | | ReturnDate | char(20) | 归还日期 | 60 | | Returned | char(20) | 是否归还 | 61 | | UserID,BookNo,LentDate | - | 主码 | 62 | 63 | - 用户信息表(users) 64 | 65 | | 字段名 | 类型 | 含义 | 66 | | -------- | -------- | ---------------- | 67 | | UserID | char(20) | 用户ID,**主码** | 68 | | Password | char(20) | 密码 | 69 | | Name | char(20) | 用户姓名 | 70 | | Contact | char(20) | 联系方式 | 71 | 72 | ### 所用开发技术 73 | 74 | 本程序的亮点是uwp窗口的设计和多种多样的后端连接方式,本作者的学习路线也是十分的陡峭,从毫无Windows开发经验到两天入门c#、xaml、uwp和win32 api那一套,然后动手写该程序。由于c#远不是我最为拿手的语言,写起来也很不顺手。之前在用傻瓜式的qt做图形界面开发时,并不需要考虑什么异步操作、多线程操作,但是因为图书管理系统在取数据的时候可能很慢(比如大规模的图书数据、或者连接远程服务器时的巨大延迟),为了不使程序在这个过程中无反应(没错就是大家经常看到的此程序“未响应”然后过一会又好了),就必须采用异步编程。最后整个程序使用起来也是十分的流畅。 75 | 76 | navigation bar和透明毛玻璃效果时也是微软对新的UI布局方案一次尝试,笔者注意到,在最新的WWDC 2018苹果全球开发者大会上,新版本的macOS mojave的一些内置程序如“新闻、应用商店”等也采用了类似于Windows uwp的窗口布局方案,这也与本程序的高度一致。如下图所示: 77 | 78 | 79 | 80 | 当然,作为一个数据库作业,最为重要的还是后端数据库逻辑的设计,本实验和数据库理论联系最为密切的应该就是于后端数据库的连接和数据表data schema的设计了。本程序连接后端或者云端的mysql数据库使用的是c#版本的mysql connector,由mysql官方发布,专门为Microsoft .NET framwork框架开发,相应的visual studio集成驱动的下载官网链接为https://dev.mysql.com/downloads/connector/net/,如下图所示: 81 | 82 | 83 | 84 | 数据表的内容在前面也已经列表说明了,主要就是三个数据表,books表存的就是图书的信息,包括图书编号、图书名等等关于本图书的各种信息,在用户登录的情况下可以进行借书操作。users表存的是用户的信息,包括用户账号密码、姓名和联系方式,user表在**管理员账号**下可以进行修改,然后新的用户可以登录借书。连接这两个表的就是借书记录了,借书记录主要包含了借的人的编号,图书编号和出借/归还日期,注意这里的主码是(图书编号+用户编号+出借日期),因为同一本书的库存可能不止一本,用户也可能借很多本这个书(尽管不大可能)。 85 | 86 | 87 | 88 | ### 代码一览 89 | 90 | 代码在这里贴个大概看一下,在报告中附上太多的代码影响美观,就不贴了。 91 | 92 | 前端代码使用xaml设计: 93 | 94 | 95 | 96 | 后端逻辑当然使用微软亲生的c#了: 97 | 98 | 99 | 100 | another: 101 | 102 | 103 | 104 | ## 四、详细设计 105 | 106 | ### 1· 主界面设计 107 | 108 | 主界面的数据表的展示使用了telerik UI for ASP.NET的一个控件,就是下图中的图书显示模块。该空间十分自然的以列表方式展示了数据。 109 | 110 | 右上角有两个按钮,刷新按钮在按下后会重新连接数据库并取回新的数据,用户登录按钮按下后会弹出新的窗口,并提示进行用户的登录。登录后可以进行借书操作。 111 | 112 | 113 | 114 | 点击右上角的用户登录,弹出新的窗口,如果输入账号或者密码错误,则会提示,登录不成功。如下图所示: 115 | 116 | 117 | 118 | ### 2· 图书借阅模块 119 | 120 | 在上方的搜索栏输入对应的信息进行搜索,下面会自动弹出满足条件的图书,选中、点击借阅即可。 121 | 122 | 123 | 124 | 如果没有登录,或者没有选中该图书、该书库存不足,则会报错,借阅不成功: 125 | 126 | 127 | 128 | 如果借阅成功,一条新的记录会被导入到record表中,**并且该书库存减1**. 129 | 130 | 131 | 132 | ### 3· 图书归还模块 133 | 134 | 好了,现在在上一步骤中借到书已经添加到records中了,我们到图书归还模块中去查看(这是归还完成时的界面): 135 | 136 | 137 | 138 | 我们可以点击归还该书: 139 | 140 | 141 | 142 | ### 4· 图书入库/添加用户模块 143 | 144 | 在这里,只有登录了管理员才可以进行操作,如果登录了管理员,**那么左上角会显示管理员字样** 145 | 146 | 147 | 148 | 如果没有登录管理员,则不具有管理员权限,那么在试图操作时会报错: 149 | 150 | 151 | 152 | 我们现在登录管理员账号,导入新图书: 153 | 154 | 155 | 156 | 然后顺便尝试一下添加新用户: 157 | 158 | 159 | 160 | ### 5· 用户管理模块 161 | 162 | 好了,在上一步骤中添加的新用户,可以在用户管理界面进行查看和删除了,同样,如果没有登录管理员,那么跳转到这个界面的时候什么也看不到~ 163 | 164 | 165 | 166 | 然后我们选择一个用户,删除掉它: 167 | 168 | 169 | 170 | 这时候,我们可以看到,这个用户已经人间蒸发了: 171 | 172 | 173 | 174 | ### 6·管理员/用户登录模块 175 | 176 | 这里是管理员登录弹窗,注意的是和在主界面的用户登录界面基本相似,但是职能不同。只有在这里登录了,才可以进行上面的图书入库/用户添加删除操作。 177 | 178 | 179 | 180 | 181 | 182 | ### 7· 设置模块 183 | 184 | 设置模块在第一次启动本程序的时候会自动跳转到这里,并且如果没有在这里选择一个数据库源的话,即使点击左侧栏目的其他窗口,也会弹窗报错,提示无法连接数据库。在第一次打开本程序并选择一个方式初始化数据库后,该信息会保存在windows缓存中,接下来打开本程序都不需要再次设置,并且会自动跳转到首页作为默认的界面。 185 | 186 | 187 | 188 | 如果选择本地连接,且之前已经创建好了一个数据库,那么惦记连接到现有的数据库,否则,点击新建一个数据库: 189 | 190 | 191 | 192 | 如果希望连接到一个远程的数据库,那么点击下面的连接到云端数据库(注意:由于网络延迟,服务器在国外(你懂的),所以每次交互需要的时间可能较长,在我的电脑上实际测试,每个操作大概都有一秒左右的延迟): 193 | 194 | 195 | 196 | 同理,也可以连接到程序自带的minisql数据库。 197 | -------------------------------------------------------------------------------- /bookmanagement/jpg/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/1.png -------------------------------------------------------------------------------- /bookmanagement/jpg/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/10.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/11.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/12.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/13.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/14.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/15.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/16.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/17.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/18.png -------------------------------------------------------------------------------- /bookmanagement/jpg/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/19.png -------------------------------------------------------------------------------- /bookmanagement/jpg/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/2.png -------------------------------------------------------------------------------- /bookmanagement/jpg/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/3.png -------------------------------------------------------------------------------- /bookmanagement/jpg/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/4.png -------------------------------------------------------------------------------- /bookmanagement/jpg/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/5.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/6.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/7.png -------------------------------------------------------------------------------- /bookmanagement/jpg/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/8.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/9.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9281.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9281.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9359.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9359.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9360.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9362.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9362.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9363.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9363.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9365.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9365.jpg -------------------------------------------------------------------------------- /bookmanagement/jpg/IMG_9366.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/bookmanagement/jpg/IMG_9366.jpg -------------------------------------------------------------------------------- /miniSQL/README.md: -------------------------------------------------------------------------------- 1 | # MiniSQL 设计报告 2 | 3 | | 作者 | 学号 | 课程 | 专业 | 4 | | ------ | ---- | ---------- | ------------------------------------------- | 5 | | 肖振新 | * | 数据库系统 | 竺可桢学院交叉创新平台(计算机+自动化控制) | 6 | 7 | ### 程序运行截图 8 | 9 | 10 | 11 | ## 目录: 12 | 13 | [TOC] 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ## 概论 28 | 29 | 本MiniSQL数据库是仿照MySQL数据库交互界面开发的简易数据库引擎,支持查找、插入、删除等数据库的基本操作。设计过程全部由一人完成,所以没有具体的分工。开发语言采用python 3.7,并进行打包成无需额外依赖的二进制可执行程序,在Windows、macOS和Linux平台下均可运行。 30 | 31 | 本项目的GitHub地址:https://github.com/AlanShaw-GitHub/MiniSQL 32 | 33 | 功能/亮点: 34 | 35 | 1. 程序添加了登录模块,分为root管理员和普通用户两种,root可以**添加删除用户**(通过root权限的sys(username,password)表)。 36 | 2. 程序交互界面高度贴近MySQL和大多数Linux终端交互逻辑,**按键盘的“上”、“下”键可以定位到上、下一条语句**,十分方便。 37 | 3. 程序使用“lasy”的修改方式,即为了减少频繁写入磁盘的用时,所有的更改都暂时保存在内存中,不会写到本地文件,在键入“quit”命令退出本程序时,所有的更改才会被写入文件。任何非正常的退出都会导致此次所有更改的丢失,如果希望立即写入文件,**请敲入“commit”命令**,则所有的更改都会被立即写入文件。 38 | 4. **B+树索引**,在查找时优先检测是否存在在主码上的条件查询,如果没有,再检测是否存在在索引上的条件查询,然后其他。所以大部分查询时间复杂度都在log级别。详细的设计将会在后面一一介绍。 39 | 5. 类型char(N)中的**N没有大小限制**,多大都可以。同样,一个表也可以定义**无数个**属性,没任何限制。 40 | 6. 支持从文件中读取批量命令并执行,命令之间用分号分隔,**支持注释**(以#开头的行会被当作注释而不会被执行) 41 | 7. 提供基本的帮助文档,键入help或者?即可以查看支持的命令,键入“help 【命令】”即可以查看对应命令的帮助文档。 42 | 8. buffer manager采用**嵌套式json文件**存储,b+树有多高就有几层json嵌套。方便而又高效地完整保存了整个B+树的结构,详细设计会在后面介绍。 43 | 9. 较为完善的错误提示机制,使用了大量的检测点以及完整的错误捕捉机制,程序因此不会因为各种异常而退出。大量常见的语法错误都会检测到并且在终端中输出提示,告知用户语法错误的地方。 44 | 10. 简洁而不简单:本程序总代码只有一千多行,但是涵盖了所有数据库所必需的组件,尽可能的精简代码和提高操作效率,是本程序在设计时的首要目标。**每个操作的用时也都会随着结果一起打印出来**,供用户分析。 45 | 46 | ## 第一章 MiniSQL总体框架 47 | 48 | ### 第1.1节 MiniSQL实现功能分析 49 | 50 | 1. 总功能:允许用户通过字符界面输入 SQL 语句实现表的建立/删除;索引的建立/删除以及表记录的插入/删除/查找; 51 | 2. 数据类型:支持三种基本数据类型:INT,CHAR(N),FLOAT,**其中 CHAR(N)中的 N 没有大小限制**,多大都行。 52 | 3. 表定义:一个表最多可以定义**无数个**属性,各属性可以指定是否为 UNIQUE;支持单属性的 主键定义; 53 | 4. 索引的建立和删除:对于表的主属性自动建立B+树索引,对于声明为 UNIQUE 的属性可以 通过 SQL 语句由用户指定建立/删除 B+树索引(因此,所有的 B+树索引都是单属性单值的); 54 | 5. 查找记录:可以通过指定用 AND 连接的多个条件进行查询,支持**等值/不等值**查询和区间查询; 55 | 6. 插入和删除记录:支持每次一条记录的插入操作;支持每次一条或多条记录的删除操作。 56 | 57 | ### 第1.2节 MiniSQL系统体系结构 58 | 59 | 系统体系结构如下图所示,和设计报告模版中的略有不同,我将record manager整合到了catalog和index manager中。 60 | 61 | 62 | 63 | ### 第1.3节 MiniSQL设计语言和运行环境 64 | 65 | 设计语言:python 3.7 66 | 67 | 开发环境:macOS 10.14 Jetbrains PyCharm 2018.1 68 | 69 | ## 第二章 MiniSQL各模块实现的功能/接口 70 | 71 | ### 第2.1节 概论 72 | 73 | ### 第2.2节 Interpreter 实现功能/接口 74 | 75 | ##### Interpreter 模块直接与用户交互,主要实现以下功能: 76 | 77 | 1. 登录模块的基本接口,如果是管理员账户(默认账号root密码123456)登录则具有管理员权限,可以添加删除用户(在 API 层完成)。 78 | 2. 程序流程控制,即“启动并初始化、接收命令、处理命令、显示命令结果、循环、退出”流程。 79 | 3. 接收并解释用户输入的命令,生成命令的内部数据结构表示,调用 API 层提供的函数执行并显示执行结果。(错误语法处理在 API 层实现,Interpreter 模块只负责捕捉错误并提示在窗口。 80 | 4. 帮助信息的实现也在这个部分完成,用户可以在终端键入“help 【命令名】”来查看对应的帮助文档。 81 | 5. 对从文件加载批量命令的支持,其实就是从文件中读取命令,然后调用API层的函数来一句句解析这些语句,以#开头的行会被当作注释而不会被执行。 82 | 83 | ##### Interpreter 模块的接口 84 | 85 | Interpreter 模块主要是调用其他模块的接口,自己没有提供接口给其他模块。 86 | 87 | ### 第2.3节 API 实现功能/接口 88 | 89 | API 模块是整个系统的核心,其主要功能为提供执行 SQL 语句的接口,供 Interpreter 层调用。该接口以 Interpreter 层解释生成的命令内部表示为输入,根据 Catalog Manager 提供的信息确定执行规则,并调用 Record Manager、Index Manager 和 Catalog Manager 提供的相应接口进行执行,最后返回执行结果给 Interpreter 模块。 90 | 91 | API 模块主要是在做语法解析工作,并且也会调用其他模块来进行错误处理。 92 | 93 | ##### 主要实现功能: 94 | 95 | 主要就是对insert、select、delete、create、drop五个指令进行语法解析和错误处理,并调用 Record Manager、Index Manager 和 Catalog Manager 提供的接口实现数据库的更改操作 96 | 97 | ##### 模块接口 98 | 99 | 提供以下五个函数,全部为顶层的Interpreter 模块服务,执行五种不同的操作: 100 | 101 | 1. select:顾名思义。 102 | 2. insert:顾名思义。 103 | 3. delete:顾名思义。 104 | 4. create:顾名思义。 105 | 5. drop:顾名思义。 106 | 107 | 108 | 109 | ### 第2.4节 Catalog Manager 实现功能/接口 110 | 111 | ##### 实现功能 112 | 113 | Catalog Manager 负责管理数据库的所有模式信息,包括: 114 | 115 | 1. 数据库中所有表的定义信息,包括表的名称、表中字段(列)数、主键、定义在该 表上的索引。 116 | 2. 表中每个字段的定义信息,包括字段类型、是否唯一等。 117 | 3. 数据库中所有索引的定义,包括所属表、索引建立在那个字段上等。 118 | 119 | Catalog Manager 还必需提供访问及操作上述信息的接口,供 Interpreter 和 API 模块使用。 120 | 121 | ##### 模块接口 122 | 123 | 1. create_table:创建表的定义。 124 | 125 | 2. check_types_of_table:被Interpreter 模块调用,如果不满足则抛出一个异常。在insert一个记录时,该函数帮助检查values的值是否满足要求,比如values的数量和列数量是否相同、unique的值是否有重复(调用record manager检测)、char的宽度是否满足条件(小于N)。 126 | 127 | 3. exists_table:如果table已经存在,就产生一个异常,被Interpreter 模块在创建一个表的时候预先调用做检查。 128 | 129 | 4. not_exists_table:如果一个表不存在,就产生一个异常,被Interpreter 模块在删除一个表的时候预先调用做检查。 130 | 131 | 5. not_exists_index:如上同理。 132 | 133 | 6. exists_index:如上同理。 134 | 135 | 7. drop_table:删除一个表。 136 | 137 | 8. drop_index:删除一个索引。 138 | 139 | 9. create_index:创建一个索引。 140 | 141 | 10. check_select_statement:检查选择语句的正确性,被Interpreter 模块调用,如果不满足则抛出一个异常。在Interpreter 模块调用select相关操作之前做错误检查,主要检查选择的columns是否在表中有这些列,where语句提到的这些列是否存在、类型是否满足等等。 142 | 143 | 144 | ### 第2.5节 Record/Index Manager 实现功能/接口 145 | 146 | ##### 实现功能 147 | 148 | Index Manager负责 B+树索引的实现,实现B+树的创建和删除(由索引的定义与删除引起)、等值查找、插入键值、删除键值等操作,并对外提供相应的接口。 B+树中节点大小应与缓冲区的块大小相同,B+树的叉数由节点大小与索引键大小计算得到。 149 | 150 | ##### 模块接口 151 | 152 | 1. insert_into_table:将一个列插入b+树。 153 | 154 | 2. delete_from_table:将一个或多个列从b+树中删除,根据where语句。 155 | 156 | 3. select_from_table:从表中选择,并负责格式化输出,根据where语句。 157 | 158 | 4. create_table:创建一个空的b+树。 159 | 160 | 5. delete_table:删除一个表。 161 | 162 | 6. create_index:创建一个索引。 163 | 164 | 7. check_unique:检查该列的某个值是否已经在b+树中,是为Catalog Manager模块的check_types_of_table提供错误检查。 165 | 166 | 8. exist_user:检查sys系统表中是否存在该用户和正确的密码,为顶层interpreter模块服务,做用户登录模块。 167 | 168 | 169 | 170 | ### 第2.6节 Buffer/DB Files Manager 实现功能/接口 171 | 172 | 事实上,在设计时,buffer manager的设计内容都是集成在record和catalog代码中的,record和catalog代码直接实现了 __store__ 和 __load__ 函数,进行数据与磁盘之间的交互。 173 | 174 | ##### 实现功能 175 | 176 | Buffer Manager 负责缓冲区的管理,主要功能有: 177 | 178 | 1. 根据需要,读取指定的数据到系统缓冲区或将缓冲区中的数据写出到文件 179 | 2. 实现缓冲区的替换算法,当缓冲区满时选择合适的页进行替换 180 | 3. 记录缓冲区中各页的状态,如是否被修改过等 181 | 4. 提供缓冲区页的 pin 功能,及锁定缓冲区的页,不允许替换出去为提高磁盘 I/O 操作的效率,缓冲区与文件系统交互的单位是块,块的大小应为文件系统与磁盘交互单位的整数倍,一般可定为 4KB 或 8KB。 182 | 183 | DB Files 指构成数据库的所有数据文件,主要由记录数据文件、索引数据文件和Catalog 数据文件组成。同时还有写回文件和读取文件的功能 。 184 | 185 | ##### 模块接口 186 | 187 | ###### catalog部分存取 188 | 189 | catalog存取表、索引的信息,比如表名、列名、是否unique、主码等等信息,这部分的信息比较容易存取,提供以下函数: 190 | 191 | 1. __store__:存catalog信息。 192 | 2. __load__:从文件中取catalog信息。 193 | 194 | ###### index部分存取 195 | 196 | index存取b+树结构,这部分是比较难存取的,主要是树结构的复杂性和各种指针的问题,使得存取树时要格外小心。 197 | 198 | 1. __store__:存b+树信息。 199 | 200 | 2. __load__:取b+树信息。 201 | 202 | 203 | 204 | ## 第三章 MiniSQL各模块设计详解 205 | 206 | ### 第3.1节 概论 207 | 208 | ### 第3.2节 Interpreter设计详解 209 | 210 | 这个模块主要是调用了python的cmd模块,实现不间断的轮询用户输入并调用底层api进行处理。同时,还提供了用户登录模块、从文件中执行等模块。 211 | 212 | 加载和保存,其实调用底层api,传入当前路径。自己什么也没干: 213 | 214 | ```python 215 | def __initialize__(): 216 | CatalogManager.catalog.__initialize__(os.getcwd()) 217 | IndexManager.index.__initialize__(os.getcwd()) 218 | 219 | def __finalize__(): 220 | CatalogManager.catalog.__finalize__() 221 | IndexManager.index.__finalize__() 222 | ``` 223 | 224 | 从文件中执行,代码如下,先打开文件,然后用分号分隔命令,去掉以#开头的注释字段,然后一个个调用底层函数进行处理,最后提交修改到底层文件。: 225 | 226 | ```python 227 | def exec_from_file(filename): 228 | f = open(filename) 229 | text = f.read() 230 | f.close() 231 | comands = text.split(';') 232 | comands = [i.strip().replace('\n','') for i in comands] 233 | __initialize__() 234 | for comand in comands: 235 | if comand == '': 236 | continue 237 | if comand[0] == '#': 238 | continue 239 | if comand.split(' ')[0] == 'insert': 240 | try: 241 | APIManager.api.insert(comand[6:]) 242 | except Exception as e: 243 | print(str(e)) 244 | elif comand.split(' ')[0] == 'select': 245 | try: 246 | APIManager.api.select(comand[6:]) 247 | except Exception as e: 248 | print(str(e)) 249 | elif comand.split(' ')[0] == 'delete': 250 | try: 251 | APIManager.api.delete(comand[6:]) 252 | except Exception as e: 253 | print(str(e)) 254 | elif comand.split(' ')[0] == 'drop': 255 | try: 256 | APIManager.api.drop(comand[4:]) 257 | except Exception as e: 258 | print(str(e)) 259 | elif comand.split(' ')[0] == 'create': 260 | try: 261 | APIManager.api.create(comand[6:]) 262 | except Exception as e: 263 | print(str(e)) 264 | __finalize__() 265 | ``` 266 | 267 | 用户登录模块,除了管理员账号,其他的账号全部都从sys表中读取确认,使用底层的IndexManager.index.exist_user函数,并设置APIManager.api.__root标记表明当前是什么状态: 268 | 269 | ```python 270 | if len(sys.argv) < 5: 271 | print('ERROR : Unsupported syntax, please login.\n',errortext) 272 | sys.exit() 273 | if sys.argv[1] != '-u' or sys.argv[3] != '-p': 274 | print('ERROR : Unsupported syntax, please login.\n',errortext) 275 | sys.exit() 276 | __initialize__() 277 | if sys.argv[2] == 'root' and sys.argv[4] == '123456': 278 | APIManager.api.__root = True 279 | elif IndexManager.index.exist_user(username=sys.argv[2],password=sys.argv[4]): 280 | APIManager.api.__root = False 281 | else: 282 | print('Error : username or password is not correct,please ' 283 | 'check and login again.\n',errortext) 284 | sys.exit() 285 | if len(sys.argv) > 5: 286 | if sys.argv[5] != '-execfile': 287 | print('ERROR : Unsupported syntax.\n',errortext) 288 | exec_from_file(sys.argv[6]) 289 | sys.exit() 290 | ``` 291 | 292 | 继承自cmd模块的类定义,删掉了很多代码,用(...somecode)代替: 293 | 294 | ```python 295 | class miniSQL(cmd.Cmd): 296 | intro = 'Welcome to the MiniSQL database server.\nType help or ? to list commands.\n' 297 | def do_select(self,args): 298 | try: 299 | APIManager.api.select(args.replace(';','')) 300 | except Exception as e: 301 | print(str(e)) 302 | ...somecode 303 | 304 | def do_commit(self,args): 305 | time_start = time.time() 306 | __finalize__() 307 | time_end = time.time() 308 | print('Modifications has been commited to local files,',end='') 309 | print(" time elapsed : %fs." % (time_end - time_start)) 310 | 311 | def do_quit(self,args): 312 | __finalize__() 313 | print('Goodbye.') 314 | sys.exit() 315 | 316 | def emptyline(self): 317 | pass 318 | 319 | def default(self, line): 320 | print('Unrecognized command.\nNo such symbol : %s' % line) 321 | 322 | def help_commit(self): 323 | print() 324 | text = "To reduce file transfer's time, this SQL server is designed to "+\ 325 | "'lasy' write changes to local files, which means it will not store changes "+\ 326 | "until you type 'quit' to normally exit the server. if this server exit "+\ 327 | "unnormally, all changes will be lost. If you want to write changes to "+\ 328 | "local files immediately, please use 'commit' command.\n" 329 | print(text) 330 | 331 | def help_quit(self): 332 | print() 333 | print('Quit the program and write changes to local file.') 334 | 335 | def help_select(self): 336 | print() 337 | print("select * from student;") 338 | print("select num from student where num >= 2 and num < 10 and gender = 'male';") 339 | 340 | ...somecode 341 | ``` 342 | 343 | 344 | 345 | ### 第3.3节 API设计详解 346 | 347 | api模块主要是实现了五个函数(select、insert等),调用catalog和index的函数操作,并自己做一些语法上的错误检查,从代码中也可以看到,对于很多的格式错误,我都提供了监测点并抛出错误给顶层模块,抛出错误时,也会在最前面提示是哪个模块抛出的错误。由于篇幅有限,下面只展示两个函数的实现: 348 | 349 | ```python 350 | def insert(args): 351 | time_start = time.time() 352 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 353 | lists = args.split(' ') 354 | if lists[0] != 'into': 355 | raise Exception("API Module : Unrecoginze symbol for command 'insert',it should be 'into'.") 356 | table = lists[1] 357 | if table == 'sys' and __root == False: 358 | raise Exception("ERROR : Can't modify 'sys' table, you are not root.") 359 | if lists[2] != 'values': 360 | raise Exception("API Module : Unrecoginze symbol for command 'insert',it should be 'values'.") 361 | value = args[re.search('\(',args).start()+1:find_last(args,')')] 362 | values = value.split(',') 363 | CatalogManager.catalog.not_exists_table(table) 364 | CatalogManager.catalog.check_types_of_table(table,values) 365 | IndexManager.index.insert_into_table(table,values) 366 | time_end = time.time() 367 | print(" time elapsed : %fs." % (time_end-time_start)) 368 | 369 | def delete(args): 370 | time_start = time.time() 371 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 372 | lists = args.split(' ') 373 | if lists[0] != 'from': 374 | raise Exception("API Module : Unrecoginze symbol for command 'delete',it should be 'from'.") 375 | table = lists[1] 376 | if table == 'sys' and __root == False: 377 | raise Exception("ERROR : Can't modify 'sys' table, you are not root.") 378 | CatalogManager.catalog.not_exists_table(table) 379 | if len(lists) == 2: 380 | IndexManager.index.delete_from_table(table,[]) 381 | else: 382 | IndexManager.index.delete_from_table(table,lists[3:]) 383 | time_end = time.time() 384 | print(" time elapsed : %fs." % (time_end-time_start)) 385 | ``` 386 | 387 | 388 | 389 | ### 第3.4节 Catalog Manager设计详解 390 | 391 | catalog模块存取表的信息,定义的数据结构为: 392 | 393 | ```python 394 | tables = {} 395 | class table_instance(): 396 | def __init__(self,table_name,primary_key = 0): 397 | self.table_name = table_name 398 | self.primary_key = primary_key 399 | columns =[] 400 | 401 | class column(): 402 | def __init__(self,column_name,is_unique,type = 'char',length = 16): 403 | self.column_name = column_name 404 | self.is_unique = is_unique 405 | self.type = type 406 | self.length = length 407 | ``` 408 | 409 | tables是所有的表的集合,是一个字典,key是表名,value是一个指向class table_instance的指针,代表了一个表实例对象。一个表中存了表名、主键的名字,以及一个列的集合。列又是个class column实例对象,他存了列名、是否unique、类型是什么以及如果是char类型的话,长度是多少。 410 | 411 | 下面简单给出一个表的创建对应的函数,其他琐碎的函数就不给出了。 412 | 413 | ```python 414 | def create_table(table,statement): 415 | global tables 416 | primary_place = re.search('primary key *\(',statement).end() 417 | primary_place_end = re.search('\)',statement[primary_place:]).start() 418 | primary_key = statement[primary_place:][:primary_place_end].strip() 419 | cur_table = table_instance(table,primary_key) 420 | lists = statement.split(',') 421 | columns = [] 422 | for cur_column_statement in lists[0:len(lists)-1]: 423 | cur_column_statement = cur_column_statement.strip() 424 | cur_lists = cur_column_statement.split(' ') 425 | is_unique = False 426 | type = 'char' 427 | column_name = cur_lists[0] 428 | if re.search('unique',concat_list(cur_lists[1:])) or column_name == primary_key: 429 | is_unique = True 430 | if re.search('char',concat_list(cur_lists[1:])): 431 | length_start = re.search('\(',concat_list(cur_lists[1:])).start()+1 432 | length_end = re.search('\)', concat_list(cur_lists[1:])).start() 433 | length = int(concat_list(cur_lists[1:])[length_start:length_end]) 434 | 435 | elif re.search('int', concat_list(cur_lists[1:])): 436 | length = 0 437 | type = 'int' 438 | elif re.search('float', concat_list(cur_lists[1:])): 439 | length = 0 440 | type = 'float' 441 | else: 442 | raise Exception("Catalog Module : Unsupported type for %d." % column_name) 443 | columns.append(column(column_name,is_unique,type,length)) 444 | cur_table.columns = columns 445 | seed = False 446 | for index,__column in enumerate(cur_table.columns): 447 | if __column.column_name == cur_table.primary_key: 448 | cur_table.primary_key = index 449 | seed = True 450 | break 451 | if seed == False: 452 | raise Exception("Catalog Module : primary_key '%s' not exists." 453 | % cur_table.primary_key) 454 | 455 | tables[table] = cur_table 456 | ``` 457 | 458 | 459 | 460 | ### 第3.5节 Record/Index Manager设计详解 461 | 462 | 这个模块是整个数据库的关键部分。这个模块主要实现了b+树的增删查找。这部分也是整个程序代码量最大的部分。 463 | 464 | 向b+树中插入的代码:(先调用了find_leaf_place找到要插入的叶节点位置,如果能插下去,那么就调用insert_into_leaf插入,完事。如果不能,就先调用insert_into_leaf插入,然后拆分,然后调用insert_into_parent递归的向父节点更新。 465 | 466 | ```python 467 | def insert_into_table(table,__values): 468 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 469 | if col.type == 'int': 470 | __values[index] = int(__values[index]) 471 | elif col.type == 'char': 472 | __values[index] = __values[index].strip().replace("'",'') 473 | elif col.type == 'float': 474 | __values[index] = float(__values[index]) 475 | 476 | cur_node = tables[table] 477 | __primary_key = CatalogManager.catalog.tables[table].primary_key 478 | # __primary_key = 0 479 | if len(cur_node.keys) == 0: 480 | # new tree 481 | cur_node.keys.append(__values[__primary_key]) 482 | cur_node.pointers.append(__values) 483 | cur_node.pointers.append('') 484 | print('Successfully insert into table %s,' % table,end='') 485 | return 486 | 487 | cur_node = find_leaf_place(table,__values[__primary_key]) 488 | if len(cur_node.keys) < N - 1: 489 | insert_into_leaf(cur_node,__values[__primary_key],__values) 490 | 491 | else: 492 | insert_into_leaf(cur_node,__values[__primary_key],__values) 493 | new_node = node(True,[],[]) 494 | tmp_keys = cur_node.keys 495 | tmp_pointers = cur_node.pointers 496 | cur_node.keys = [] 497 | cur_node.pointers = [] 498 | for i in range(math.ceil(N/2)): 499 | cur_node.keys.append(tmp_keys.pop(0)) 500 | cur_node.pointers.append(tmp_pointers.pop(0)) 501 | for i in range(N - math.ceil(N/2)): 502 | new_node.keys.append(tmp_keys.pop(0)) 503 | new_node.pointers.append(tmp_pointers.pop(0)) 504 | cur_node.pointers.append(new_node) 505 | new_node.pointers.append(tmp_pointers.pop(0)) 506 | insert_into_parent(table,cur_node,new_node.keys[0],new_node) 507 | 508 | print('Successfully insert into table %s,' % table,end='') 509 | ``` 510 | 511 | 删除的代码:(删除时先拆分用and连接的复杂的条件,这部分和select做的事情相似。然后再看看这些条件里面有没有主码的条件,如果有,那么查找就可以在log时间内完成了(都是调用find_leaf_place_with_condition完成)。然后对于满足的所有条件,调用check_conditions检查所有的条件是否满足,如果满足,就删掉他们,然后调用maintain_B_plus_tree_after_delete来保持删除后的b+树结构) 512 | 513 | ```python 514 | def delete_from_table(table,statement): 515 | # delete rows from table according to the statement's condition 516 | # usage : find_leaf_place_with_condition(table, column, value,condition) 517 | if len(statement) == 0: 518 | tables[table] = node(True,[],[],'') 519 | print("Successfully delete all entrys from table '%s'," % table,end='') 520 | else: 521 | columns = {} 522 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 523 | columns[col.column_name] = index 524 | __primary_key = CatalogManager.catalog.tables[table].primary_key 525 | # __primary_key = 0 526 | # columns = {'num':0,'val':1} 527 | 528 | conditions = [] 529 | tmp = [] 530 | pos = 1 531 | for i in statement: 532 | if i == 'and': 533 | conditions.append(tmp) 534 | tmp = [] 535 | pos = 1 536 | continue 537 | if pos == 1: 538 | tmp.append(columns[i]) 539 | elif pos == 3: 540 | if CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'char': 541 | tmp.append(i.strip().replace("'", '')) 542 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'int': 543 | tmp.append(int(i)) 544 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'float': 545 | tmp.append(float(i)) 546 | else: 547 | tmp.append(i) 548 | pos = pos + 1 549 | conditions.append(tmp) 550 | times = 0 551 | while True: 552 | nodes = find_leaf_place_with_condition(table, 553 | conditions[0][0],conditions[0][2],conditions[0][1]) 554 | for col in conditions: 555 | if col[0] == __primary_key: 556 | nodes = find_leaf_place_with_condition(table,col[0],col[2],col[1]) 557 | break 558 | 559 | if len(nodes) == 0: 560 | break 561 | seed = False 562 | for __node in nodes: 563 | if seed == True: 564 | break 565 | for index,leaf in enumerate(__node.pointers[0:-1]): 566 | if check_conditions(leaf,conditions): 567 | __node.pointers.pop(index) 568 | __node.keys.pop(index) 569 | maintain_B_plus_tree_after_delete(table,__node) 570 | times = times + 1 571 | seed = True 572 | break 573 | if seed == False: 574 | break 575 | print("Successfully delete %d entry(s) from table '%s'," % (times,table),end='') 576 | ``` 577 | 578 | 其中maintain_B_plus_tree_after_delete比较有意思,它是一个递归函数,会从叶节点开始递归的保持b+树的结构: 579 | 580 | ```python 581 | def maintain_B_plus_tree_after_delete(table,__node): 582 | global N 583 | if __node.parent == '' and len(__node.pointers) == 1: 584 | tables[table] = __node.pointers[0] 585 | elif ((len(__node.pointers) < math.ceil(N/2) and __node.is_leaf == False) or 586 | (len(__node.keys) < math.ceil((N-1)/2) and __node.is_leaf == True) ) \ 587 | and __node.parent != '': 588 | previous = False 589 | other_node = node(True,[],[]) 590 | K = '' 591 | __index = 0 592 | for index, i in enumerate(__node.parent.pointers): 593 | if i == __node: 594 | if index == len(__node.parent.pointers) - 1: 595 | other_node = __node.parent.pointers[-2] 596 | previous = True 597 | K = __node.parent.keys[index - 1] 598 | else: 599 | K = __node.parent.keys[index] 600 | other_node = __node.parent.pointers[index + 1] 601 | __index = index + 1 602 | 603 | if (other_node.is_leaf == True and len(other_node.keys)+len(__node.keys) < N) or \ 604 | (other_node.is_leaf == False and len(other_node.pointers) + 605 | len(__node.pointers) <= N): 606 | if previous == True: 607 | if other_node.is_leaf == False: 608 | other_node.pointers = other_node.pointers + __node.pointers 609 | other_node.keys = other_node.keys + [K] + __node.keys 610 | for __node__ in __node.pointers: 611 | __node__.parent = other_node 612 | else: 613 | other_node.pointers = other_node.pointers[0:-1] 614 | other_node.pointers = other_node.pointers + __node.pointers 615 | other_node.keys = other_node.keys + __node.keys 616 | __node.parent.pointers = __node.parent.pointers[0:-1] 617 | __node.parent.keys = __node.parent.keys[0:-1] 618 | maintain_B_plus_tree_after_delete(table,__node.parent) 619 | else: 620 | if other_node.is_leaf == False: 621 | __node.pointers = __node.pointers + other_node.pointers 622 | __node.keys = __node.keys + [K] + other_node.keys 623 | for __node__ in other_node.pointers: 624 | __node__.parent = __node 625 | else: 626 | __node.pointers = __node.pointers[0:-1] 627 | __node.pointers = __node.pointers + other_node.pointers 628 | __node.keys = __node.keys + other_node.keys 629 | __node.parent.pointers.pop(__index) 630 | __node.parent.keys.pop(__index-1) 631 | maintain_B_plus_tree_after_delete(table,__node.parent) 632 | else: 633 | if previous == True: 634 | if other_node.is_leaf == True: 635 | __node.keys.insert(0,other_node.keys.pop(-1)) 636 | __node.pointers.insert(0,other_node.pointers.pop(-2)) 637 | __node.parent.keys[-1] = __node.keys[0] 638 | else: 639 | __tmp = other_node.pointers.pop(-1) 640 | __tmp.parent = __node 641 | __node.pointers.insert(0,__tmp) 642 | __node.keys.insert(0,__node.parent.keys[-1]) 643 | __node.parent.keys[-1] = other_node.keys.pop(-1) 644 | else: 645 | if other_node.is_leaf == True: 646 | __node.keys.insert(-1,other_node.keys.pop(0)) 647 | __node.pointers.insert(-2,other_node.pointers.pop(0)) 648 | __node.parent.keys[__index-1] = other_node.keys[0] 649 | else: 650 | __tmp = other_node.pointers.pop(0) 651 | __tmp.parent = __node 652 | __node.pointers.insert(-1,__tmp) 653 | __node.keys.insert(-1,__node.parent.keys[__index-1]) 654 | __node.parent.keys[__index-1] = other_node.keys.pop(0) 655 | 656 | ``` 657 | 658 | 然后就是select函数了,这部分的代码比较多,主要是要先看一下查询条件where子句有没有,如果没有就说默认返回所有的记录。如果有的话,那么就像上面的delete做的那样对where子句进行解析、查找(当然也要看一下有没有主码属性,有的话就优先查找主码属性),然后把这些记录保存在缓冲区,然后再看一要输出哪些column,如果是*就是默认输出所有的column,如果出现不存在的column会报错。然后代码的最后是优雅地格式化输出所有的列。 659 | 660 | ```python 661 | def select_from_table(table,__conditions = '',__columns = ''): 662 | results = [] 663 | columns = {} 664 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 665 | columns[col.column_name] = index 666 | __primary_key = CatalogManager.catalog.tables[table].primary_key 667 | if len(tables[table].keys) == 0: 668 | pass 669 | else: 670 | if __conditions != '': 671 | conditions = [] 672 | statement = __conditions.split(' ') 673 | tmp = [] 674 | pos = 1 675 | for i in statement: 676 | if i == 'and': 677 | conditions.append(tmp) 678 | tmp = [] 679 | pos = 1 680 | continue 681 | if pos == 1: 682 | tmp.append(columns[i]) 683 | elif pos == 3: 684 | if CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'char': 685 | tmp.append(i.strip().replace("'",'')) 686 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'int': 687 | tmp.append(int(i)) 688 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'float': 689 | tmp.append(float(i)) 690 | else: 691 | tmp.append(i) 692 | pos = pos + 1 693 | conditions.append(tmp) 694 | nodes = find_leaf_place_with_condition(table, 695 | conditions[0][0], conditions[0][2], conditions[0][1]) 696 | for col in conditions: 697 | if col[0] == __primary_key: 698 | nodes = find_leaf_place_with_condition(table, col[0], col[2], col[1]) 699 | break 700 | for __node in nodes: 701 | for pointer in __node.pointers[0:-1]: 702 | if check_conditions(pointer,conditions): 703 | results.append(pointer) 704 | else: 705 | first_leaf_node = tables[table] 706 | while first_leaf_node.is_leaf != True: 707 | first_leaf_node = first_leaf_node.pointers[0] 708 | while True: 709 | for i in first_leaf_node.pointers[0:-1]: 710 | results.append(i) 711 | if first_leaf_node.pointers[-1] != '': 712 | first_leaf_node = first_leaf_node.pointers[-1] 713 | else: 714 | break 715 | 716 | if __columns == '*': 717 | __columns_list = list(columns.keys()) 718 | __columns_list_num = list(columns.values()) 719 | else: 720 | __columns_list_num = [columns[i.strip()] for i in __columns.split(',')] 721 | __columns_list = [i.strip() for i in __columns.split(',')] 722 | 723 | print('-' * (17 * len(__columns_list_num) + 1)) 724 | for i in __columns_list: 725 | if len(str(i)) > 14: 726 | output = str(i)[0:14] 727 | else: 728 | output = str(i) 729 | print('|',output.center(15),end='') 730 | print('|') 731 | print('-' * (17 * len(__columns_list_num) + 1)) 732 | for i in results: 733 | for j in __columns_list_num: 734 | if len(str(i[j])) > 14: 735 | output = str(i[j])[0:14] 736 | else: 737 | output = str(i[j]) 738 | print('|',output.center(15) ,end='') 739 | print('|') 740 | print('-' * (17 * len(__columns_list_num) + 1)) 741 | print("Returned %d entrys," % len(results),end='') 742 | ``` 743 | 744 | 745 | 746 | ### 第3.6节 Buffer/DB Files Manager设计详解 747 | 748 | index模块的b+树格式的存取方法是本程序创新的一个地方,使用嵌套式json文件格式递归地存取b+树结构,**无损完美优雅**的保存了b+树的所有信息: 749 | 750 | ```python 751 | __last_leaf_pointer = '' 752 | def __load__(): 753 | global __last_leaf_pointer 754 | f = open(os.path.join(path,'dbfiles/index_files/tables_B-plus_tree.msql')) 755 | json_tables = json.loads(f.read()) 756 | f.close() 757 | for table in json_tables.items(): 758 | if len(table[1]['keys']) == 0: 759 | tables[table[0]] = node(True,[],[]) 760 | continue 761 | tables[table[0]] = node(table[1]['is_leaf'],table[1]['keys'],table[1]['pointers'],'') 762 | if not tables[table[0]].is_leaf: 763 | tables[table[0]].pointers = recursive_load_node(table[1]['pointers'],tables[table[0]]) 764 | 765 | def recursive_load_node(pointer_list,parent): 766 | global __last_leaf_pointer 767 | lists = [] 768 | for pointer in pointer_list: 769 | new_node = node(pointer['is_leaf'],pointer['keys'],pointer['pointers'],parent) 770 | lists.append(new_node) 771 | if not lists[-1].is_leaf: 772 | new_node.pointers = recursive_load_node(pointer['pointers'],lists[-1]) 773 | else: 774 | if __last_leaf_pointer == '': 775 | __last_leaf_pointer = new_node 776 | else: 777 | __last_leaf_pointer.pointers.append(new_node) 778 | __last_leaf_pointer = new_node 779 | return lists 780 | 781 | def __store__(): 782 | global path 783 | __tables = {} 784 | for table in tables.items(): 785 | __tables[table[0]] = recursive_store_node(table[1]) 786 | f = open(os.path.join(path,'dbfiles/index_files/tables_B-plus_tree.msql'),'w') 787 | json_tables = json.dumps(__tables) 788 | f.write(json_tables) 789 | f.close() 790 | 791 | def recursive_store_node(node): 792 | cur_node = {} 793 | cur_node['is_leaf'] = node.is_leaf 794 | cur_node['keys'] = node.keys 795 | if node.is_leaf == True and node.pointers[-1] != '': 796 | cur_node['pointers'] = node.pointers[0:-1] 797 | elif node.is_leaf == True and node.pointers[-1] == '': 798 | cur_node['pointers'] = node.pointers 799 | else: 800 | cur_node['pointers'] = [] 801 | for __node in node.pointers: 802 | cur_node['pointers'].append(recursive_store_node(__node)) 803 | return cur_node 804 | ``` 805 | 806 | 文件最终存储效果,多层嵌套,**b+树有多高就有几层嵌套**: 807 | 808 | ```json 809 | {"sys": {"is_leaf": true, "keys": ["alan"], "pointers": [["alan", "123456"], ""]}, "student": {"is_leaf": false, "keys": [7], "pointers": [{"is_leaf": false, "keys": [3, 5], "pointers": [{"is_leaf": true, "keys": [1, 2], "pointers": [[1, "Alan", "male", "2017.9.1"], [2, "rose", "female", "2016.9.1"]]}, {"is_leaf": true, "keys": [3, 4], "pointers": [[3, "Robert", "male", "2016.9.1"], [4, "jack", "male", "2015.9.1"]]}, {"is_leaf": true, "keys": [5, 6], "pointers": [[5, "jason", "male", "2015.9.1"], [6, "Hans", "female", "2015.9.1"]]}]}, {"is_leaf": false, "keys": [9], "pointers": [{"is_leaf": true, "keys": [7, 8], "pointers": [[7, "rosa", "male", "2014.9.1"], [8, "messi", "female", "2013.9.1"]]}, {"is_leaf": true, "keys": [9, 10, 11], "pointers": [[9, "Neymar", "male", "2013.9.1"], [10, "Christ", "male", "2011.9.1"], [11, "shaw", "female", "2010.9.1"], ""]}]}]}} 810 | ``` 811 | 812 | 至于catalog模块的表信息存取就比较容易了,简单操作,也是使用json格式存储,其中每个column有三个字段,分别为(是否unique、类型、如果是char类型那么最大宽度是多少): 813 | 814 | ```json 815 | {"sys": {"primary_key": 0, "columns": {"username": [true, "char", 16], "password": [false, "char", 16]}}, "student": {"primary_key": 0, "columns": {"ID": [true, "int", 0], "name": [false, "char", 10], "gender": [false, "char", 10], "enroll_date": [false, "char", 10]}}} 816 | ``` 817 | 818 | 相应的代码: 819 | 820 | ```python 821 | def __load__(): 822 | f = open(os.path.join(path,'dbfiles/catalog_files/tables_catalog.msql')) 823 | json_tables = json.loads(f.read()) 824 | for table in json_tables.items(): 825 | __table = table_instance(table[0],table[1]['primary_key']) 826 | columns = [] 827 | for __column in table[1]['columns'].items(): 828 | columns.append(column(__column[0], 829 | __column[1][0],__column[1][1],__column[1][2])) 830 | __table.columns = columns 831 | tables[table[0]] = __table 832 | f.close() 833 | f = open(os.path.join(path, 'dbfiles/catalog_files/indexs_catalog.msql')) 834 | json_indexs = f.read() 835 | json_indexs = json.loads(json_indexs) 836 | for index in json_indexs.items(): 837 | indexs[index[0]] = index[1] 838 | f.close() 839 | 840 | def __store__(): 841 | __tables = {} 842 | for items in tables.items(): 843 | definition = {} 844 | definition['primary_key'] = items[1].primary_key 845 | __columns = {} 846 | for i in items[1].columns: 847 | __columns[i.column_name] = [i.is_unique,i.type,i.length] 848 | definition['columns'] = __columns 849 | __tables[items[0]] = definition 850 | json_tables = json.dumps(__tables) 851 | f = open(os.path.join(path,'dbfiles/catalog_files/tables_catalog.msql'),'w') 852 | f.write(json_tables) 853 | f.close() 854 | f = open(os.path.join(path, 'dbfiles/catalog_files/indexs_catalog.msql'), 'w') 855 | f.write(json.dumps(indexs)) 856 | f.close() 857 | ``` 858 | 859 | 860 | 861 | ## 第四章 MiniSQL系统测试 862 | 863 | 首先,尝试登录,故意输错格式或者账号密码,会提示错误是什么,并且提示正确的格式是什么样子的: 864 | 865 | 866 | 867 | 然后,根据提示的正确格式,我们可以进行正确的登录了,初始账号密码为root和123456(这是管理员账号),我们先从文件中读取批量执行指令,文件中的命令如下所示: 868 | 869 | ```mysql 870 | create table student (ID int, name char(10),gender char(10),enroll_date char(10),primary key(ID)); 871 | insert into student values ( 00001,'Alan','male','2017.9.1'); 872 | insert into student values ( 00002,'rose','female','2016.9.1'); 873 | insert into student values ( 00003,'Robert','male','2016.9.1'); 874 | insert into student values ( 00004,'jack','male','2015.9.1'); 875 | insert into student values ( 00005,'jason','male','2015.9.1'); 876 | insert into student values ( 00006,'Hans','female','2015.9.1'); 877 | insert into student values ( 00007,'rosa','male','2014.9.1'); 878 | insert into student values ( 00008,'messi','female','2013.9.1'); 879 | insert into student values ( 00009,'Neymar','male','2013.9.1'); 880 | insert into student values ( 00010,'Christ','male','2011.9.1'); 881 | insert into student values ( 00011,'shaw','female','2010.9.1'); 882 | ``` 883 | 884 | 执行该文件的内容,输出如下所示: 885 | 886 | 887 | 888 | 好了,我们现在直接登录来操作,输入正确的账号密码,登录成功,提示一些信息: 889 | 890 | 891 | 892 | 我们来管理用户,管理方法是在用户表sys中插入删除用户,然后这些用户可以以普通模式登录数据库,这些用户不具有写(但有读)sys表的权限。一些操作如下图所示(注意,即使是管理员也不具有删除sys表的权限,因为这是系统必须表,不能被删除): 893 | 894 | 895 | 896 | 现在我们可以使用新添加的“shaw”用户进行登录了,当然,他没有写sys表的权限: 897 | 898 | 899 | 900 | 好了,用户管理部分就到这里,下面我们进行select操作,不带任何参数就是选择所有的,也进行复杂的select条件查询,连接多个and表达式(中途有个小插曲,输错了列的名字,可以看到程序提供的较为完整的错误检查也准确的定位到了错误并输出): 901 | 902 | 903 | 904 | 然后,我们进行delete操作,不提供where语句就是默认删除所有的entry: 905 | 906 | 907 | 908 | 当然,我们也可以进行复杂的where表达式来批量删除,下面把性别为female的都删除(注意,此处提供的where表达式解析和select的基本一致,同样支持复杂的and连接多个表达式): 909 | 910 | 911 | 912 | 创建一个新的表然后添加一些entry: 913 | 914 | 915 | 916 | 删除一个表,这时候再尝试使用该表就会报错,因为他已经不存在了,当然,这时候想要后悔可以强制退出程序,之前说过的,这样所有的更改不会被写入磁盘文件: 917 | 918 | 919 | 920 | 这时候,如果想要提交修改,使用commit命令,可以看到,用时是其他在内存上直接操作的10-100倍,已经达到了毫秒级,所以本程序使用lasy store来提高效率是十分有意义的: 921 | 922 | 923 | 924 | 创建索引也很简单,可以看到错误提示机制也很完善,输入格式错误是会提示的: 925 | 926 | 927 | 928 | 删除索引也很简单,如果尝试删除不存在的索引,也会报错。 929 | 930 | 931 | 932 | 最后,敲入quit退出程序,同样,修改会被写进内存: 933 | 934 | 935 | 936 | -------------------------------------------------------------------------------- /miniSQL/jpg/1 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/1 2.jpg -------------------------------------------------------------------------------- /miniSQL/jpg/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/1.jpg -------------------------------------------------------------------------------- /miniSQL/jpg/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/1.png -------------------------------------------------------------------------------- /miniSQL/jpg/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/10.png -------------------------------------------------------------------------------- /miniSQL/jpg/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/11.png -------------------------------------------------------------------------------- /miniSQL/jpg/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/12.png -------------------------------------------------------------------------------- /miniSQL/jpg/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/13.png -------------------------------------------------------------------------------- /miniSQL/jpg/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/14.png -------------------------------------------------------------------------------- /miniSQL/jpg/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/15.png -------------------------------------------------------------------------------- /miniSQL/jpg/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/2.png -------------------------------------------------------------------------------- /miniSQL/jpg/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/3.png -------------------------------------------------------------------------------- /miniSQL/jpg/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/4.png -------------------------------------------------------------------------------- /miniSQL/jpg/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/5.png -------------------------------------------------------------------------------- /miniSQL/jpg/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/6.png -------------------------------------------------------------------------------- /miniSQL/jpg/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/7.png -------------------------------------------------------------------------------- /miniSQL/jpg/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/8.png -------------------------------------------------------------------------------- /miniSQL/jpg/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanShaw-GitHub/MiniSQL/101a660c38c7fe5c96b504b40de982cd27f9807c/miniSQL/jpg/9.png -------------------------------------------------------------------------------- /miniSQL/miniSQL/APIManager/api.py: -------------------------------------------------------------------------------- 1 | import re 2 | import CatalogManager.catalog 3 | import IndexManager.index 4 | import time 5 | 6 | __root = True 7 | def select(args): 8 | time_start = time.time() 9 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 10 | lists = args.split(' ') 11 | start_from = re.search('from', args).start() 12 | end_from = re.search('from', args).end() 13 | columns = args[0:start_from].strip() 14 | if re.search('where', args): 15 | start_where = re.search('where', args).start() 16 | end_where = re.search('where', args).end() 17 | table = args[end_from+1:start_where].strip() 18 | conditions = args[end_where+1:].strip() 19 | else: 20 | table = args[end_from+1:].strip() 21 | conditions = '' 22 | CatalogManager.catalog.not_exists_table(table) 23 | CatalogManager.catalog.check_select_statement(table,conditions,columns) 24 | IndexManager.index.select_from_table(table,conditions,columns) 25 | time_end = time.time() 26 | print(" time elapsed : %fs." % (time_end-time_start)) 27 | 28 | 29 | def create(args): 30 | time_start = time.time() 31 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 32 | lists = args.split(' ') 33 | if lists[0] == 'table': 34 | start_on = re.search('table', args).end() 35 | start = re.search('\(', args).start() 36 | end = find_last(args,')') 37 | table = args[start_on:start].strip() 38 | statement = args[start + 1:end].strip() 39 | CatalogManager.catalog.exists_table(table) 40 | IndexManager.index.create_table(table,statement) 41 | CatalogManager.catalog.create_table(table,statement) 42 | 43 | elif lists[0] == 'index': 44 | index_name = lists[1] 45 | if lists[2] != 'on': 46 | raise Exception("API Module : Unrecoginze symbol for command 'create index',it should be 'on'.") 47 | start_on = re.search('on',args).start() 48 | start = re.search('\(',args).start() 49 | end = find_last(args, ')') 50 | table = args[start_on:start].strip() 51 | column = args[start+1:end].strip() 52 | CatalogManager.catalog.exists_index(index_name) 53 | CatalogManager.catalog.create_index(index_name,table,column) 54 | IndexManager.index.create_index(index_name,table,column) 55 | 56 | else: 57 | raise Exception("API Module : Unrecoginze symbol for command 'create',it should be 'table' or 'index'.") 58 | time_end = time.time() 59 | print("Successfully create table '%s', time elapsed : %fs." 60 | % (table,time_end - time_start)) 61 | 62 | def drop(args): 63 | time_start = time.time() 64 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 65 | if args[0:5] == 'table': 66 | table = args[6:].strip() 67 | if table == 'sys': 68 | raise Exception("ERROR : Can't delete 'sys' table, it is necessary for MiniSQL server to run.") 69 | CatalogManager.catalog.not_exists_table(table) 70 | CatalogManager.catalog.drop_table(table) 71 | IndexManager.index.delete_table(table) 72 | time_end = time.time() 73 | print("Successfully delete table '%s', time elapsed : %fs." % (table,time_end - time_start)) 74 | 75 | elif args[0:5] == 'index': 76 | index = args[6:].strip() 77 | CatalogManager.catalog.not_exists_index(index) 78 | CatalogManager.catalog.drop_index(index) 79 | 80 | else: 81 | raise Exception("API Module : Unrecoginze symbol for command 'drop',it should be 'table' or 'index'.") 82 | 83 | 84 | def insert(args): 85 | time_start = time.time() 86 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 87 | lists = args.split(' ') 88 | if lists[0] != 'into': 89 | raise Exception("API Module : Unrecoginze symbol for command 'insert',it should be 'into'.") 90 | table = lists[1] 91 | if table == 'sys' and __root == False: 92 | raise Exception("ERROR : Can't modify 'sys' table, you are not root.") 93 | if lists[2] != 'values': 94 | raise Exception("API Module : Unrecoginze symbol for command 'insert',it should be 'values'.") 95 | value = args[re.search('\(',args).start()+1:find_last(args,')')] 96 | values = value.split(',') 97 | CatalogManager.catalog.not_exists_table(table) 98 | CatalogManager.catalog.check_types_of_table(table,values) 99 | IndexManager.index.insert_into_table(table,values) 100 | time_end = time.time() 101 | print(" time elapsed : %fs." % (time_end-time_start)) 102 | 103 | def delete(args): 104 | time_start = time.time() 105 | args = re.sub(r' +', ' ', args).strip().replace('\u200b','') 106 | lists = args.split(' ') 107 | if lists[0] != 'from': 108 | raise Exception("API Module : Unrecoginze symbol for command 'delete',it should be 'from'.") 109 | table = lists[1] 110 | if table == 'sys' and __root == False: 111 | raise Exception("ERROR : Can't modify 'sys' table, you are not root.") 112 | CatalogManager.catalog.not_exists_table(table) 113 | if len(lists) == 2: 114 | IndexManager.index.delete_from_table(table,[]) 115 | else: 116 | IndexManager.index.delete_from_table(table,lists[3:]) 117 | time_end = time.time() 118 | print(" time elapsed : %fs." % (time_end-time_start)) 119 | 120 | def find_last(string,str): 121 | last_position=-1 122 | while True: 123 | position=string.find(str,last_position+1) 124 | if position==-1: 125 | return last_position 126 | last_position=position -------------------------------------------------------------------------------- /miniSQL/miniSQL/CatalogManager/catalog.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import IndexManager.index 5 | 6 | tables = {} 7 | path = '' 8 | indexs = {} 9 | 10 | class table_instance(): 11 | def __init__(self,table_name,primary_key = 0): 12 | self.table_name = table_name 13 | self.primary_key = primary_key 14 | 15 | columns =[] 16 | 17 | class column(): 18 | def __init__(self,column_name,is_unique,type = 'char',length = 16): 19 | self.column_name = column_name 20 | self.is_unique = is_unique 21 | self.type = type 22 | self.length = length 23 | 24 | def __initialize__(__path): 25 | global path 26 | path = __path 27 | if not os.path.exists(os.path.join(path,'dbfiles/catalog_files')): 28 | os.makedirs(os.path.join(path,'dbfiles/catalog_files')) 29 | f = open(os.path.join(path,'dbfiles/catalog_files/tables_catalog.msql'),'w') 30 | f.close() 31 | f = open(os.path.join(path,'dbfiles/catalog_files/indexs_catalog.msql'),'w') 32 | f.close() 33 | tables['sys'] = table_instance('sys',0) 34 | indexs['sys_default_index'] = {'table':'sys','column':'username'} 35 | columns = [] 36 | columns.append(column('username',True)) 37 | columns.append(column('password', False)) 38 | tables['sys'].columns = columns 39 | __store__() 40 | __load__() 41 | 42 | def __finalize__(): 43 | __store__() 44 | 45 | def __load__(): 46 | f = open(os.path.join(path,'dbfiles/catalog_files/tables_catalog.msql')) 47 | json_tables = json.loads(f.read()) 48 | for table in json_tables.items(): 49 | __table = table_instance(table[0],table[1]['primary_key']) 50 | columns = [] 51 | for __column in table[1]['columns'].items(): 52 | columns.append(column(__column[0], 53 | __column[1][0],__column[1][1],__column[1][2])) 54 | __table.columns = columns 55 | tables[table[0]] = __table 56 | f.close() 57 | f = open(os.path.join(path, 'dbfiles/catalog_files/indexs_catalog.msql')) 58 | json_indexs = f.read() 59 | json_indexs = json.loads(json_indexs) 60 | for index in json_indexs.items(): 61 | indexs[index[0]] = index[1] 62 | f.close() 63 | 64 | def __store__(): 65 | __tables = {} 66 | for items in tables.items(): 67 | definition = {} 68 | definition['primary_key'] = items[1].primary_key 69 | __columns = {} 70 | for i in items[1].columns: 71 | __columns[i.column_name] = [i.is_unique,i.type,i.length] 72 | definition['columns'] = __columns 73 | __tables[items[0]] = definition 74 | json_tables = json.dumps(__tables) 75 | f = open(os.path.join(path,'dbfiles/catalog_files/tables_catalog.msql'),'w') 76 | f.write(json_tables) 77 | f.close() 78 | f = open(os.path.join(path, 'dbfiles/catalog_files/indexs_catalog.msql'), 'w') 79 | f.write(json.dumps(indexs)) 80 | f.close() 81 | 82 | def check_types_of_table(table,values): 83 | cur_table = tables[table] 84 | if len(cur_table.columns) != len(values): 85 | raise Exception("Catalog Module : table '%s' " 86 | "has %d columns." % (table,len(cur_table.columns))) 87 | for index,i in enumerate(cur_table.columns): 88 | if i.type == 'int': 89 | value = int(values[index]) 90 | elif i.type == 'float': 91 | value = float(values[index]) 92 | else: 93 | value = values[index] 94 | if len(value) > i.length: 95 | raise Exception("Catalog Module : table '%s' : column '%s' 's length" 96 | " can't be longer than %d." % (table, i.column_name,i.length)) 97 | 98 | if i.is_unique: 99 | IndexManager.index.check_unique(table,index,value) 100 | 101 | def exists_table(table): 102 | for key in tables.keys(): 103 | if key == table: 104 | raise Exception("Catalog Module : table already exists.") 105 | 106 | def not_exists_table(table): 107 | for key in tables.keys(): 108 | if key == table: 109 | return 110 | raise Exception("Catalog Module : table not exists.") 111 | 112 | def not_exists_index(index): 113 | for key in indexs.keys(): 114 | if key == index: 115 | return 116 | raise Exception("Catalog Module : index not exists.") 117 | 118 | def exists_index(index): 119 | for key in indexs.keys(): 120 | if key == index: 121 | raise Exception("Catalog Module : index already exists.") 122 | 123 | def create_table(table,statement): 124 | global tables 125 | primary_place = re.search('primary key *\(',statement).end() 126 | primary_place_end = re.search('\)',statement[primary_place:]).start() 127 | primary_key = statement[primary_place:][:primary_place_end].strip() 128 | cur_table = table_instance(table,primary_key) 129 | lists = statement.split(',') 130 | columns = [] 131 | for cur_column_statement in lists[0:len(lists)-1]: 132 | cur_column_statement = cur_column_statement.strip() 133 | cur_lists = cur_column_statement.split(' ') 134 | is_unique = False 135 | type = 'char' 136 | column_name = cur_lists[0] 137 | if re.search('unique',concat_list(cur_lists[1:])) or column_name == primary_key: 138 | is_unique = True 139 | if re.search('char',concat_list(cur_lists[1:])): 140 | length_start = re.search('\(',concat_list(cur_lists[1:])).start()+1 141 | length_end = re.search('\)', concat_list(cur_lists[1:])).start() 142 | length = int(concat_list(cur_lists[1:])[length_start:length_end]) 143 | 144 | elif re.search('int', concat_list(cur_lists[1:])): 145 | length = 0 146 | type = 'int' 147 | elif re.search('float', concat_list(cur_lists[1:])): 148 | length = 0 149 | type = 'float' 150 | else: 151 | raise Exception("Catalog Module : Unsupported type for %d." % column_name) 152 | columns.append(column(column_name,is_unique,type,length)) 153 | cur_table.columns = columns 154 | seed = False 155 | for index,__column in enumerate(cur_table.columns): 156 | if __column.column_name == cur_table.primary_key: 157 | cur_table.primary_key = index 158 | seed = True 159 | break 160 | if seed == False: 161 | raise Exception("Catalog Module : primary_key '%s' not exists." 162 | % cur_table.primary_key) 163 | 164 | tables[table] = cur_table 165 | 166 | 167 | def drop_table(table): 168 | tables.pop(table) 169 | 170 | def drop_index(index): 171 | indexs.pop(index) 172 | print("Successfully delete index '%s'." % index) 173 | 174 | def create_index(index_name,table,column): 175 | indexs[index_name] = {'table':table,'column':column} 176 | 177 | def check_select_statement(table,conditions,__columns): 178 | # raise an exception if something is wrong 179 | columns = [] 180 | for i in tables[table].columns: 181 | columns.append(i.column_name) 182 | if conditions != '': 183 | conditions = re.sub('and|or',',',conditions) 184 | conditions_lists = conditions.split(',') 185 | for i in conditions_lists: 186 | if i.strip().split(' ')[0] not in columns: 187 | raise Exception("Catalog Module : no such column" 188 | " name '%s'." % i.strip().split(' ')[0]) 189 | if __columns == '*': 190 | return 191 | 192 | __columns_list = __columns.split(',') 193 | for i in __columns_list: 194 | if i.strip() not in columns: 195 | raise Exception("Catalog Module : no such column name '%s'." % i.strip()) 196 | 197 | 198 | def concat_list(lists): 199 | statement = '' 200 | for i in lists: 201 | statement = statement + i 202 | return statement 203 | 204 | if __name__ == '__main__': 205 | # new_table = table_instance('my_table','yes') 206 | # new_table.columns.append(column('yes',True)) 207 | # new_table.columns.append(column('no',False)) 208 | # tables['my_table'] = new_table 209 | __initialize__('/Users/alan/Desktop/CodingLife/Python/miniSQL') 210 | ##__store__() 211 | 212 | -------------------------------------------------------------------------------- /miniSQL/miniSQL/IndexManager/index.py: -------------------------------------------------------------------------------- 1 | import CatalogManager.catalog 2 | import math 3 | import json 4 | import os 5 | 6 | N = 4 7 | 8 | tables = {} 9 | path = '' 10 | 11 | def __initialize__(__path): 12 | global path 13 | path = __path 14 | if not os.path.exists(os.path.join(path,'dbfiles/index_files')): 15 | os.makedirs(os.path.join(path,'dbfiles/index_files')) 16 | tables['sys'] = node(True, ['alan'], [['alan','123456'],'']) 17 | __store__() 18 | __load__() 19 | 20 | def __finalize__(): 21 | __store__() 22 | 23 | class node(): 24 | def __init__(self,is_leaf,keys,pointers,parent = ''): 25 | self.is_leaf = is_leaf 26 | self.keys = keys 27 | self.pointers = pointers 28 | self.parent = parent 29 | 30 | __last_leaf_pointer = '' 31 | def __load__(): 32 | global __last_leaf_pointer 33 | f = open(os.path.join(path,'dbfiles/index_files/tables_B-plus_tree.msql')) 34 | json_tables = json.loads(f.read()) 35 | f.close() 36 | for table in json_tables.items(): 37 | if len(table[1]['keys']) == 0: 38 | tables[table[0]] = node(True,[],[]) 39 | continue 40 | tables[table[0]] = \ 41 | node(table[1]['is_leaf'],table[1]['keys'],table[1]['pointers'],'') 42 | if not tables[table[0]].is_leaf: 43 | tables[table[0]].pointers = \ 44 | recursive_load_node(table[1]['pointers'],tables[table[0]]) 45 | 46 | def recursive_load_node(pointer_list,parent): 47 | global __last_leaf_pointer 48 | lists = [] 49 | for pointer in pointer_list: 50 | new_node = node(pointer['is_leaf'],pointer['keys'],pointer['pointers'],parent) 51 | lists.append(new_node) 52 | if not lists[-1].is_leaf: 53 | new_node.pointers = recursive_load_node(pointer['pointers'],lists[-1]) 54 | else: 55 | if __last_leaf_pointer == '': 56 | __last_leaf_pointer = new_node 57 | else: 58 | __last_leaf_pointer.pointers.append(new_node) 59 | __last_leaf_pointer = new_node 60 | return lists 61 | 62 | def __store__(): 63 | global path 64 | __tables = {} 65 | for table in tables.items(): 66 | __tables[table[0]] = recursive_store_node(table[1]) 67 | f = open(os.path.join(path,'dbfiles/index_files/tables_B-plus_tree.msql'),'w') 68 | json_tables = json.dumps(__tables) 69 | f.write(json_tables) 70 | f.close() 71 | 72 | def recursive_store_node(node): 73 | cur_node = {} 74 | cur_node['is_leaf'] = node.is_leaf 75 | cur_node['keys'] = node.keys 76 | if node.is_leaf == True and node.pointers[-1] != '': 77 | cur_node['pointers'] = node.pointers[0:-1] 78 | elif node.is_leaf == True and node.pointers[-1] == '': 79 | cur_node['pointers'] = node.pointers 80 | else: 81 | cur_node['pointers'] = [] 82 | for __node in node.pointers: 83 | cur_node['pointers'].append(recursive_store_node(__node)) 84 | return cur_node 85 | 86 | def __prints(table): 87 | node = tables[table] 88 | __do_print(node) 89 | 90 | def __do_print(node): 91 | if node.is_leaf == True: 92 | print('leaf') 93 | print(node.keys) 94 | print(node.pointers) 95 | if node.parent != '': 96 | print('parent:',node.parent.keys) 97 | else: 98 | print('node') 99 | print(node.keys) 100 | if node.parent != '': 101 | print('parent:',node.parent.keys) 102 | for i in node.pointers: 103 | __do_print(i) 104 | 105 | 106 | def insert_into_table(table,__values): 107 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 108 | if col.type == 'int': 109 | __values[index] = int(__values[index]) 110 | elif col.type == 'char': 111 | __values[index] = __values[index].strip().replace("'",'') 112 | elif col.type == 'float': 113 | __values[index] = float(__values[index]) 114 | 115 | cur_node = tables[table] 116 | __primary_key = CatalogManager.catalog.tables[table].primary_key 117 | # __primary_key = 0 118 | if len(cur_node.keys) == 0: 119 | # new tree 120 | cur_node.keys.append(__values[__primary_key]) 121 | cur_node.pointers.append(__values) 122 | cur_node.pointers.append('') 123 | print('Successfully insert into table %s,' % table,end='') 124 | return 125 | 126 | cur_node = find_leaf_place(table,__values[__primary_key]) 127 | if len(cur_node.keys) < N - 1: 128 | insert_into_leaf(cur_node,__values[__primary_key],__values) 129 | 130 | else: 131 | insert_into_leaf(cur_node,__values[__primary_key],__values) 132 | new_node = node(True,[],[]) 133 | tmp_keys = cur_node.keys 134 | tmp_pointers = cur_node.pointers 135 | cur_node.keys = [] 136 | cur_node.pointers = [] 137 | for i in range(math.ceil(N/2)): 138 | cur_node.keys.append(tmp_keys.pop(0)) 139 | cur_node.pointers.append(tmp_pointers.pop(0)) 140 | for i in range(N - math.ceil(N/2)): 141 | new_node.keys.append(tmp_keys.pop(0)) 142 | new_node.pointers.append(tmp_pointers.pop(0)) 143 | cur_node.pointers.append(new_node) 144 | new_node.pointers.append(tmp_pointers.pop(0)) 145 | insert_into_parent(table,cur_node,new_node.keys[0],new_node) 146 | 147 | print('Successfully insert into table %s,' % table,end='') 148 | 149 | def create_table(table,statement): 150 | tables[table] = node(True,[],[]) 151 | 152 | def delete_table(table): 153 | # delete table instance 154 | tables.pop(table) 155 | 156 | def delete_from_table(table,statement): 157 | # delete rows from table according to the statement's condition 158 | # usage : find_leaf_place_with_condition(table, column, value,condition) 159 | if len(statement) == 0: 160 | tables[table] = node(True,[],[],'') 161 | print("Successfully delete all entrys from table '%s'," % table,end='') 162 | else: 163 | columns = {} 164 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 165 | columns[col.column_name] = index 166 | __primary_key = CatalogManager.catalog.tables[table].primary_key 167 | # __primary_key = 0 168 | # columns = {'num':0,'val':1} 169 | 170 | conditions = [] 171 | tmp = [] 172 | pos = 1 173 | for i in statement: 174 | if i == 'and': 175 | conditions.append(tmp) 176 | tmp = [] 177 | pos = 1 178 | continue 179 | if pos == 1: 180 | tmp.append(columns[i]) 181 | elif pos == 3: 182 | if CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'char': 183 | tmp.append(i.strip().replace("'", '')) 184 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'int': 185 | tmp.append(int(i)) 186 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'float': 187 | tmp.append(float(i)) 188 | else: 189 | tmp.append(i) 190 | pos = pos + 1 191 | conditions.append(tmp) 192 | times = 0 193 | while True: 194 | nodes = find_leaf_place_with_condition(table, 195 | conditions[0][0],conditions[0][2],conditions[0][1]) 196 | for col in conditions: 197 | if col[0] == __primary_key: 198 | nodes = find_leaf_place_with_condition(table,col[0],col[2],col[1]) 199 | break 200 | 201 | if len(nodes) == 0: 202 | break 203 | seed = False 204 | for __node in nodes: 205 | if seed == True: 206 | break 207 | for index,leaf in enumerate(__node.pointers[0:-1]): 208 | if check_conditions(leaf,conditions): 209 | __node.pointers.pop(index) 210 | __node.keys.pop(index) 211 | maintain_B_plus_tree_after_delete(table,__node) 212 | times = times + 1 213 | seed = True 214 | break 215 | if seed == False: 216 | break 217 | print("Successfully delete %d entry(s) from table '%s'," % (times,table),end='') 218 | 219 | def check_conditions(leaf,conditions): 220 | for cond in conditions: 221 | # cond <-> column op value 222 | __value = leaf[cond[0]] 223 | if cond[1] == '<': 224 | if not (__value < cond[2]): 225 | return False 226 | elif cond[1] == '<=': 227 | if not (__value <= cond[2]): 228 | return False 229 | elif cond[1] == '>': 230 | if not (__value > cond[2]): 231 | return False 232 | elif cond[1] == '>=': 233 | if not (__value >= cond[2]): 234 | return False 235 | elif cond[1] == '<>': 236 | if not (__value != cond[2]): 237 | return False 238 | elif cond[1] == '=': 239 | if not (__value == cond[2]): 240 | return False 241 | else: 242 | raise Exception("Index Module : unsupported op.") 243 | return True 244 | 245 | def maintain_B_plus_tree_after_delete(table,__node): 246 | global N 247 | if __node.parent == '' and len(__node.pointers) == 1: 248 | tables[table] = __node.pointers[0] 249 | elif ((len(__node.pointers) < math.ceil(N/2) and __node.is_leaf == False) or 250 | (len(__node.keys) < math.ceil((N-1)/2) and __node.is_leaf == True) ) \ 251 | and __node.parent != '': 252 | previous = False 253 | other_node = node(True,[],[]) 254 | K = '' 255 | __index = 0 256 | for index, i in enumerate(__node.parent.pointers): 257 | if i == __node: 258 | if index == len(__node.parent.pointers) - 1: 259 | other_node = __node.parent.pointers[-2] 260 | previous = True 261 | K = __node.parent.keys[index - 1] 262 | else: 263 | K = __node.parent.keys[index] 264 | other_node = __node.parent.pointers[index + 1] 265 | __index = index + 1 266 | 267 | if (other_node.is_leaf == True and len(other_node.keys)+len(__node.keys) < N) or \ 268 | (other_node.is_leaf == False and len(other_node.pointers) + 269 | len(__node.pointers) <= N): 270 | if previous == True: 271 | if other_node.is_leaf == False: 272 | other_node.pointers = other_node.pointers + __node.pointers 273 | other_node.keys = other_node.keys + [K] + __node.keys 274 | for __node__ in __node.pointers: 275 | __node__.parent = other_node 276 | else: 277 | other_node.pointers = other_node.pointers[0:-1] 278 | other_node.pointers = other_node.pointers + __node.pointers 279 | other_node.keys = other_node.keys + __node.keys 280 | __node.parent.pointers = __node.parent.pointers[0:-1] 281 | __node.parent.keys = __node.parent.keys[0:-1] 282 | maintain_B_plus_tree_after_delete(table,__node.parent) 283 | else: 284 | if other_node.is_leaf == False: 285 | __node.pointers = __node.pointers + other_node.pointers 286 | __node.keys = __node.keys + [K] + other_node.keys 287 | for __node__ in other_node.pointers: 288 | __node__.parent = __node 289 | else: 290 | __node.pointers = __node.pointers[0:-1] 291 | __node.pointers = __node.pointers + other_node.pointers 292 | __node.keys = __node.keys + other_node.keys 293 | __node.parent.pointers.pop(__index) 294 | __node.parent.keys.pop(__index-1) 295 | maintain_B_plus_tree_after_delete(table,__node.parent) 296 | else: 297 | if previous == True: 298 | if other_node.is_leaf == True: 299 | __node.keys.insert(0,other_node.keys.pop(-1)) 300 | __node.pointers.insert(0,other_node.pointers.pop(-2)) 301 | __node.parent.keys[-1] = __node.keys[0] 302 | else: 303 | __tmp = other_node.pointers.pop(-1) 304 | __tmp.parent = __node 305 | __node.pointers.insert(0,__tmp) 306 | __node.keys.insert(0,__node.parent.keys[-1]) 307 | __node.parent.keys[-1] = other_node.keys.pop(-1) 308 | else: 309 | if other_node.is_leaf == True: 310 | __node.keys.insert(-1,other_node.keys.pop(0)) 311 | __node.pointers.insert(-2,other_node.pointers.pop(0)) 312 | __node.parent.keys[__index-1] = other_node.keys[0] 313 | else: 314 | __tmp = other_node.pointers.pop(0) 315 | __tmp.parent = __node 316 | __node.pointers.insert(-1,__tmp) 317 | __node.keys.insert(-1,__node.parent.keys[__index-1]) 318 | __node.parent.keys[__index-1] = other_node.keys.pop(0) 319 | 320 | def create_index(index_name,table,column): 321 | pass 322 | 323 | def select_from_table(table,__conditions = '',__columns = ''): 324 | results = [] 325 | columns = {} 326 | for index,col in enumerate(CatalogManager.catalog.tables[table].columns): 327 | columns[col.column_name] = index 328 | __primary_key = CatalogManager.catalog.tables[table].primary_key 329 | # __primary_key = 0 330 | # columns = {'num': 0, 'val': 1} 331 | 332 | if len(tables[table].keys) == 0: 333 | pass 334 | else: 335 | if __conditions != '': 336 | conditions = [] 337 | statement = __conditions.split(' ') 338 | tmp = [] 339 | pos = 1 340 | for i in statement: 341 | if i == 'and': 342 | conditions.append(tmp) 343 | tmp = [] 344 | pos = 1 345 | continue 346 | if pos == 1: 347 | tmp.append(columns[i]) 348 | elif pos == 3: 349 | if CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'char': 350 | tmp.append(i.strip().replace("'",'')) 351 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'int': 352 | tmp.append(int(i)) 353 | elif CatalogManager.catalog.tables[table].columns[tmp[0]].type == 'float': 354 | tmp.append(float(i)) 355 | else: 356 | tmp.append(i) 357 | pos = pos + 1 358 | conditions.append(tmp) 359 | nodes = find_leaf_place_with_condition(table, 360 | conditions[0][0], conditions[0][2], conditions[0][1]) 361 | for col in conditions: 362 | if col[0] == __primary_key: 363 | nodes = find_leaf_place_with_condition(table, col[0], col[2], col[1]) 364 | break 365 | for __node in nodes: 366 | for pointer in __node.pointers[0:-1]: 367 | if check_conditions(pointer,conditions): 368 | results.append(pointer) 369 | else: 370 | first_leaf_node = tables[table] 371 | while first_leaf_node.is_leaf != True: 372 | first_leaf_node = first_leaf_node.pointers[0] 373 | while True: 374 | for i in first_leaf_node.pointers[0:-1]: 375 | results.append(i) 376 | if first_leaf_node.pointers[-1] != '': 377 | first_leaf_node = first_leaf_node.pointers[-1] 378 | else: 379 | break 380 | 381 | if __columns == '*': 382 | __columns_list = list(columns.keys()) 383 | __columns_list_num = list(columns.values()) 384 | else: 385 | __columns_list_num = [columns[i.strip()] for i in __columns.split(',')] 386 | __columns_list = [i.strip() for i in __columns.split(',')] 387 | 388 | print('-' * (17 * len(__columns_list_num) + 1)) 389 | for i in __columns_list: 390 | if len(str(i)) > 14: 391 | output = str(i)[0:14] 392 | else: 393 | output = str(i) 394 | print('|',output.center(15),end='') 395 | print('|') 396 | print('-' * (17 * len(__columns_list_num) + 1)) 397 | for i in results: 398 | for j in __columns_list_num: 399 | if len(str(i[j])) > 14: 400 | output = str(i[j])[0:14] 401 | else: 402 | output = str(i[j]) 403 | print('|',output.center(15) ,end='') 404 | print('|') 405 | print('-' * (17 * len(__columns_list_num) + 1)) 406 | print("Returned %d entrys," % len(results),end='') 407 | 408 | def check_unique(table,column,value): 409 | columns = [] 410 | for col in CatalogManager.catalog.tables[table].columns: 411 | columns.append(col) 412 | if len(find_leaf_place_with_condition(table,column,value,'=')): 413 | raise Exception("Index Module : column '%s' does not satisfy " 414 | "unique constrains." % columns[column]) 415 | 416 | def find_leaf_place(table,value): 417 | # search on primary key 418 | cur_node = tables[table] 419 | while not cur_node.is_leaf: 420 | seed = False 421 | for index,key in enumerate(cur_node.keys): 422 | if key > value: 423 | cur_node = cur_node.pointers[index] 424 | seed = True 425 | break 426 | if seed == False: 427 | cur_node = cur_node.pointers[-1] 428 | return cur_node 429 | 430 | def find_leaf_place_with_condition(table,column,value,condition): 431 | # __primary_key = CatalogManager.catalog.tables[table].primary_key 432 | __primary_key = 0 433 | head_node = tables[table] 434 | first_leaf_node = head_node 435 | while first_leaf_node.is_leaf != True: 436 | first_leaf_node = first_leaf_node.pointers[0] 437 | lists = [] 438 | 439 | if __primary_key == column and condition != '<>': 440 | while not head_node.is_leaf: 441 | seed = False 442 | for index, key in enumerate(head_node.keys): 443 | if key > value: 444 | head_node = head_node.pointers[index] 445 | seed = True 446 | break 447 | if seed == False: 448 | head_node = head_node.pointers[-1] 449 | if condition == '=': 450 | for pointer in head_node.pointers[0:-1]: 451 | if pointer[column] == value: 452 | lists.append(head_node) 453 | elif condition == '<=': 454 | cur_node = first_leaf_node 455 | while True: 456 | if cur_node != head_node: 457 | lists.append(cur_node) 458 | cur_node = cur_node.pointers[-1] 459 | else: 460 | break 461 | for pointer in head_node.pointers[0:-1]: 462 | if pointer[column] <= value: 463 | lists.append(head_node) 464 | break 465 | elif condition == '<': 466 | cur_node = first_leaf_node 467 | while True: 468 | if cur_node != head_node: 469 | lists.append(cur_node) 470 | cur_node = cur_node.pointers[-1] 471 | else: 472 | break 473 | for pointer in head_node.pointers[0:-1]: 474 | if pointer[column] < value: 475 | lists.append(head_node) 476 | break 477 | elif condition == '>': 478 | for pointer in head_node.pointers[0:-1]: 479 | if pointer[column] > value: 480 | lists.append(head_node) 481 | break 482 | while True: 483 | head_node = head_node.pointers[-1] 484 | if head_node != '': 485 | lists.append(head_node) 486 | else: 487 | break 488 | elif condition == '>=': 489 | for pointer in head_node.pointers[0:-1]: 490 | if pointer[column] >= value: 491 | lists.append(head_node) 492 | break 493 | while True: 494 | head_node = head_node.pointers[-1] 495 | if head_node != '': 496 | lists.append(head_node) 497 | else: 498 | break 499 | else: 500 | raise Exception("Index Module : unsupported op.") 501 | 502 | else: 503 | while True: 504 | for pointer in first_leaf_node.pointers[0:-1]: 505 | if condition == '=': 506 | if pointer[column] == value: 507 | lists.append(first_leaf_node) 508 | break 509 | elif condition == '<': 510 | if pointer[column] < value: 511 | lists.append(first_leaf_node) 512 | break 513 | elif condition == '<=': 514 | if pointer[column] <= value: 515 | lists.append(first_leaf_node) 516 | break 517 | elif condition == '>': 518 | if pointer[column] > value: 519 | lists.append(first_leaf_node) 520 | break 521 | elif condition == '>=': 522 | if pointer[column] >= value: 523 | lists.append(first_leaf_node) 524 | break 525 | elif condition == '<>': 526 | if pointer[column] != value: 527 | lists.append(first_leaf_node) 528 | break 529 | else: 530 | raise Exception("Index Module : unsupported op.") 531 | if first_leaf_node.pointers[-1] == '': 532 | break 533 | first_leaf_node = first_leaf_node.pointers[-1] 534 | return lists 535 | 536 | def insert_into_leaf(cur_node,value,pointer): 537 | for index,key in enumerate(cur_node.keys): 538 | if key == value: 539 | raise Exception("Index Module : primary_key already exists.") 540 | if key > value: 541 | cur_node.pointers.insert(index,pointer) 542 | cur_node.keys.insert(index,value) 543 | return 544 | cur_node.pointers.insert(len(cur_node.keys), pointer) 545 | cur_node.keys.insert(len(cur_node.keys), value) 546 | 547 | def insert_into_parent(table,__node,__key,new_node): 548 | if __node.parent == '': 549 | cur_node = node(False,[],[],'') 550 | cur_node.pointers.append(__node) 551 | cur_node.pointers.append(new_node) 552 | cur_node.keys.append(__key) 553 | __node.parent = cur_node 554 | new_node.parent = cur_node 555 | tables[table] = cur_node 556 | else: 557 | p = __node.parent 558 | if len(p.pointers) < N: 559 | seed = False 560 | for index,key in enumerate(p.keys): 561 | if __key < key: 562 | p.keys.insert(index,__key) 563 | p.pointers.insert(index+1,new_node) 564 | seed = True 565 | break 566 | if seed == False: 567 | p.keys.append(__key) 568 | p.pointers.append(new_node) 569 | new_node.parent = p 570 | else: 571 | seed = False 572 | for index,key in enumerate(p.keys): 573 | if __key < key: 574 | p.keys.insert(index,__key) 575 | p.pointers.insert(index+1,new_node) 576 | seed = True 577 | break 578 | if seed == False: 579 | p.keys.append(__key) 580 | p.pointers.append(new_node) 581 | __new_node = node(False,[],[]) 582 | tmp_keys = p.keys 583 | tmp_pointers = p.pointers 584 | p.keys = [] 585 | p.pointers = [] 586 | for i in range(math.ceil(N / 2)): 587 | p.keys.append(tmp_keys.pop(0)) 588 | p.pointers.append(tmp_pointers.pop(0)) 589 | p.pointers.append(tmp_pointers.pop(0)) 590 | k__ = tmp_keys.pop(0) 591 | for i in range(N - math.ceil(N / 2) - 1): 592 | __new_node.keys.append(tmp_keys.pop(0)) 593 | __tmp = tmp_pointers.pop(0) 594 | __tmp.parent = __new_node 595 | __new_node.pointers.append(__tmp) 596 | __tmp = tmp_pointers.pop(0) 597 | __tmp.parent = __new_node 598 | __new_node.pointers.append(__tmp) 599 | new_node.parent = __new_node 600 | insert_into_parent(table,p,k__,__new_node) 601 | 602 | def exist_user(username,password): 603 | nodes = find_leaf_place_with_condition('sys', 0, username, '=') 604 | for __node in nodes: 605 | for ptr in __node.pointers[0:-1]: 606 | if ptr[0] == username and ptr[1] == password: 607 | return True 608 | return False 609 | 610 | 611 | if __name__ == '__main__': 612 | __initialize__('/Users/alan/Desktop/CodingLife/Python/miniSQL/') 613 | tables['student'] = node(True, [], []) 614 | insert_into_table('student',[2,'we']) 615 | insert_into_table('student', [3, 'ke']) 616 | insert_into_table('student', [5, 'ww']) 617 | insert_into_table('student', [7, 'ww']) 618 | insert_into_table('student', [11, 'wl']) 619 | insert_into_table('student', [17, 'wl']) 620 | insert_into_table('student', [19, 'wl']) 621 | insert_into_table('student', [23, 'wl']) 622 | insert_into_table('student', [29, 'wl']) 623 | insert_into_table('student', [31, 'wl']) 624 | insert_into_table('student', [9, 'wl']) 625 | insert_into_table('student', [10, 'wl']) 626 | insert_into_table('student', [8, 'wl']) 627 | 628 | 629 | # delete_from_table('student',['num','=',23]) 630 | # delete_from_table('student', ['num', '=', 19]) 631 | # __prints('student') 632 | # select_from_table('student','num > 0','*') 633 | __store__() 634 | pass 635 | -------------------------------------------------------------------------------- /miniSQL/miniSQL/MiniSQL.py: -------------------------------------------------------------------------------- 1 | import cmd 2 | import APIManager.api 3 | import CatalogManager.catalog 4 | import IndexManager.index 5 | import sys 6 | import time 7 | import os 8 | 9 | class miniSQL(cmd.Cmd): 10 | intro = 'Welcome to the MiniSQL database server.\nType help or ? to list commands.\n' 11 | def do_select(self,args): 12 | # APIManager.api.select(args.replace(';', '')) 13 | try: 14 | APIManager.api.select(args.replace(';','')) 15 | except Exception as e: 16 | print(str(e)) 17 | 18 | def do_create(self,args): 19 | try: 20 | APIManager.api.create(args.replace(';','')) 21 | except Exception as e: 22 | print(str(e)) 23 | 24 | def do_drop(self,args): 25 | try: 26 | APIManager.api.drop(args.replace(';','')) 27 | except Exception as e: 28 | print(str(e)) 29 | 30 | def do_insert(self,args): 31 | try: 32 | APIManager.api.insert(args.replace(';','')) 33 | except Exception as e: 34 | print(str(e)) 35 | 36 | def do_delete(self,args): 37 | try: 38 | APIManager.api.delete(args.replace(';','')) 39 | except Exception as e: 40 | print(str(e)) 41 | 42 | def do_commit(self,args): 43 | time_start = time.time() 44 | __finalize__() 45 | time_end = time.time() 46 | print('Modifications has been commited to local files,',end='') 47 | print(" time elapsed : %fs." % (time_end - time_start)) 48 | 49 | def do_quit(self,args): 50 | __finalize__() 51 | print('Goodbye.') 52 | sys.exit() 53 | 54 | def emptyline(self): 55 | pass 56 | 57 | def default(self, line): 58 | print('Unrecognized command.\nNo such symbol : %s' % line) 59 | 60 | def help_commit(self): 61 | print() 62 | text = "To reduce file transfer's time, this SQL server is designed to "+\ 63 | "'lasy' write changes to local files, which means it will not store changes "+\ 64 | "until you type 'quit' to normally exit the server. if this server exit "+\ 65 | "unnormally, all changes will be lost. If you want to write changes to "+\ 66 | "local files immediately, please use 'commit' command.\n" 67 | print(text) 68 | 69 | def help_quit(self): 70 | print() 71 | print('Quit the program and write changes to local file.') 72 | 73 | def help_select(self): 74 | print() 75 | print("select * from student;") 76 | print("select num from student where num >= 2 and num < 10 and gender = 'male';") 77 | 78 | def help_create(self): 79 | print() 80 | print("create table student (ID int, name char(10),gender char(10)" 81 | ",enroll_date char(10),primary key(ID));") 82 | 83 | def help_drop(self): 84 | print() 85 | print("drop table student;") 86 | 87 | def help_insert(self): 88 | print(''' 89 | insert into student values ( 1,'Alan','male','2017.9.1'); 90 | insert into student values ( 2,'rose','female','2016.9.1'); 91 | insert into student values ( 3,'Robert','male','2016.9.1'); 92 | insert into student values ( 4,'jack','male','2015.9.1'); 93 | insert into student values ( 5,'jason','male','2015.9.1'); 94 | insert into student values ( 6,'Hans','female','2015.9.1'); 95 | insert into student values ( 7,'rosa','male','2014.9.1'); 96 | insert into student values ( 8,'messi','female','2013.9.1'); 97 | insert into student values ( 9,'Neymar','male','2013.9.1'); 98 | insert into student values ( 10,'Christ','male','2011.9.1'); 99 | insert into student values ( 11,'shaw','female','2010.9.1'); 100 | ''') 101 | 102 | def help_delete(self): 103 | print() 104 | print("delete from students") 105 | print("delete from student where sno = '88888888';") 106 | 107 | def exec_from_file(filename): 108 | f = open(filename) 109 | text = f.read() 110 | f.close() 111 | comands = text.split(';') 112 | comands = [i.strip().replace('\n','') for i in comands] 113 | __initialize__() 114 | for comand in comands: 115 | if comand == '': 116 | continue 117 | if comand[0] == '#': 118 | continue 119 | if comand.split(' ')[0] == 'insert': 120 | try: 121 | APIManager.api.insert(comand[6:]) 122 | except Exception as e: 123 | print(str(e)) 124 | elif comand.split(' ')[0] == 'select': 125 | try: 126 | APIManager.api.select(comand[6:]) 127 | except Exception as e: 128 | print(str(e)) 129 | elif comand.split(' ')[0] == 'delete': 130 | try: 131 | APIManager.api.delete(comand[6:]) 132 | except Exception as e: 133 | print(str(e)) 134 | elif comand.split(' ')[0] == 'drop': 135 | try: 136 | APIManager.api.drop(comand[4:]) 137 | except Exception as e: 138 | print(str(e)) 139 | elif comand.split(' ')[0] == 'create': 140 | try: 141 | APIManager.api.create(comand[6:]) 142 | except Exception as e: 143 | print(str(e)) 144 | __finalize__() 145 | 146 | 147 | def __initialize__(): 148 | CatalogManager.catalog.__initialize__(os.getcwd()) 149 | IndexManager.index.__initialize__(os.getcwd()) 150 | 151 | def __finalize__(): 152 | CatalogManager.catalog.__finalize__() 153 | IndexManager.index.__finalize__() 154 | 155 | if __name__ == '__main__': 156 | errortext = ''' 157 | MiniSQL -u [username] -p [password] (optional)-execfile [filename] 158 | \tLogin operators : 159 | \t\t-u username\tusername for MiniSQL. 160 | \t\t-p password\tpassword for MiniSQL.\n 161 | \tExecute SQL file operators : 162 | \t\t-execfile filename\tSQL filename to be executed for MiniSQL. 163 | ''' 164 | if len(sys.argv) < 5: 165 | print('ERROR : Unsupported syntax, please login.\n',errortext) 166 | sys.exit() 167 | if sys.argv[1] != '-u' or sys.argv[3] != '-p': 168 | print('ERROR : Unsupported syntax, please login.\n',errortext) 169 | sys.exit() 170 | __initialize__() 171 | if sys.argv[2] == 'root' and sys.argv[4] == '123456': 172 | APIManager.api.__root = True 173 | elif IndexManager.index.exist_user(username=sys.argv[2],password=sys.argv[4]): 174 | APIManager.api.__root = False 175 | else: 176 | print('Error : username or password is not correct,please ' 177 | 'check and login again.\n',errortext) 178 | sys.exit() 179 | if len(sys.argv) > 5: 180 | if sys.argv[5] != '-execfile': 181 | print('ERROR : Unsupported syntax.\n',errortext) 182 | exec_from_file(sys.argv[6]) 183 | sys.exit() 184 | print("MiniSQL database server, version 1.0.0-release, (x86_64-apple-darwin)\n" 185 | "Copyright 2018 @ Alan Shaw from ZJU. Course final project for DBS.\n" 186 | "These shell commands are defined internally. Type `help' to see this list.\n" 187 | "Type `help name' to find out more about the function `name'.\n") 188 | 189 | miniSQL.prompt = '(%s)' % sys.argv[2] + 'MiniSQL > ' 190 | miniSQL().cmdloop() 191 | -------------------------------------------------------------------------------- /miniSQL/miniSQL/dbfiles/catalog_files/indexs_catalog.msql: -------------------------------------------------------------------------------- 1 | {"sys_default_index": {"table": "sys", "column": "username"}} -------------------------------------------------------------------------------- /miniSQL/miniSQL/dbfiles/catalog_files/tables_catalog.msql: -------------------------------------------------------------------------------- 1 | {"sys": {"primary_key": 0, "columns": {"username": [true, "char", 16], "password": [false, "char", 16]}}, "student": {"primary_key": 0, "columns": {"num": [true, "int", 0], "val": [false, "char", 10]}}} -------------------------------------------------------------------------------- /miniSQL/miniSQL/dbfiles/index_files/tables_B-plus_tree.msql: -------------------------------------------------------------------------------- 1 | {"sys": {"is_leaf": true, "keys": ["alan"], "pointers": [["alan", "123456"], ""]}, "student": {"is_leaf": false, "keys": [7], "pointers": [{"is_leaf": false, "keys": [3, 5], "pointers": [{"is_leaf": true, "keys": [1, 2], "pointers": [[1, "a"], [2, "b"]]}, {"is_leaf": true, "keys": [3, 4], "pointers": [[3, "c"], [4, "d"]]}, {"is_leaf": true, "keys": [5, 6], "pointers": [[5, "e"], [6, "f"]]}]}, {"is_leaf": false, "keys": [9], "pointers": [{"is_leaf": true, "keys": [7, 8], "pointers": [[7, "g"], [8, "h"]]}, {"is_leaf": true, "keys": [9, 10, 11], "pointers": [[9, "I"], [10, "j"], [11, "k"], ""]}]}]}} --------------------------------------------------------------------------------