├── .gitignore ├── .travis.yml ├── Build.PL ├── Changes ├── LICENSE ├── META.json ├── README.md ├── cpanfile ├── lib ├── Anego.pm └── Anego │ ├── CLI.pm │ ├── CLI │ ├── Diff.pm │ ├── Help.pm │ ├── Migrate.pm │ ├── Status.pm │ └── Version.pm │ ├── Config.pm │ ├── Git.pm │ ├── Logger.pm │ ├── Task │ ├── Diff.pm │ ├── GitLog.pm │ └── SchemaLoader.pm │ └── Util.pm ├── minil.toml ├── script └── anego └── t └── basic.t /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | /_build/ 3 | /Build 4 | /Build.bat 5 | /blib 6 | /Makefile 7 | /pm_to_blib 8 | 9 | /carton.lock 10 | /.carton/ 11 | /local/ 12 | 13 | nytprof.out 14 | nytprof/ 15 | 16 | cover_db/ 17 | 18 | *.bak 19 | *.old 20 | *~ 21 | *.swp 22 | *.o 23 | *.obj 24 | 25 | !LICENSE 26 | 27 | /_build_params 28 | 29 | MYMETA.* 30 | 31 | /Anego-* 32 | 33 | /.test.requires.git.lock 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | sudo: false 3 | perl: 4 | - "5.8" 5 | - "5.10" 6 | - "5.12" 7 | - "5.14" 8 | - "5.16" 9 | - "5.18" 10 | - "5.20" 11 | - "5.22" 12 | - "5.24" 13 | - "5.26" 14 | install: 15 | - cpanm --quiet --notest DBD::SQLite 16 | - cpanm --quiet --installdeps --notest . 17 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 3 | # DO NOT EDIT DIRECTLY. 4 | # ========================================================================= 5 | 6 | use 5.008_001; 7 | use strict; 8 | 9 | use Module::Build::Tiny 0.035; 10 | 11 | Build_PL(); 12 | 13 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension Anego 2 | 3 | {{$NEXT}} 4 | 5 | 0.02 2017-08-28T13:18:45Z 6 | 7 | - add test 8 | - update document 9 | 10 | 0.01_03 2017-08-19T05:41:24Z 11 | 12 | - fix some bugs 13 | - new feature: "help" command 14 | 15 | 0.01_02 2016-11-04T03:56:54Z 16 | 17 | - new feature: "diff" command 18 | 19 | 0.01_01 2016-07-19T12:28:04Z 20 | 21 | - original version 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2016 by papix . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2016 by papix . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2016 by papix . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "The database migration utility as our elder sister.", 3 | "author" : [ 4 | "papix " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Minilla/v3.0.13, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : "2" 14 | }, 15 | "name" : "Anego", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples", 24 | "author", 25 | "builder" 26 | ] 27 | }, 28 | "prereqs" : { 29 | "configure" : { 30 | "requires" : { 31 | "Module::Build::Tiny" : "0.035" 32 | } 33 | }, 34 | "develop" : { 35 | "requires" : { 36 | "Test::CPAN::Meta" : "0", 37 | "Test::MinimumVersion::Fast" : "0.04", 38 | "Test::PAUSE::Permissions" : "0.04", 39 | "Test::Pod" : "1.41", 40 | "Test::Spellunker" : "v0.2.7" 41 | } 42 | }, 43 | "runtime" : { 44 | "requires" : { 45 | "DBI" : "0", 46 | "DBIx::Schema::DSL" : "0", 47 | "Digest::MD5" : "0", 48 | "Exporter" : "0", 49 | "File::Spec" : "0", 50 | "Getopt::Long" : "0", 51 | "Git::Repository" : "0", 52 | "SQL::Translator" : "0", 53 | "Term::ANSIColor" : "0", 54 | "Try::Tiny" : "0", 55 | "perl" : "5.008001" 56 | } 57 | }, 58 | "test" : { 59 | "requires" : { 60 | "Capture::Tiny" : "0", 61 | "Test::Git" : "0", 62 | "Test::More" : "0.98", 63 | "Test::Requires::Git" : "0" 64 | } 65 | } 66 | }, 67 | "release_status" : "unstable", 68 | "resources" : { 69 | "bugtracker" : { 70 | "web" : "https://github.com/papix/Anego/issues" 71 | }, 72 | "homepage" : "https://github.com/papix/Anego", 73 | "repository" : { 74 | "type" : "git", 75 | "url" : "git://github.com/papix/Anego.git", 76 | "web" : "https://github.com/papix/Anego" 77 | } 78 | }, 79 | "version" : "0.02", 80 | "x_authority" : "cpan:PAPIX", 81 | "x_contributors" : [ 82 | "dokechin ", 83 | "dokechin " 84 | ], 85 | "x_serialization_backend" : "JSON::PP version 2.27400_02" 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/papix/Anego.svg?branch=master)](https://travis-ci.org/papix/Anego) 2 | # NAME 3 | 4 | Anego - The database migration utility as our elder sister. 5 | 6 | # SYNOPSIS 7 | 8 | # show status 9 | $ anego status 10 | 11 | RDBMS: MySQL 12 | Database: myapp 13 | Schema class: MyApp::DB::Schema (lib/MyApp/DB/Schema.pm) 14 | 15 | Hash Commit message 16 | -------------------------------------------------- 17 | e299e9f commit 18 | 1fdc91a initial commit 19 | 20 | # migrate to latest schema 21 | $ anego migrate 22 | 23 | # migrate to schema of specified revision 24 | $ anego migrate revision 1fdc91a 25 | 26 | # show difference between current database schema and latest schema 27 | $ anego diff 28 | 29 | # show difference between current database schema and schema of specified revision 30 | $ anego diff revision 1fdc91a 31 | 32 | # DESCRIPTION 33 | 34 | Anego is database migration utility. 35 | 36 | # CONFIGURATION 37 | 38 | Anego requires configuration file. 39 | In default, Anego uses `.anego.pl` as configuration file. 40 | 41 | # .anego.pl 42 | +{ 43 | connect_info => ['dbi:mysql:database=myapp;host=localhost', 'root'], 44 | schema_class => 'MyApp::DB::Schema', 45 | } 46 | 47 | If you want to use other files for configuration, you can use `-c` option: `anego status -c ./config.pl` 48 | 49 | # SCHEMA CLASS 50 | 51 | To define database schema, Anego uses [DBIx::Schema::DSL](https://metacpan.org/pod/DBIx::Schema::DSL): 52 | 53 | package MyApp::DB::Schema; 54 | use strict; 55 | use warnings; 56 | use DBIx::Schema::DSL; 57 | 58 | create_table 'author' => columns { 59 | integer 'id', primary_key, auto_increment; 60 | varchar 'name', unique; 61 | }; 62 | 63 | create_table 'module' => columns { 64 | integer 'id', primary_key, auto_increment; 65 | varchar 'name'; 66 | text 'description'; 67 | integer 'author_id'; 68 | 69 | add_index 'author_id_idx' => ['author_id']; 70 | 71 | belongs_to 'author'; 72 | }; 73 | 74 | 1; 75 | 76 | # LICENSE 77 | 78 | Copyright (C) papix. 79 | 80 | This library is free software; you can redistribute it and/or modify 81 | it under the same terms as Perl itself. 82 | 83 | # AUTHOR 84 | 85 | papix 86 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Module::Load'; 2 | requires 'DBI'; 3 | requires 'DBIx::Schema::DSL'; 4 | requires 'Digest::MD5'; 5 | requires 'Exporter'; 6 | requires 'File::Spec'; 7 | requires 'Getopt::Long'; 8 | requires 'Git::Repository'; 9 | requires 'SQL::Translator'; 10 | requires 'Term::ANSIColor'; 11 | requires 'Try::Tiny'; 12 | requires 'perl', '5.008001'; 13 | 14 | on 'test' => sub { 15 | requires 'Test::More', '0.98'; 16 | requires 'Test::Git'; 17 | requires 'Test::Requires::Git'; 18 | 19 | requires 'Capture::Tiny'; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/Anego.pm: -------------------------------------------------------------------------------- 1 | package Anego; 2 | use 5.008001; 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | 7 | our $VERSION = "0.02"; 8 | 9 | 1; 10 | 11 | __END__ 12 | 13 | =encoding utf-8 14 | 15 | =head1 NAME 16 | 17 | Anego - The database migration utility as our elder sister. 18 | 19 | =head1 SYNOPSIS 20 | 21 | # show status 22 | $ anego status 23 | 24 | RDBMS: MySQL 25 | Database: myapp 26 | Schema class: MyApp::DB::Schema (lib/MyApp/DB/Schema.pm) 27 | 28 | Hash Commit message 29 | -------------------------------------------------- 30 | e299e9f commit 31 | 1fdc91a initial commit 32 | 33 | # migrate to latest schema 34 | $ anego migrate 35 | 36 | # migrate to schema of specified revision 37 | $ anego migrate revision 1fdc91a 38 | 39 | # show difference between current database schema and latest schema 40 | $ anego diff 41 | 42 | # show difference between current database schema and schema of specified revision 43 | $ anego diff revision 1fdc91a 44 | 45 | =head1 DESCRIPTION 46 | 47 | Anego is database migration utility. 48 | 49 | =head1 CONFIGURATION 50 | 51 | Anego requires configuration file. 52 | In default, Anego uses C<.anego.pl> as configuration file. 53 | 54 | # .anego.pl 55 | +{ 56 | connect_info => ['dbi:mysql:database=myapp;host=localhost', 'root'], 57 | schema_class => 'MyApp::DB::Schema', 58 | } 59 | 60 | If you want to use other files for configuration, you can use C<-c> option: C 61 | 62 | =head1 SCHEMA CLASS 63 | 64 | To define database schema, Anego uses L: 65 | 66 | package MyApp::DB::Schema; 67 | use strict; 68 | use warnings; 69 | use DBIx::Schema::DSL; 70 | 71 | create_table 'author' => columns { 72 | integer 'id', primary_key, auto_increment; 73 | varchar 'name', unique; 74 | }; 75 | 76 | create_table 'module' => columns { 77 | integer 'id', primary_key, auto_increment; 78 | varchar 'name'; 79 | text 'description'; 80 | integer 'author_id'; 81 | 82 | add_index 'author_id_idx' => ['author_id']; 83 | 84 | belongs_to 'author'; 85 | }; 86 | 87 | 1; 88 | 89 | =head1 LICENSE 90 | 91 | Copyright (C) papix. 92 | 93 | This library is free software; you can redistribute it and/or modify 94 | it under the same terms as Perl itself. 95 | 96 | =head1 AUTHOR 97 | 98 | papix Email@papix.netE 99 | 100 | =cut 101 | 102 | -------------------------------------------------------------------------------- /lib/Anego/CLI.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Getopt::Long; 6 | use Module::Load; 7 | use Try::Tiny; 8 | 9 | use Anego::Logger; 10 | 11 | sub run { 12 | my ($self, @args) = @_; 13 | 14 | local @ARGV = @args; 15 | my $parser = Getopt::Long::Parser->new( 16 | config => [ "no_ignore_case", "pass_through" ], 17 | ); 18 | $parser->getoptions( 19 | "config=s" => \$Anego::Config::CONFIG_PATH, 20 | ); 21 | 22 | my @commands = @ARGV;; 23 | my $command = shift @commands || 'help'; 24 | my $klass = sprintf('Anego::CLI::%s', ucfirst($command)); 25 | 26 | try { 27 | Module::Load::load $klass; 28 | try { 29 | $klass->run(@commands); 30 | } catch { 31 | errorf("$_\n"); 32 | }; 33 | } catch { 34 | warnf("Could not find command: %s\n", $command); 35 | errorf("$_\n") if $_ !~ /^Can't locate Anego/; 36 | exit 2; 37 | }; 38 | } 39 | 40 | 1; 41 | -------------------------------------------------------------------------------- /lib/Anego/CLI/Diff.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI::Diff; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Anego::Config; 7 | use Anego::Logger; 8 | use Anego::Task::Diff; 9 | use Anego::Task::SchemaLoader; 10 | use Anego::Util; 11 | 12 | sub run { 13 | my ($class, @args) = @_; 14 | my $config = Anego::Config->load; 15 | 16 | my $source_schema = Anego::Task::SchemaLoader->database; 17 | my $target_schema = Anego::Task::SchemaLoader->from(@args); 18 | 19 | my $diff = Anego::Task::Diff->diff($source_schema, $target_schema); 20 | unless ($diff) { 21 | warnf("target schema == database schema, should no differences\n"); 22 | return; 23 | } 24 | 25 | print $diff; 26 | } 27 | 28 | 1; 29 | -------------------------------------------------------------------------------- /lib/Anego/CLI/Help.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI::Help; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub run { 7 | my ($class, @args) = @_; 8 | 9 | my $module = 'Anego'; 10 | system 'perldoc', $module; 11 | } 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/Anego/CLI/Migrate.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI::Migrate; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Anego::Config; 7 | use Anego::Logger; 8 | use Anego::Task::Diff; 9 | use Anego::Task::SchemaLoader; 10 | use Anego::Util; 11 | 12 | sub run { 13 | my ($class, @args) = @_; 14 | my $config = Anego::Config->load; 15 | 16 | my $source_schema = Anego::Task::SchemaLoader->database; 17 | my $target_schema = Anego::Task::SchemaLoader->from(@args); 18 | 19 | my $diff = Anego::Task::Diff->diff($source_schema, $target_schema); 20 | unless ($diff) { 21 | warnf("target schema == database schema, should no differences\n"); 22 | return; 23 | } 24 | 25 | do_sql($diff); 26 | 27 | infof "Migrated\n"; 28 | } 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /lib/Anego/CLI/Status.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI::Status; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Anego::Config; 7 | use Anego::Logger; 8 | use Anego::Task::GitLog; 9 | 10 | sub run { 11 | my $config = Anego::Config->load; 12 | 13 | my $logs = Anego::Task::GitLog->fetch; 14 | errorf("No change log\n") if @{ $logs } == 0; 15 | 16 | printf <<'__EOF__', $config->rdbms, $config->database, $config->schema_class, $config->schema_path; 17 | 18 | RDBMS: %s 19 | Database: %s 20 | Schema class: %s (%s) 21 | 22 | Hash Commit message 23 | -------------------------------------------------- 24 | __EOF__ 25 | 26 | for my $log (@{ $logs }) { 27 | printf "%7s %s\n", $log->{hash}, $log->{message}; 28 | } 29 | } 30 | 31 | 1; 32 | -------------------------------------------------------------------------------- /lib/Anego/CLI/Version.pm: -------------------------------------------------------------------------------- 1 | package Anego::CLI::Version; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Anego; 7 | 8 | sub run { 9 | print "Anego: $Anego::VERSION\n"; 10 | } 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /lib/Anego/Config.pm: -------------------------------------------------------------------------------- 1 | package Anego::Config; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use File::Spec; 6 | use DBI; 7 | 8 | use Anego::Logger; 9 | 10 | our $CONFIG_PATH; 11 | our $CONFIG; 12 | 13 | sub config_path { $CONFIG_PATH || './.anego.pl' } 14 | 15 | sub load { 16 | my ($class) = @_; 17 | 18 | return $CONFIG if $CONFIG; 19 | 20 | my $config_path = $class->config_path; 21 | errorf("Could not find config file: $config_path\n") unless -f $config_path; 22 | 23 | my $config = do $config_path or errorf("Could not load config file: $@"); 24 | $CONFIG = bless $config, $class; 25 | 26 | return $CONFIG; 27 | } 28 | 29 | sub schema_class { $_[0]->{schema_class} } 30 | sub connect_info { $_[0]->{connect_info} } 31 | 32 | sub database { 33 | my ($self) = @_; 34 | unless ($self->{database}) { 35 | $self->{database} = do { my (undef, $d) = $self->connect_info->[0] =~ /(database|dbname|name|db)=([\w:]+)/; $d }; 36 | } 37 | return $self->{database}; 38 | } 39 | 40 | sub schema_path { 41 | my ($self) = @_; 42 | unless ($self->{schema_path}) { 43 | my @splited_schema_class = split /::/, $self->schema_class; 44 | my $basename = pop @splited_schema_class; 45 | 46 | $self->{schema_path} = File::Spec->catfile('lib', @splited_schema_class, "$basename.pm"); 47 | } 48 | return $self->{schema_path}; 49 | } 50 | 51 | sub rdbms { 52 | my ($self) = @_; 53 | unless ($self->{rdbms}) { 54 | my $dsn = $self->connect_info->[0]; 55 | $self->{rdbms} = $dsn =~ /:mysql:/ ? 'MySQL' 56 | : $dsn =~ /:Pg:/ ? 'PostgreSQL' 57 | : do { my ($d) = $dsn =~ /dbi:(.*?):/; $d }; 58 | } 59 | return $self->{rdbms}; 60 | } 61 | 62 | sub dbh { 63 | my ($self) = @_; 64 | unless ($self->{dbh}) { 65 | $self->{dbh} = DBI->connect(@{ $self->connect_info }); 66 | } 67 | return $self->{dbh}; 68 | } 69 | 70 | 1; 71 | -------------------------------------------------------------------------------- /lib/Anego/Git.pm: -------------------------------------------------------------------------------- 1 | package Anego::Git; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw/ Exporter /; 6 | use Git::Repository; 7 | 8 | use Anego::Logger; 9 | 10 | our @EXPORT = qw/ git_log git_cat_file /; 11 | 12 | sub git_log { 13 | Git::Repository->new->run('log', @_); 14 | } 15 | 16 | sub git_cat_file { 17 | Git::Repository->new->run('cat-file', '-p', @_); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/Anego/Logger.pm: -------------------------------------------------------------------------------- 1 | package Anego::Logger; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw/ Exporter /; 6 | 7 | use Term::ANSIColor qw/ colored /; 8 | 9 | our @EXPORT = qw/ infof warnf errorf /; 10 | 11 | use constant { 12 | INFO => 1, 13 | WARN => 2, 14 | ERROR => 3, 15 | }; 16 | 17 | our $COLORS = { 18 | INFO, => 'cyan', 19 | WARN, => 'yellow', 20 | ERROR, => 'red', 21 | }; 22 | 23 | sub _print { 24 | my ($type, $string, @args) = @_; 25 | 26 | my $message = sprintf($string, (map { defined($_) ? $_ : '-' } @args)); 27 | my $colored_message = defined $COLORS->{$type} 28 | ? colored $message, $COLORS->{$type} 29 | : $message; 30 | 31 | my $fh = $type && $type <= INFO ? *STDOUT : *STDERR; 32 | print {$fh} $colored_message; 33 | } 34 | 35 | sub infof { _print(INFO, @_) } 36 | 37 | sub warnf { _print(WARN, @_) } 38 | 39 | sub errorf { 40 | _print(ERROR, @_); 41 | exit 1; 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /lib/Anego/Task/Diff.pm: -------------------------------------------------------------------------------- 1 | package Anego::Task::Diff; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use SQL::Translator::Diff; 6 | 7 | use Anego::Config; 8 | 9 | sub diff { 10 | my ($class, $source, $target) = @_; 11 | my $config = Anego::Config->load; 12 | 13 | my $diff = SQL::Translator::Diff->new({ 14 | output_db => $config->rdbms, 15 | source_schema => $source->schema, 16 | target_schema => $target->schema, 17 | })->compute_differences->produce_diff_sql; 18 | 19 | # ignore initial comments. 20 | 1 while $diff =~ s/\A--.*?\r?\n//ms; 21 | 22 | return $diff =~ /\A\s*-- No differences found;\s*\z/ms ? undef : $diff; 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/Anego/Task/GitLog.pm: -------------------------------------------------------------------------------- 1 | package Anego::Task::GitLog; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Anego::Config; 7 | use Anego::Git; 8 | use Anego::Logger; 9 | 10 | sub fetch { 11 | my $config = Anego::Config->load; 12 | my @revs = map { +{ 13 | hash => $_->[0], 14 | message => $_->[1], 15 | } } map { 16 | [ split /\t/, $_ ] 17 | } git_log("--pretty=format:%h\t%s", $config->schema_path); 18 | return \@revs; 19 | } 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /lib/Anego/Task/SchemaLoader.pm: -------------------------------------------------------------------------------- 1 | package Anego::Task::SchemaLoader; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Digest::MD5 qw/ md5_hex /; 6 | use SQL::Translator; 7 | 8 | use Anego::Config; 9 | use Anego::Git; 10 | use Anego::Logger; 11 | 12 | sub from { 13 | my $class = shift; 14 | my $method = lc(shift || 'latest'); 15 | my @args = @_; 16 | 17 | unless ($class->can($method)) { 18 | errorf("Could not use method: %s\n", $method); 19 | } 20 | 21 | return $class->$method(@args); 22 | } 23 | 24 | sub revision { 25 | my ($class, $revision) = @_; 26 | my $config = Anego::Config->load; 27 | 28 | my $schema_class = $config->schema_class; 29 | my $schema_str = git_cat_file(sprintf('%s:%s', $revision, $config->schema_path)); 30 | 31 | my $ddl = _load_ddl_from_schema_string($schema_class, $schema_str); 32 | 33 | my $tr = SQL::Translator->new( 34 | parser => $config->rdbms, 35 | data => \$ddl, 36 | ); 37 | $tr->translate; 38 | return _filter($tr); 39 | } 40 | 41 | sub latest { 42 | my ($class) = @_; 43 | my $config = Anego::Config->load; 44 | 45 | my $schema_class = $config->schema_class; 46 | my $schema_path = $config->schema_path; 47 | 48 | errorf("Could not find schema class file: $schema_path") unless -f $schema_path; 49 | 50 | open my $fh, '<', $schema_path or errorf("Failed to open: $!"); 51 | my $schema_str = do { local $/; <$fh> }; 52 | close $fh; 53 | 54 | my $ddl = _load_ddl_from_schema_string($schema_class, $schema_str); 55 | 56 | my $tr = SQL::Translator->new( 57 | parser => $config->rdbms, 58 | data => \$ddl, 59 | ); 60 | $tr->translate; 61 | return _filter($tr); 62 | } 63 | 64 | sub database { 65 | my ($class) = @_; 66 | my $config = Anego::Config->load; 67 | 68 | my $tr = SQL::Translator->new( 69 | parser => 'DBI', 70 | parser_args => { dbh => $config->dbh }, 71 | ); 72 | $tr->translate; 73 | return _filter($tr); 74 | } 75 | 76 | sub _load_ddl_from_schema_string { 77 | my ($schema_class, $schema_str) = @_; 78 | 79 | $schema_str =~ s/package\s+$schema_class;?//; 80 | 81 | my $klass = sprintf('Anego::Task::SchemaLoader::__ANON__::%s', md5_hex(int rand 65535)); 82 | eval sprintf <<'__SRC__', $klass, $schema_str; 83 | package %s; 84 | 85 | %s 86 | __SRC__ 87 | 88 | return $klass->output; 89 | } 90 | 91 | 92 | sub _filter { 93 | my ($tr) = @_; 94 | return $tr unless $tr; 95 | 96 | my $config = Anego::Config->load; 97 | if ($config->rdbms eq 'MySQL') { 98 | for my $table ($tr->schema->get_tables) { 99 | my $options = $table->options; 100 | if (my ($idx) = grep { $options->[$_]->{AUTO_INCREMENT} } 0..$#{$options}) { 101 | splice @{ $options }, $idx, 1; 102 | } 103 | for my $field ($table->get_fields) { 104 | delete $field->{default_value} if $field->{is_nullable} && exists $field->{default_value} && $field->{default_value} eq 'NULL'; 105 | } 106 | } 107 | } 108 | return $tr; 109 | } 110 | 111 | 1; 112 | -------------------------------------------------------------------------------- /lib/Anego/Util.pm: -------------------------------------------------------------------------------- 1 | package Anego::Util; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw/ Exporter /; 6 | 7 | use Anego::Logger; 8 | use Anego::Config; 9 | 10 | our @EXPORT = qw/ do_sql /; 11 | 12 | sub do_sql { 13 | my ($sql) = @_; 14 | my $config = Anego::Config->load; 15 | 16 | my @statements = map { "$_;" } grep { /\S+/ } split /;/, $sql; 17 | for my $statement (@statements) { 18 | $config->dbh->do($statement) or errorf($config->dbh->errstr) 19 | } 20 | } 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /minil.toml: -------------------------------------------------------------------------------- 1 | name = "Anego" 2 | badges = ["travis"] 3 | authority="cpan:PAPIX" 4 | 5 | module_maker="ModuleBuildTiny" 6 | 7 | -------------------------------------------------------------------------------- /script/anego: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use lib 'lib'; 6 | 7 | use Anego::CLI; 8 | 9 | Anego::CLI->run(@ARGV); 10 | -------------------------------------------------------------------------------- /t/basic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use Test::Git; 5 | use Test::Requires::Git; 6 | 7 | use Capture::Tiny qw/ capture /; 8 | use DBI; 9 | use File::Spec; 10 | 11 | use Anego::Config; 12 | use Anego::CLI::Diff; 13 | use Anego::CLI::Migrate; 14 | use Anego::CLI::Status; 15 | 16 | $Anego::Logger::COLORS = {}; 17 | 18 | eval 'use DBD::SQLite'; 19 | if ($@) { plan skip_all => 'DBD::SQLite is required' } 20 | 21 | test_requires_git; 22 | 23 | my $repo = test_repository; 24 | $repo->run(qw/ config --local user.name papix /); 25 | $repo->run(qw/ config --local user.email mail@papix.net /); 26 | 27 | chdir $repo->work_tree; 28 | mkdir File::Spec->catdir(qw/ lib /); 29 | mkdir File::Spec->catdir(qw/ lib MyApp /); 30 | 31 | my $schema_file = File::Spec->catfile($repo->work_tree, qw/ lib MyApp Schema.pm /); 32 | 33 | my $schema1 = <<__SCHEMA__; 34 | package MyApp::Schema; 35 | use strict; 36 | use warnings; 37 | use utf8; 38 | 39 | use DBIx::Schema::DSL; 40 | 41 | database 'SQLite'; 42 | 43 | create_table user => columns { 44 | integer 'id' => not_null, unsigned, primary_key; 45 | varchar 'name' => not_null; 46 | }; 47 | 48 | 1; 49 | __SCHEMA__ 50 | 51 | spew($schema_file, $schema1); 52 | $repo->run('add', $schema_file); 53 | $repo->run('commit', '-m', 'initial commit'); 54 | 55 | my $schema2 = <<__SCHEMA__; 56 | package MyApp::Schema; 57 | use strict; 58 | use warnings; 59 | use utf8; 60 | 61 | use DBIx::Schema::DSL; 62 | 63 | database 'SQLite'; 64 | 65 | create_table user => columns { 66 | integer 'id' => not_null, unsigned, primary_key; 67 | varchar 'name' => not_null; 68 | 69 | datetime 'created_at' => not_null; 70 | datetime 'updated_at' => not_null; 71 | }; 72 | 73 | 1; 74 | __SCHEMA__ 75 | 76 | spew($schema_file, $schema2); 77 | $repo->run('add', $schema_file); 78 | $repo->run('commit', '-m', 'second commit'); 79 | 80 | my $config = <<__CONFIG__; 81 | { 82 | connect_info => ['dbi:SQLite:dbname=:memory:', '', ''], 83 | schema_class => 'MyApp::Schema', 84 | } 85 | __CONFIG__ 86 | 87 | my $config_file = File::Spec->catfile($repo->work_tree, qw/ .anego.pl /); 88 | spew($config_file, $config); 89 | 90 | subtest 'status subcommand' => sub { 91 | $Anego::Config::CONFIG = undef; 92 | 93 | my ($stdout, $stderr) = capture { 94 | Anego::CLI::Status->run(); 95 | }; 96 | 97 | like $stdout, qr!RDBMS:\s+SQLite!; 98 | like $stdout, qr!Database:\s+:memory:!; 99 | like $stdout, qr!Schema class:\s+MyApp::Schema\s+\(lib/MyApp/Schema\.pm\)!; 100 | 101 | like $stdout, qr!initial commit!; 102 | like $stdout, qr!second commit!; 103 | }; 104 | 105 | subtest 'diff / migrate subcommand (latest)' => sub { 106 | $Anego::Config::CONFIG = undef; 107 | 108 | subtest 'diff (1)' => sub { 109 | my ($stdout, $stderr) = capture { 110 | Anego::CLI::Diff->run(); 111 | }; 112 | 113 | is $stdout, <<__DDL__; 114 | 115 | BEGIN; 116 | 117 | CREATE TABLE user ( 118 | id INTEGER PRIMARY KEY NOT NULL, 119 | name VARCHAR(255) NOT NULL, 120 | created_at DATETIME NOT NULL, 121 | updated_at DATETIME NOT NULL 122 | ); 123 | 124 | 125 | COMMIT; 126 | 127 | __DDL__ 128 | is $stderr, ''; 129 | }; 130 | 131 | subtest 'migrate (1)' => sub { 132 | my ($stdout, $stderr) = capture { 133 | Anego::CLI::Migrate->run(); 134 | }; 135 | 136 | is $stdout, "Migrated\n"; 137 | is $stderr, ''; 138 | }; 139 | 140 | subtest 'diff (2)' => sub { 141 | my ($stdout, $stderr) = capture { 142 | Anego::CLI::Diff->run(); 143 | }; 144 | 145 | is $stdout, ''; 146 | is $stderr, "target schema == database schema, should no differences\n"; 147 | }; 148 | 149 | subtest 'migrate (2)' => sub { 150 | my ($stdout, $stderr) = capture { 151 | Anego::CLI::Migrate->run(); 152 | }; 153 | 154 | is $stdout, ''; 155 | is $stderr, "target schema == database schema, should no differences\n"; 156 | }; 157 | }; 158 | 159 | subtest 'diff / migrate subcommand (revision)' => sub { 160 | $Anego::Config::CONFIG = undef; 161 | 162 | subtest 'diff (1)' => sub { 163 | my ($stdout, $stderr) = capture { 164 | Anego::CLI::Diff->run(qw/ revision HEAD^ /); 165 | }; 166 | 167 | is $stdout, <<__DDL__; 168 | 169 | BEGIN; 170 | 171 | CREATE TABLE user ( 172 | id INTEGER PRIMARY KEY NOT NULL, 173 | name VARCHAR(255) NOT NULL 174 | ); 175 | 176 | 177 | COMMIT; 178 | 179 | __DDL__ 180 | is $stderr, ''; 181 | }; 182 | 183 | subtest 'migrate (1)' => sub { 184 | my ($stdout, $stderr) = capture { 185 | Anego::CLI::Migrate->run(qw/ revision HEAD^ /); 186 | }; 187 | 188 | is $stdout, "Migrated\n"; 189 | is $stderr, ''; 190 | }; 191 | 192 | subtest 'diff (2)' => sub { 193 | my ($stdout, $stderr) = capture { 194 | Anego::CLI::Diff->run(qw/ revision HEAD^ /); 195 | }; 196 | 197 | is $stdout, ''; 198 | is $stderr, "target schema == database schema, should no differences\n"; 199 | }; 200 | 201 | subtest 'migrate (2)' => sub { 202 | my ($stdout, $stderr) = capture { 203 | Anego::CLI::Migrate->run(qw/ revision HEAD^ /); 204 | }; 205 | 206 | is $stdout, ''; 207 | is $stderr, "target schema == database schema, should no differences\n"; 208 | }; 209 | }; 210 | 211 | sub spew { 212 | my ($path, $content) = @_; 213 | 214 | open my $fh, '>', $path or die "$!"; 215 | print $fh $content; 216 | close $fh; 217 | } 218 | 219 | done_testing; 220 | --------------------------------------------------------------------------------