├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .perltidyrc ├── .pls_cache └── index ├── Changes ├── MANIFEST.SKIP ├── Makefile.PL ├── README.md ├── examples ├── blog │ ├── blog.conf │ ├── lib │ │ ├── Blog.pm │ │ └── Blog │ │ │ ├── Controller │ │ │ └── Posts.pm │ │ │ └── Model │ │ │ └── Posts.pm │ ├── migrations │ │ └── blog.sql │ ├── script │ │ └── blog │ └── templates │ │ ├── layouts │ │ └── blog.html.ep │ │ └── posts │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ └── show.html.ep └── chat.pl ├── lib ├── Mojo │ ├── mysql.pm │ └── mysql │ │ ├── Database.pm │ │ ├── Migrations.pm │ │ ├── PubSub.pm │ │ ├── Results.pm │ │ └── Transaction.pm └── SQL │ └── Abstract │ └── mysql.pm └── t ├── 00-project.t ├── async_query_in_flight.t ├── blocking-leak.t ├── connection.t ├── crud.t ├── database.t ├── destroy.t ├── json.t ├── mariadb.t ├── migrations.t ├── migrations └── test.sql ├── mysql.t ├── mysql_auto_reconnect.t ├── mysql_lite_app.t ├── pubsub.t ├── results.t ├── results_methods.t ├── sql-live.t ├── sql.t ├── strict-mode.t ├── test-dbi-async.t └── utf8.t /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - "**" 7 | jobs: 8 | perl: 9 | name: "Perl ${{matrix.perl}} on ${{matrix.os}}" 10 | strategy: 11 | matrix: 12 | os: ["ubuntu-latest"] 13 | perl: ["5.32", "5.26", "5.16"] 14 | runs-on: "${{matrix.os}}" 15 | steps: 16 | - name: Install and start mysql 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y mysql-client 20 | sudo systemctl start mysql.service 21 | mysql -e 'create database test' -uroot -proot 22 | - run: mysql -V 23 | - uses: shogo82148/actions-setup-perl@v1 24 | with: 25 | perl-version: "${{matrix.perl}}" 26 | - run: perl -V 27 | - uses: actions/checkout@v2 28 | - name: Fix ExtUtils::MakeMaker for Perl 5.16 29 | run: cpanm -n App::cpanminus ExtUtils::MakeMaker 30 | - name: Install dependencies 31 | run: | 32 | cpanm -n Test::CPAN::Changes Test::Pod::Coverage Test::Pod Test::Spelling 33 | cpanm -n --installdeps . 34 | - name: Run tests 35 | run: prove -l t/*.t 36 | env: 37 | HARNESS_OPTIONS: j4 38 | TEST_FOR: 500 39 | TEST_ONLINE: mysql://root:root@localhost:3306/test 40 | TEST_POD: 1 41 | TEST_PUBSUB: 1 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | *.bak 3 | *.o 4 | *.old 5 | *.swp 6 | /*.tar.gz 7 | /blib/ 8 | /cover_db 9 | /inc/ 10 | /local 11 | /Makefile 12 | /Makefile.old 13 | /MANIFEST 14 | /MANIFEST.bak 15 | /META* 16 | /MYMETA* 17 | /nytprof* 18 | /pm_to_blib 19 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -pbp # Start with Perl Best Practices 2 | -w # Show all warnings 3 | -iob # Ignore old breakpoints 4 | -l=120 # 120 characters per line 5 | -mbl=2 # No more than 2 blank lines 6 | -i=2 # Indentation is 2 columns 7 | -ci=2 # Continuation indentation is 2 columns 8 | -vt=0 # Less vertical tightness 9 | -pt=2 # High parenthesis tightness 10 | -bt=2 # High brace tightness 11 | -sbt=2 # High square bracket tightness 12 | -isbc # Don't indent comments without leading space 13 | -nst # Don't output to STDOUT 14 | -wn # Opening and closing containers to be "welded" together 15 | -------------------------------------------------------------------------------- /.pls_cache/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhthorsen/mojo-mysql/550d7cfd2a7cf00c96a79fb7ca3ffc4b4947031d/.pls_cache/index -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for perl distribution Mojo-mysql 2 | 3 | 1.27 2023-10-26T20:59:16 4 | - Fix not cleaning up during GLOBAL_PHASE is DESTRUCT 5 | Contributor: Lasse Løvik 6 | 7 | 1.26 2022-12-09T10:41:01 8 | - Add support for "MOJO_MYSQL_PREFER_DRIVER" in Makefile.PL 9 | - Add support for autodetecting DBD::MariaDB 10 | - Fix spelling mistakes #88 11 | Contributor: Lucas Tiago de Moraes 12 | - Specified Perl version 13 | - Updated basic repository files 14 | - Updated contributors list 15 | 16 | 1.25 2021-11-22T18:13:22+0900 17 | - Changed DBD::mysql is not a dependency if DBD::MariaDB 1.21 is already installed #88 18 | 19 | 1.24 2021-10-06T07:57:45+0900 20 | - Fixed last_insert_id() and warnings_count() for MariaDB #86 21 | Contributor: Dan Book 22 | 23 | 1.23 2021-09-11T10:36:17+0200 24 | - Fix version number issues #84 25 | - Internal changes regarding MariaDB/mysql attributes 26 | 27 | 1.21 2021-04-28T12:15:39+0900 28 | - Fix uninitialized warning with expand(1) #83 29 | - Removed delay() from tests and documentation 30 | 31 | 1.20 2020-09-05T13:27:52+0900 32 | - Mojo::mysql::PubSub is less susceptible to deadlocks/timeouts #80 33 | Contributor: Larry Leszczynski 34 | 35 | 1.19 2020-05-01T06:58:48+0900 36 | - Fix documentation for Mojo::mysql::close_idle_connections() 37 | - Add documentation for SQL::Abstract::mysql::where() #78 38 | - Made SQL::Abstract::mysql more compatible with SQL::Abstract #77 39 | Contributor: Matt S Trout 40 | 41 | 1.18 2019-12-01T09:41:10+0100 42 | - Add missing code for SELECT AS 43 | 44 | 1.17 2019-08-01T09:44:57+0200 45 | - Fix leaking $sth when used in blocking mode, fixes #66 46 | 47 | 1.16 2019-06-25T06:33:55+0200 48 | - Add DBI to prerequisites 49 | Contributor: Mohammad S Anwar 50 | 51 | 1.15 2019-04-22T06:41:34+0200 52 | - Add support for NATURAL JOIN and JOIN USING #59 53 | 54 | 1.14 2019-03-23T08:07:17+0100 55 | - Correct handling of fetchall and arrays in Results 56 | 57 | 1.13 2019-03-02T11:27:01+0800 58 | - Add support for DBD::MariaDB #47 59 | - Add missing code for SQL JOIN #56 60 | Contributor: Tekki 61 | - Made it clearer that PubSub is an experiment 62 | 63 | 1.12 2019-01-05T12:34:13+0900 64 | - Bumped Mojolicious version to 8.03 65 | - Bumped SQL::Abstract version to 1.86 #49 66 | 67 | 1.11 2018-12-18T19:27:08+0900 68 | - Avoid "Gathering async_query_in_flight results for the wrong handle" warning 69 | 70 | 1.10 2018-12-18T07:25:14+0900 71 | - Add SQL::Abstract::mysql 72 | Contributor: Rolf Stöckli 73 | 74 | 1.09 2018-11-27T09:32:01+0900 75 | - Fix MariaDB/MySQL incompatibility #41 76 | Contributor: Rolf Stöckli 77 | - Fix documentation mismatch regarding "mysql_client_found_rows" #42 78 | Contributor: Yuriy Zhilovets 79 | 80 | 1.08 2018-11-13T17:31:49+0900 81 | - Fix query() with callback returns $self 82 | - Fix Gathering async_query_in_flight results for the wrong handle bug 83 | - Add close_idle_connections to Mojo::mysql 84 | - Add support for working with JSON 85 | - Add tables method to Mojo::mysql::Database 86 | - Change database name is optional in constructor #38 87 | 88 | 1.07 2018-05-03T12:25:08+0200 89 | - Fix using "mysql_socket" instead of "host" when connecting to a unix socket #34 90 | - Allow constructor to take a single hashref #37 91 | 92 | 1.06 2018-02-27T19:32:40+0100 93 | - Changed from_string() to also accept Mojo::URL objects #36 94 | Contributor: Karl Rune Nilsen 95 | 96 | 1.05 2017-11-11T10:04:40+0800 97 | - Add delete_p(), insert_p(), query_p(), select_p() and update_p() 98 | 99 | 1.04 2017-08-14T19:22:33+0200 100 | - Documented "mysql_enable_utf8" v.s. "mysql_enable_utf8mb4" #32 101 | - Can pass on attributes to new() 102 | 103 | 1.03 2017-05-21T23:19:29+0200 104 | - Add ability to set types of query parameters #31 105 | Contributor: Dan Book 106 | 107 | 1.02 2017-05-15T20:34:01+0200 108 | - Fix utf8 handling in DBD::mysql 4.042 109 | - Prevent warnings when creating the mojo_migrations table #26 110 | - Add proper quoting of table and column names #30 111 | - Add warning when using Mojo::mysql::PubSub 112 | 113 | 1.01 2017-03-25T08:24:29+0100 114 | - Add strict_mode() method and constructor #29 115 | 116 | 1.00 2017-02-12T18:30:58+0100 117 | - Add support for generating queries with SQL::Abstract 118 | - Add abstract attribute to Mojo::Pg 119 | - Add delete, insert, select and update methods to Mojo::Pg::Database 120 | - Add database_class attribute to Mojo::mysql 121 | - Add results_class attribute to Mojo::mysql::Database 122 | - Improved contextual caller information in query error messages 123 | - Compatible with Mojolicious 7.18 124 | 125 | 0.14 2016-02-15T14:06:24+0100 126 | - Add Mojo::mysql::auto_migrate 127 | 128 | 0.13 2016-01-27T21:05:37+0100 129 | - Remove deprecrated do() method 130 | - Add finish() to Mojo::mysql::Results 131 | - Fix bug where non-blocking queries could get lost after the database 132 | connection got closed unexpectedly 133 | https://github.com/kraih/mojo-pg/commit/2165b8e1131f2a5044ec2aae1c0ba8a00232b7c8 134 | - Improved Mojo::mysql::Migrations to detect if the currently active version 135 | is greater than the latest version. 136 | https://github.com/kraih/mojo-pg/commit/92bc312e725042b748950b9c61319d0256d0004a 137 | 138 | 0.12 2015-05-02T17:55:13Z 139 | - Added module Mojo::mysql::PubSub. 140 | - Added pubsub attribute to Mojo::mysql. 141 | 142 | 0.11 2015-04-06T03:38:31Z 143 | - Fixed bug in Mojo::mysql::Migrations where migrations would sometimes be 144 | executed in the wrong order. 145 | 146 | 0.10 2015-04-05T23:32:03Z 147 | - Fixed bug in Mojo::mysql::Migrations where the latest version could not 148 | always be determined correctly. (Hernan Lopes) 149 | - Updated blog example application from Mojo::Pg 150 | 151 | 0.09 2015-03-29T18:29:35Z 152 | - Fixed Mojo::mysql::Migrations to allow delimiter in comments and quoted 153 | strings 154 | - delimiter support in Mojo::mysql::Migrations, allows creation of stored 155 | procedures and triggers in migration scripts 156 | - 'quote' and 'quote_id' methods in Mojo::mysql::Database 157 | 158 | 0.08 2015-03-24T13:14:32Z 159 | - blog example from Mojo::Pg 160 | - better examples in POD 161 | - Improved Mojo::mysql::Migrations to make no changes to the database when 162 | checking the currently active version. 163 | - Fixed Mojo::mysql::Migrations to handle UTF-8 encoded files correctly 164 | 165 | 0.07 2015-03-09T13:34:31Z 166 | - Deprecated Mojo::mysql::Database::do in favour of 167 | Mojo::mysql::Database::query as in Mojo::Pg 168 | - Some new methods in Mojo::mysql::Result eliminating need to access sth 169 | - bugfix in Mojo::mysql::Migrations with trailing whitespace after last 170 | semicolon 171 | 172 | 0.06 2015-02-25T17:31:24Z 173 | - OO Mojo::Loader API is deprecated in Mojolicious 5.81 174 | 175 | 0.05 2015-01-22T00:14:11Z 176 | - Do not cache statement handles in Mojo::mysql::Database. 177 | - Synced changes from Mojo::Pg 178 | - utf8 enabled by default 179 | 180 | 0.04 2015-01-02T12:15:26Z 181 | - Add support for migrations #3 Contributor: Curt Hochwender 182 | - Add Mojo::mysql::Migrations. 183 | - Add migrations attribute to Mojo::msyql 184 | - Add db attribute to Mojo::mysql::Transaction. 185 | - Fix bug where Perl would close the DBD::mysql file descriptor after it 186 | had been used with the event loop. 187 | - Remove dbh attribute from Mojo::mysql::Transaction 188 | - Updated Mojolicious requirement to 5.49 to ensure migrations in the DATA 189 | section are not served as static files 190 | 191 | 0.03 2014-10-13T13:39:59Z 192 | - Removed commit and rollback methods from Mojo::mysql::Database. 193 | - Added Mojo::mysql::Transaction. 194 | 195 | 0.02 2014-10-12T18:14:33Z 196 | - Force mysql_auto_reconnect = 0 to avoid nasty reconnect bugs under some 197 | environments. https://metacpan.org/pod/DBD::mysql#mysql_auto_reconnect 198 | 199 | 0.01 2014-10-11T17:34:05Z 200 | - First release. 201 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | ~$ 2 | \#$ 3 | \.# 4 | \.bak$ 5 | \.old$ 6 | \.perltidyrc$ 7 | \.swp$ 8 | \.tmp$ 9 | \B\.DS_Store 10 | \B\._ 11 | \B\.git\b 12 | \B\.gitattributes\b 13 | \B\.github\b 14 | \B\.gitignore\b 15 | \B\.pls_cache\b 16 | \B\.vstags\b 17 | \bMANIFEST\.bak 18 | \bMakeMaker-\d 19 | \bMakefile$ 20 | \b\.# 21 | \bblib/ 22 | \bblibdirs\.ts$ # 6.18 through 6.25 generated this 23 | \bcover_db\b 24 | \bcovered\b 25 | \bnode_modules\b 26 | \bpm_to_blib$ 27 | \bpm_to_blib\.ts$ 28 | ^MANIFEST\.SKIP 29 | ^MYMETA\. 30 | ^README\.md 31 | ^README\.pod 32 | ^local/ 33 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.016; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker; 6 | 7 | my @DRIVER = $ENV{MOJO_MYSQL_PREFER_DRIVER} ? (split /=/, $ENV{MOJO_MYSQL_PREFER_DRIVER}) : (); 8 | $DRIVER[0] ||= eval('use DBD::MariaDB 1.21;1') ? 'DBD::MariaDB' : 'DBD::mysql'; 9 | $DRIVER[1] ||= $DRIVER[0] eq 'DBD::mysql' ? '4.050' : $DRIVER[0] eq 'DBD::MariaDB' ? '1.21' : '0'; 10 | 11 | my $GITHUB_URL = 'https://github.com/jhthorsen/mojo-mysql'; 12 | my %WriteMakefileArgs = ( 13 | NAME => 'Mojo::mysql', 14 | AUTHOR => 'Jan Henning Thorsen ', 15 | LICENSE => 'artistic_2', 16 | ABSTRACT_FROM => 'lib/Mojo/mysql.pm', 17 | VERSION_FROM => 'lib/Mojo/mysql.pm', 18 | TEST_REQUIRES => {'Test::More' => '0.90'}, 19 | PREREQ_PM => {@DRIVER, 'DBI' => '1.643', 'Mojolicious' => '8.03', 'SQL::Abstract' => '1.86'}, 20 | META_MERGE => { 21 | 'dynamic_config' => 0, 22 | 'meta-spec' => {version => 2}, 23 | 'no_index' => {directory => [qw(examples t)]}, 24 | 'prereqs' => {runtime => {requires => {perl => '5.016'}}}, 25 | 'resources' => { 26 | bugtracker => {web => "$GITHUB_URL/issues"}, 27 | homepage => $GITHUB_URL, 28 | license => ['http://www.opensource.org/licenses/artistic-license-2.0'], 29 | repository => {type => 'git', url => "$GITHUB_URL.git", web => $GITHUB_URL}, 30 | x_IRC => {url => 'irc://irc.libera.chat/#convos', web => 'https://web.libera.chat/#convos'}, 31 | }, 32 | 'x_contributors' => [ 33 | 'Adam Hopkins ', 34 | 'Alexander Karelas ', 35 | 'Curt Hochwender ', 36 | 'Dan Book ', 37 | 'Doug Bell ', 38 | 'Florian Heyer ', 39 | 'Hernan Lopes ', 40 | 'Jan Henning Thorsen ', 41 | 'Karl Rune Nilsen ', 42 | 'Larry Leszczynski ', 43 | 'Lucas Tiago de Moraes ', 44 | 'Matt S Trout ', 45 | 'Mike Magowan ', 46 | 'Mohammad S Anwar ', 47 | 'Rolf Stöckli ', 48 | 'Sebastian Riedel ', 49 | 'Svetoslav Naydenov ', 50 | 'Svetoslav Naydenov ', 51 | 'Tekki ', 52 | ], 53 | }, 54 | test => {TESTS => (-e 'META.yml' ? 't/*.t' : 't/*.t xt/*.t')}, 55 | ); 56 | 57 | unless (eval { ExtUtils::MakeMaker->VERSION('6.63_03') }) { 58 | my $test_requires = delete $WriteMakefileArgs{TEST_REQUIRES}; 59 | @{$WriteMakefileArgs{PREREQ_PM}}{keys %$test_requires} = values %$test_requires; 60 | } 61 | 62 | WriteMakefile(%WriteMakefileArgs); 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | Mojo::mysql - Mojolicious and Async MySQL/MariaDB 4 | 5 | # SYNOPSIS 6 | 7 | use Mojo::mysql; 8 | 9 | # Connect to a local database 10 | my $mysql = Mojo::mysql->strict_mode('mysql://username@/test'); 11 | 12 | # Connect to a remote database 13 | my $mysql = Mojo::mysql->strict_mode('mysql://username:password@hostname/test'); 14 | # MySQL >= 8.0: 15 | my $mysql = Mojo::mysql->strict_mode('mysql://username:password@hostname/test;mysql_ssl=1'); 16 | 17 | # Use DBD::MariaDB instead of DBD::mysql 18 | my $mysql = Mojo::mysql->strict_mode('mariadb://username@/test'); 19 | 20 | # Create a table 21 | $mysql->db->query( 22 | 'create table names (id integer auto_increment primary key, name text)'); 23 | 24 | # Insert a few rows 25 | my $db = $mysql->db; 26 | $db->query('insert into names (name) values (?)', 'Sara'); 27 | $db->query('insert into names (name) values (?)', 'Stefan'); 28 | 29 | # Insert more rows in a transaction 30 | eval { 31 | my $tx = $db->begin; 32 | $db->query('insert into names (name) values (?)', 'Baerbel'); 33 | $db->query('insert into names (name) values (?)', 'Wolfgang'); 34 | $tx->commit; 35 | }; 36 | say $@ if $@; 37 | 38 | # Insert another row and return the generated id 39 | say $db->query('insert into names (name) values (?)', 'Daniel') 40 | ->last_insert_id; 41 | 42 | # Use SQL::Abstract::mysql to generate queries for you 43 | $db->insert('names', {name => 'Isabel'}); 44 | say $db->select('names', undef, {name => 'Isabel'})->hash->{id}; 45 | $db->update('names', {name => 'Bel'}, {name => 'Isabel'}); 46 | $db->delete('names', {name => 'Bel'}); 47 | 48 | # Select one row at a time 49 | my $results = $db->query('select * from names'); 50 | while (my $next = $results->hash) { 51 | say $next->{name}; 52 | } 53 | 54 | # Select all rows blocking 55 | $db->query('select * from names') 56 | ->hashes->map(sub { $_->{name} })->join("\n")->say; 57 | 58 | # Select all rows non-blocking 59 | $db->query('select * from names' => sub { 60 | my ($db, $err, $results) = @_; 61 | $results->hashes->map(sub { $_->{name} })->join("\n")->say; 62 | }); 63 | 64 | # Concurrent non-blocking queries (synchronized with promises) 65 | my $now = $db->query_p('select now() as now'); 66 | my $names = $db->query_p('select * from names'); 67 | Mojo::Promise->all($now, $names)->then(sub { 68 | my ($now, $names) = @_; 69 | say $now->[0]->hash->{now}; 70 | say $_->{name} for $names->[0]->hashes->each; 71 | })->catch(sub { 72 | my $err = shift; 73 | warn "Something went wrong: $err"; 74 | })->wait; 75 | 76 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running; 77 | 78 | # DESCRIPTION 79 | 80 | [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) is a tiny wrapper around [DBD::mysql](https://metacpan.org/pod/DBD%3A%3Amysql) and [DBD::MariaDB](https://metacpan.org/pod/DBD%3A%3AMariaDB) that 81 | makes [MySQL](http://www.mysql.org) and [MariaDB](https://mariadb.org/) a lot 82 | of fun to use with the [Mojolicious](http://mojolicio.us) real-time web 83 | framework. 84 | 85 | The two DBD drivers are compatible with both MySQL and MariaDB, but they offer 86 | different ["options"](#options). [DBD::MariaDB](https://metacpan.org/pod/DBD%3A%3AMariaDB) should have better unicode support 87 | though and might become the default in the future. 88 | 89 | Database and handles are cached automatically, so they can be reused 90 | transparently to increase performance. And you can handle connection timeouts 91 | gracefully by holding on to them only for short amounts of time. 92 | 93 | use Mojolicious::Lite; 94 | use Mojo::mysql; 95 | 96 | helper mysql => 97 | sub { state $mysql = Mojo::mysql->strict_mode('mysql://sri:s3cret@localhost/db') }; 98 | 99 | get '/' => sub { 100 | my $c = shift; 101 | my $db = $c->mysql->db; 102 | $c->render(json => $db->query('select now() as time')->hash); 103 | }; 104 | 105 | app->start; 106 | 107 | While all I/O operations are performed blocking, you can wait for long running 108 | queries asynchronously, allowing the [Mojo::IOLoop](https://metacpan.org/pod/Mojo%3A%3AIOLoop) event loop to perform 109 | other tasks in the meantime. Since database connections usually have a very low 110 | latency, this often results in very good performance. 111 | 112 | Every database connection can only handle one active query at a time, this 113 | includes asynchronous ones. So if you start more than one, they will be put on 114 | a waiting list and performed sequentially. To perform multiple queries 115 | concurrently, you have to use multiple connections. 116 | 117 | # Performed sequentially (10 seconds) 118 | my $db = $mysql->db; 119 | $db->query('select sleep(5)' => sub {...}); 120 | $db->query('select sleep(5)' => sub {...}); 121 | 122 | # Performed concurrently (5 seconds) 123 | $mysql->db->query('select sleep(5)' => sub {...}); 124 | $mysql->db->query('select sleep(5)' => sub {...}); 125 | 126 | All cached database handles will be reset automatically if a new process has 127 | been forked, this allows multiple processes to share the same [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) 128 | object safely. 129 | 130 | # EVENTS 131 | 132 | [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) inherits all events from [Mojo::EventEmitter](https://metacpan.org/pod/Mojo%3A%3AEventEmitter) and can emit the 133 | following new ones. 134 | 135 | ## connection 136 | 137 | $mysql->on(connection => sub { 138 | my ($mysql, $dbh) = @_; 139 | ... 140 | }); 141 | 142 | Emitted when a new database connection has been established. 143 | 144 | # ATTRIBUTES 145 | 146 | [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) implements the following attributes. 147 | 148 | ## abstract 149 | 150 | $abstract = $mysql->abstract; 151 | $mysql = $mysql->abstract(SQL::Abstract::mysql->new); 152 | 153 | [SQL::Abstract::mysql](https://metacpan.org/pod/SQL%3A%3AAbstract%3A%3Amysql) object used to generate CRUD queries for [Mojo::mysql::Database](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ADatabase). 154 | 155 | # Generate statements and bind values 156 | my ($stmt, @bind) = $mysql->abstract->select('names'); 157 | 158 | ## auto\_migrate 159 | 160 | my $bool = $mysql->auto_migrate; 161 | $mysql = $mysql->auto_migrate($bool); 162 | 163 | Automatically migrate to the latest database schema with ["migrations"](#migrations), as 164 | soon as the first database connection has been established. 165 | 166 | Defaults to false. 167 | 168 | ## database\_class 169 | 170 | $class = $mysql->database_class; 171 | $mysql = $mysql->database_class("MyApp::Database"); 172 | 173 | Class to be used by ["db"](#db), defaults to [Mojo::mysql::Database](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ADatabase). Note that this 174 | class needs to have already been loaded before ["db"](#db) is called. 175 | 176 | ## dsn 177 | 178 | my $dsn = $mysql->dsn; 179 | $mysql = $mysql->dsn('dbi:mysql:dbname=foo'); 180 | 181 | Data Source Name, defaults to `dbi:mysql:dbname=test`. 182 | 183 | ## max\_connections 184 | 185 | my $max = $mysql->max_connections; 186 | $mysql = $mysql->max_connections(3); 187 | 188 | Maximum number of idle database handles to cache for future use, defaults to 189 | `5`. 190 | 191 | ## migrations 192 | 193 | my $migrations = $mysql->migrations; 194 | $mysql = $mysql->migrations(Mojo::mysql::Migrations->new); 195 | 196 | [Mojo::mysql::Migrations](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3AMigrations) object you can use to change your database schema more 197 | easily. 198 | 199 | # Load migrations from file and migrate to latest version 200 | $mysql->migrations->from_file('/Users/sri/migrations.sql')->migrate; 201 | 202 | MySQL and MariaDB does not support nested transactions and DDL transactions. 203 | DDL statements cause implicit `COMMIT`. `ROLLBACK` will be called if any step 204 | of migration script fails, but only DML statements after the last implicit or 205 | explicit `COMMIT` can be reverted. Not all storage engines (like `MYISAM`) 206 | support transactions. 207 | 208 | This means database will most likely be left in unknown state if migration script fails. 209 | Use this feature with caution and remember to always backup your database. 210 | 211 | ## options 212 | 213 | my $options = $mysql->options; 214 | $mysql = $mysql->options({mysql_use_result => 1}); 215 | 216 | Options for database handles, defaults to activating `mysql_enable_utf8` (only 217 | for [DBD::mysql](https://metacpan.org/pod/DBD%3A%3Amysql)), `AutoCommit`, `AutoInactiveDestroy` as well as 218 | `RaiseError` and deactivating `PrintError`. `AutoCommit` and `RaiseError` 219 | are considered mandatory, so deactivating them would be very dangerous. 220 | 221 | `mysql_auto_reconnect` is never enabled, [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) takes care of dead connections. 222 | 223 | `AutoCommit` cannot not be disabled, use $db->[begin](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ADatabase#begin) to manage transactions. 224 | 225 | `RaiseError` is enabled for blocking and disabled in event loop for non-blocking queries. 226 | 227 | About `mysql_enable_utf8`: 228 | 229 | The mysql_enable_utf8 sets the utf8 charset which only supports up to 3-byte 230 | UTF-8 encodings. mysql_enable_utf8mb4 (as of DBD::mysql 4.032) properly 231 | supports encoding unicode characters to up to 4 bytes, such as 𠜎. It means the 232 | connection charset will be utf8mb4 (supported back to at least mysql 5.5) and 233 | these unicode characters will be supported, but no other changes. 234 | 235 | See also [https://github.com/jhthorsen/mojo-mysql/pull/32](https://github.com/jhthorsen/mojo-mysql/pull/32) 236 | 237 | ## password 238 | 239 | my $password = $mysql->password; 240 | $mysql = $mysql->password('s3cret'); 241 | 242 | Database password, defaults to an empty string. 243 | 244 | ## pubsub 245 | 246 | my $pubsub = $mysql->pubsub; 247 | $mysql = $mysql->pubsub(Mojo::mysql::PubSub->new); 248 | 249 | [Mojo::mysql::PubSub](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3APubSub) should be considered an EXPERIMENT! See 250 | ["DESCRIPTION" in Mojo::mysql::PubSub](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3APubSub#DESCRIPTION) for more information. 251 | 252 | ## username 253 | 254 | my $username = $mysql->username; 255 | $mysql = $mysql->username('batman'); 256 | 257 | Database username, defaults to an empty string. 258 | 259 | # METHODS 260 | 261 | [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) inherits all methods from [Mojo::EventEmitter](https://metacpan.org/pod/Mojo%3A%3AEventEmitter) and implements the 262 | following new ones. 263 | 264 | ## close\_idle\_connections 265 | 266 | $mysql = $mysql->close_idle_connections($keep); 267 | 268 | Close all connections that are not currently active, or limit the 269 | number of idle connections to `$keep`. 270 | 271 | ## db 272 | 273 | my $db = $mysql->db; 274 | 275 | Get [Mojo::mysql::Database](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ADatabase) object for a cached or newly created database 276 | handle. The database handle will be automatically cached again when that 277 | object is destroyed, so you can handle connection timeouts gracefully by 278 | holding on to it only for short amounts of time. 279 | 280 | ## from\_string 281 | 282 | $mysql = $mysql->from_string('mysql://user@/test'); 283 | 284 | Parse configuration from connection string. 285 | 286 | # Just a database 287 | $mysql->from_string('mysql:///db1'); 288 | 289 | # Username and database 290 | $mysql->from_string('mysql://batman@/db2'); 291 | 292 | # Username, password, host and database 293 | $mysql->from_string('mysql://batman:s3cret@localhost/db3'); 294 | 295 | # Username, domain socket and database 296 | $mysql->from_string('mysql://batman@%2ftmp%2fmysql.sock/db4'); 297 | 298 | # Username, database and additional options 299 | $mysql->from_string('mysql://batman@/db5?PrintError=1&RaiseError=0'); 300 | 301 | ## new 302 | 303 | my $mysql = Mojo::mysql->new; 304 | my $mysql = Mojo::mysql->new(%attrs); 305 | my $mysql = Mojo::mysql->new(\%attrs); 306 | my $mysql = Mojo::mysql->new('mysql://user@/test'); 307 | my $mysql = Mojo::mysql->new('mariadb://user@/test'); 308 | 309 | Construct a new [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) object either from ["ATTRIBUTES"](#attributes) and or parse 310 | connection string with ["from\_string"](#from_string) if necessary. 311 | 312 | Using the "mariadb" scheme requires the optional module [DBD::MariaDB](https://metacpan.org/pod/DBD%3A%3AMariaDB) version 313 | 1.21 (or later) to be installed. 314 | 315 | ## strict\_mode 316 | 317 | my $mysql = Mojo::mysql->strict_mode('mysql://user@/test'); 318 | my $mysql = $mysql->strict_mode($boolean); 319 | 320 | This method can act as both a constructor and a method. When called as a 321 | constructor, it will be the same as: 322 | 323 | my $mysql = Mojo::mysql->new('mysql://user@/test')->strict_mode(1); 324 | 325 | Enabling strict mode will execute the following statement when a new connection 326 | is created: 327 | 328 | SET SQL_MODE = CONCAT('ANSI,TRADITIONAL,ONLY_FULL_GROUP_BY,', @@sql_mode) 329 | SET SQL_AUTO_IS_NULL = 0 330 | 331 | The idea is to set up a connection that makes it harder for MySQL to allow 332 | "invalid" data to be inserted. 333 | 334 | This method will not be removed, but the internal commands is subject to 335 | change. 336 | 337 | # DEBUGGING 338 | 339 | You can set the `DBI_TRACE` environment variable to get some advanced 340 | diagnostics information printed to `STDERR` by [DBI](https://metacpan.org/pod/DBI). 341 | 342 | DBI_TRACE=1 343 | DBI_TRACE=15 344 | DBI_TRACE=15=dbitrace.log 345 | DBI_TRACE=SQL 346 | DBI_PROFILE=2 347 | 348 | See also [https://metacpan.org/pod/DBI#DBI\_TRACE](https://metacpan.org/pod/DBI#DBI_TRACE) and 349 | [https://metacpan.org/pod/DBI#DBI\_PROFILE](https://metacpan.org/pod/DBI#DBI_PROFILE). 350 | 351 | # REFERENCE 352 | 353 | This is the class hierarchy of the [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) distribution. 354 | 355 | - [Mojo::mysql](https://metacpan.org/pod/Mojo%3A%3Amysql) 356 | - [Mojo::mysql::Database](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ADatabase) 357 | - [Mojo::mysql::Migrations](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3AMigrations) 358 | - [Mojo::mysql::PubSub](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3APubSub) 359 | - [Mojo::mysql::Results](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3AResults) 360 | - [Mojo::mysql::Transaction](https://metacpan.org/pod/Mojo%3A%3Amysql%3A%3ATransaction) 361 | 362 | # AUTHORS 363 | 364 | This project is highly inspired by Sebastian Riedel's [Mojo::Pg](https://metacpan.org/pod/Mojo%3A%3APg). 365 | 366 | ## Project Founder 367 | 368 | Jan Henning Thorsen - `jhthorsen@cpan.org` 369 | 370 | ## Contributors 371 | 372 | - Adam Hopkins 373 | - Alexander Karelas 374 | - Curt Hochwender 375 | - Dan Book 376 | - Doug Bell 377 | - Florian Heyer 378 | - Hernan Lopes 379 | - Karl Rune Nilsen 380 | - Larry Leszczynski 381 | - Lucas Tiago de Moraes 382 | - Matt S Trout 383 | - Mike Magowan 384 | - Mohammad S Anwar 385 | - Rolf Stöckli 386 | - Sebastian Riedel 387 | - Svetoslav Naydenov 388 | - Svetoslav Naydenov 389 | - Tekki 390 | 391 | # COPYRIGHT AND LICENSE 392 | 393 | Copyright (C) 2014-2019, Jan Henning Thorsen. 394 | 395 | This program is free software, you can redistribute it and/or modify it under 396 | the terms of the Artistic License version 2.0. 397 | 398 | # SEE ALSO 399 | 400 | [https://github.com/jhthorsen/mojo-mysql](https://github.com/jhthorsen/mojo-mysql), 401 | 402 | [Mojo::Pg](https://metacpan.org/pod/Mojo%3A%3APg) Async Connector for PostgreSQL using [DBD::Pg](https://metacpan.org/pod/DBD%3A%3APg), [https://github.com/kraih/mojo-pg](https://github.com/kraih/mojo-pg), 403 | 404 | [Mojo::MySQL5](https://metacpan.org/pod/Mojo%3A%3AMySQL5) Pure-Perl non-blocking I/O MySQL Connector, [https://github.com/harry-bix/mojo-mysql5](https://github.com/harry-bix/mojo-mysql5), 405 | 406 | [Mojolicious::Guides](https://metacpan.org/pod/Mojolicious%3A%3AGuides), [http://mojolicio.us](http://mojolicio.us). 407 | -------------------------------------------------------------------------------- /examples/blog/blog.conf: -------------------------------------------------------------------------------- 1 | {mysql => 'mysql://mysql@/test', secrets => ['s3cret']}; 2 | -------------------------------------------------------------------------------- /examples/blog/lib/Blog.pm: -------------------------------------------------------------------------------- 1 | package Blog; 2 | use Mojo::Base 'Mojolicious'; 3 | 4 | use Blog::Model::Posts; 5 | use Mojo::mysql; 6 | 7 | sub startup { 8 | my $self = shift; 9 | 10 | # Configuration 11 | $self->plugin('Config'); 12 | $self->secrets($self->config('secrets')); 13 | 14 | # Model 15 | $self->helper(mysql => sub { state $mysql = Mojo::mysql->new(shift->config('mysql')) }); 16 | $self->helper( 17 | posts => sub { state $posts = Blog::Model::Posts->new(mysql => shift->mysql) }); 18 | 19 | # Migrate to latest version if necessary 20 | my $path = $self->home->rel_file('migrations/blog.sql'); 21 | $self->mysql->migrations->name('blog')->from_file($path)->migrate; 22 | 23 | # Controller 24 | my $r = $self->routes; 25 | $r->get('/' => sub { shift->redirect_to('posts') }); 26 | $r->get('/posts')->to('posts#index'); 27 | $r->get('/posts/create')->to('posts#create')->name('create_post'); 28 | $r->post('/posts')->to('posts#store')->name('store_post'); 29 | $r->get('/posts/:id')->to('posts#show')->name('show_post'); 30 | $r->get('/posts/:id/edit')->to('posts#edit')->name('edit_post'); 31 | $r->put('/posts/:id')->to('posts#update')->name('update_post'); 32 | $r->delete('/posts/:id')->to('posts#remove')->name('remove_post'); 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /examples/blog/lib/Blog/Controller/Posts.pm: -------------------------------------------------------------------------------- 1 | package Blog::Controller::Posts; 2 | use Mojo::Base 'Mojolicious::Controller'; 3 | 4 | sub create { shift->stash(post => {}) } 5 | 6 | sub edit { 7 | my $self = shift; 8 | $self->stash(post => $self->posts->find($self->param('id'))); 9 | } 10 | 11 | sub index { 12 | my $self = shift; 13 | $self->stash(posts => $self->posts->all); 14 | } 15 | 16 | sub remove { 17 | my $self = shift; 18 | $self->posts->remove($self->param('id')); 19 | $self->redirect_to('posts'); 20 | } 21 | 22 | sub show { 23 | my $self = shift; 24 | $self->stash(post => $self->posts->find($self->param('id'))); 25 | } 26 | 27 | sub store { 28 | my $self = shift; 29 | 30 | my $validation = $self->_validation; 31 | return $self->render(action => 'create', post => {}) 32 | if $validation->has_error; 33 | 34 | my $id = $self->posts->add($validation->output); 35 | $self->redirect_to('show_post', id => $id); 36 | } 37 | 38 | sub update { 39 | my $self = shift; 40 | 41 | my $validation = $self->_validation; 42 | return $self->render(action => 'edit', post => {}) if $validation->has_error; 43 | 44 | my $id = $self->param('id'); 45 | $self->posts->save($id, $validation->output); 46 | $self->redirect_to('show_post', id => $id); 47 | } 48 | 49 | sub _validation { 50 | my $self = shift; 51 | 52 | my $validation = $self->validation; 53 | $validation->required('title'); 54 | $validation->required('body'); 55 | 56 | return $validation; 57 | } 58 | 59 | 1; 60 | -------------------------------------------------------------------------------- /examples/blog/lib/Blog/Model/Posts.pm: -------------------------------------------------------------------------------- 1 | package Blog::Model::Posts; 2 | use Mojo::Base -base; 3 | 4 | has 'mysql'; 5 | 6 | sub add { 7 | my ($self, $post) = @_; 8 | my $sql = 'insert into posts (title, body) values (?, ?)'; 9 | return $self->mysql->db->query($sql, $post->{title}, $post->{body})->last_insert_id; 10 | } 11 | 12 | sub all { shift->mysql->db->query('select * from posts')->hashes->to_array } 13 | 14 | sub find { 15 | my ($self, $id) = @_; 16 | return $self->mysql->db->query('select * from posts where id = ?', $id)->hash; 17 | } 18 | 19 | sub remove { shift->mysql->db->query('delete from posts where id = ?', shift) } 20 | 21 | sub save { 22 | my ($self, $id, $post) = @_; 23 | my $sql = 'update posts set title = ?, body = ? where id = ?'; 24 | $self->mysql->db->query($sql, $post->{title}, $post->{body}, $id); 25 | } 26 | 27 | 1; 28 | -------------------------------------------------------------------------------- /examples/blog/migrations/blog.sql: -------------------------------------------------------------------------------- 1 | -- 1 up 2 | create table if not exists posts ( 3 | id integer auto_increment primary key, 4 | title text, 5 | body text 6 | ); 7 | 8 | -- 1 down 9 | drop table if exists posts; 10 | -------------------------------------------------------------------------------- /examples/blog/script/blog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use lib 'lib'; 7 | 8 | # Start command line interface for application 9 | require Mojolicious::Commands; 10 | Mojolicious::Commands->start_app('Blog'); 11 | -------------------------------------------------------------------------------- /examples/blog/templates/layouts/blog.html.ep: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 14 | 15 | 16 |

<%= link_to 'Blog' => 'posts' %>

17 | %= content 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/blog/templates/posts/_form.html.ep: -------------------------------------------------------------------------------- 1 | %= form_for $target => begin 2 | %= label_for title => 'Title' 3 |
4 | %= text_field title => $post->{title} 5 |
6 | %= label_for body => 'Body' 7 |
8 | %= text_area body => $post->{body} 9 |
10 | %= submit_button $caption 11 | % end 12 | -------------------------------------------------------------------------------- /examples/blog/templates/posts/create.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'blog', title => 'New post'; 2 |

New post

3 | %= include 'posts/_form', caption => 'Create', target => 'store_post' 4 | -------------------------------------------------------------------------------- /examples/blog/templates/posts/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'blog', title => 'Edit post'; 2 |

Edit post

3 | %= include 'posts/_form', caption => 'Update', target => 'update_post' 4 | %= form_for remove_post => {id => $post->{id}} => begin 5 | %= submit_button 'Remove' 6 | % end 7 | -------------------------------------------------------------------------------- /examples/blog/templates/posts/index.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'blog', title => 'Blog'; 2 | % for my $post (@$posts) { 3 |

4 |

<%= link_to $post->{title} => show_post => {id => $post->{id}} %>

5 | %= $post->{body} 6 |

7 | % } 8 | %= link_to 'New post' => 'create_post' 9 | -------------------------------------------------------------------------------- /examples/blog/templates/posts/show.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'blog', title => $post->{title}; 2 |

<%= $post->{title} %>

3 |

<%= $post->{body} %>

4 | %= link_to 'Edit' => edit_post => {id => $post->{id}} 5 | -------------------------------------------------------------------------------- /examples/chat.pl: -------------------------------------------------------------------------------- 1 | use Mojolicious::Lite; 2 | use Mojo::mysql; 3 | 4 | helper mysql => sub { state $mysql = Mojo::mysql->new('mysql://mysql@/test') }; 5 | 6 | get '/' => 'chat'; 7 | 8 | websocket '/channel' => sub { 9 | my $c = shift; 10 | 11 | $c->inactivity_timeout(3600); 12 | 13 | # Forward messages from the browser to MySQL 14 | $c->on(message => sub { shift->mysql->pubsub->notify(mojochat => shift) }); 15 | 16 | # Forward messages from MySQL to the browser 17 | my $cb = $c->mysql->pubsub->listen(mojochat => sub { $c->send(pop) }); 18 | $c->on(finish => sub { shift->mysql->pubsub->unlisten(mojochat => $cb) }); 19 | }; 20 | 21 | app->start; 22 | __DATA__ 23 | 24 | @@ chat.html.ep 25 |
26 |
27 | 34 | -------------------------------------------------------------------------------- /lib/Mojo/mysql.pm: -------------------------------------------------------------------------------- 1 | package Mojo::mysql; 2 | use Mojo::Base 'Mojo::EventEmitter'; 3 | 4 | use Carp 'croak'; 5 | use DBI; 6 | use File::Spec::Functions 'file_name_is_absolute'; 7 | use Mojo::mysql::Database; 8 | use Mojo::mysql::Migrations; 9 | use Mojo::URL; 10 | use Scalar::Util 'weaken'; 11 | use SQL::Abstract::mysql; 12 | 13 | our $VERSION = '1.27'; 14 | 15 | has abstract => sub { SQL::Abstract::mysql->new(quote_char => chr(96), name_sep => '.') }; 16 | has auto_migrate => 0; 17 | has database_class => 'Mojo::mysql::Database'; 18 | has dsn => 'dbi:mysql:dbname=test'; 19 | has max_connections => 5; 20 | 21 | has migrations => sub { 22 | my $migrations = Mojo::mysql::Migrations->new(mysql => shift); 23 | weaken $migrations->{mysql}; 24 | return $migrations; 25 | }; 26 | 27 | has options => sub { 28 | my $self = shift; 29 | my $options = {AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1}; 30 | $options->{mysql_enable_utf8} = 1 if $self->dsn =~ m!^dbi:mysql:!; 31 | return $options; 32 | }; 33 | 34 | has [qw(password username)] => ''; 35 | has pubsub => sub { 36 | require Mojo::mysql::PubSub; 37 | my $pubsub = Mojo::mysql::PubSub->new(mysql => shift); 38 | warn "Use of Mojo::mysql::PubSub is highly EXPERIMENTAL and should be considered an experiment" 39 | unless $ENV{MOJO_PUBSUB_EXPERIMENTAL}; 40 | weaken $pubsub->{mysql}; 41 | return $pubsub; 42 | }; 43 | 44 | sub close_idle_connections { 45 | my ($self, $keep) = (shift, $_[0] || 0); 46 | my $queue = $self->{queue} || []; 47 | 48 | # The database handle needs to be destroyed before the file handle 49 | shift(@$queue)->[0] = undef while @$queue > $keep; 50 | return $self; 51 | } 52 | 53 | sub db { 54 | my $self = shift; 55 | 56 | # Fork safety 57 | delete @$self{qw(pid queue)} unless ($self->{pid} //= $$) eq $$; 58 | 59 | my ($dbh, $handle) = @{$self->_dequeue}; 60 | return $self->database_class->new(dbh => $dbh, handle => $handle, mysql => $self); 61 | } 62 | 63 | sub from_string { 64 | my ($self, $str) = @_; 65 | 66 | # Protocol 67 | return $self unless $str; 68 | my $url = UNIVERSAL::isa($str, 'Mojo::URL') ? $str : Mojo::URL->new($str); 69 | croak qq{Invalid MySQL/MariaDB connection string "$str"} unless $url->protocol =~ m!^(mariadb|mysql)$!; 70 | my $dsn = $url->protocol eq 'mariadb' ? 'dbi:MariaDB' : 'dbi:mysql'; 71 | 72 | # https://github.com/jhthorsen/mojo-mysql/pull/47 73 | die "DBD::MariaDB 1.21 is required for Mojo::mysql to work properly" 74 | if $dsn eq 'dbi:MariaDB' and !eval 'use DBD::MariaDB 1.21;1'; 75 | 76 | # Database 77 | $dsn .= ':dbname=' . $url->path->parts->[0] if defined $url->path->parts->[0]; 78 | 79 | # Host and port 80 | if (my $host = $url->host) { $dsn .= file_name_is_absolute($host) ? ";mysql_socket=$host" : ";host=$host" } 81 | if (my $port = $url->port) { $dsn .= ";port=$port" } 82 | 83 | # Need to set the dsn before reading options 84 | $self->dsn($dsn); 85 | 86 | # Username and password 87 | if (($url->userinfo // '') =~ /^([^:]+)(?::([^:]+))?$/) { 88 | $self->username($1); 89 | $self->password($2) if defined $2; 90 | } 91 | 92 | # Options 93 | my $hash = $url->query->to_hash; 94 | @{$self->options}{keys %$hash} = values %$hash; 95 | 96 | return $self; 97 | } 98 | 99 | sub new { 100 | @_ > 2 || ref $_[-1] eq 'HASH' ? shift->SUPER::new(@_) : shift->SUPER::new->from_string(@_); 101 | } 102 | 103 | sub strict_mode { 104 | my $self = ref $_[0] ? shift : shift->new(@_); 105 | $self->{strict_mode} = $_[0] ? 1 : @_ ? 0 : 1; 106 | warn "[Mojo::mysql] strict_mode($self->{strict_mode})\n" if $ENV{DBI_TRACE}; 107 | $self->close_idle_connections; 108 | return $self; 109 | } 110 | 111 | sub _dequeue { 112 | my $self = shift; 113 | my $dbh; 114 | 115 | while (my $c = shift @{$self->{queue}}) { return $c if $c->[0]->ping } 116 | $dbh = DBI->connect(map { $self->$_ } qw(dsn username password options)); 117 | 118 | # batman's probably going to have more "fun" than you have ... 119 | # especially once he discovers that DBD::mysql randomly reconnects under 120 | # you, silently, but only if certain env vars are set 121 | # hint: force-set mysql_auto_reconnect or whatever it's called to 0 122 | Mojo::mysql::Database->_dbh_attr($dbh, mysql_auto_reconnect => 0); 123 | 124 | # Maintain Commits with Mojo::mysql::Transaction 125 | $dbh->{AutoCommit} = 1; 126 | 127 | $self->_set_strict_mode($dbh) if $self->{strict_mode}; 128 | $self->migrations->migrate if $self->auto_migrate and !$self->{migrated}++; 129 | $self->emit(connection => $dbh); 130 | [$dbh]; 131 | } 132 | 133 | sub _enqueue { 134 | my ($self, $dbh, $handle) = @_; 135 | push @{$self->{queue}}, [$dbh, $handle] if $dbh->{Active}; 136 | $self->close_idle_connections($self->max_connections); 137 | } 138 | 139 | sub _set_strict_mode { 140 | $_[1]->do(q[SET SQL_MODE = CONCAT('ANSI,TRADITIONAL,ONLY_FULL_GROUP_BY,', @@sql_mode)]); 141 | $_[1]->do(q[SET SQL_AUTO_IS_NULL = 0]); 142 | } 143 | 144 | 1; 145 | 146 | =encoding utf8 147 | 148 | =head1 NAME 149 | 150 | Mojo::mysql - Mojolicious and Async MySQL/MariaDB 151 | 152 | =head1 SYNOPSIS 153 | 154 | use Mojo::mysql; 155 | 156 | # Connect to a local database 157 | my $mysql = Mojo::mysql->strict_mode('mysql://username@/test'); 158 | 159 | # Connect to a remote database 160 | my $mysql = Mojo::mysql->strict_mode('mysql://username:password@hostname/test'); 161 | # MySQL >= 8.0: 162 | my $mysql = Mojo::mysql->strict_mode('mysql://username:password@hostname/test;mysql_ssl=1'); 163 | 164 | # Use DBD::MariaDB instead of DBD::mysql 165 | my $mysql = Mojo::mysql->strict_mode('mariadb://username@/test'); 166 | 167 | # Create a table 168 | $mysql->db->query( 169 | 'create table names (id integer auto_increment primary key, name text)'); 170 | 171 | # Insert a few rows 172 | my $db = $mysql->db; 173 | $db->query('insert into names (name) values (?)', 'Sara'); 174 | $db->query('insert into names (name) values (?)', 'Stefan'); 175 | 176 | # Insert more rows in a transaction 177 | eval { 178 | my $tx = $db->begin; 179 | $db->query('insert into names (name) values (?)', 'Baerbel'); 180 | $db->query('insert into names (name) values (?)', 'Wolfgang'); 181 | $tx->commit; 182 | }; 183 | say $@ if $@; 184 | 185 | # Insert another row and return the generated id 186 | say $db->query('insert into names (name) values (?)', 'Daniel') 187 | ->last_insert_id; 188 | 189 | # Use SQL::Abstract::mysql to generate queries for you 190 | $db->insert('names', {name => 'Isabel'}); 191 | say $db->select('names', undef, {name => 'Isabel'})->hash->{id}; 192 | $db->update('names', {name => 'Bel'}, {name => 'Isabel'}); 193 | $db->delete('names', {name => 'Bel'}); 194 | 195 | # Select one row at a time 196 | my $results = $db->query('select * from names'); 197 | while (my $next = $results->hash) { 198 | say $next->{name}; 199 | } 200 | 201 | # Select all rows blocking 202 | $db->query('select * from names') 203 | ->hashes->map(sub { $_->{name} })->join("\n")->say; 204 | 205 | # Select all rows non-blocking 206 | $db->query('select * from names' => sub { 207 | my ($db, $err, $results) = @_; 208 | $results->hashes->map(sub { $_->{name} })->join("\n")->say; 209 | }); 210 | 211 | # Concurrent non-blocking queries (synchronized with promises) 212 | my $now = $db->query_p('select now() as now'); 213 | my $names = $db->query_p('select * from names'); 214 | Mojo::Promise->all($now, $names)->then(sub { 215 | my ($now, $names) = @_; 216 | say $now->[0]->hash->{now}; 217 | say $_->{name} for $names->[0]->hashes->each; 218 | })->catch(sub { 219 | my $err = shift; 220 | warn "Something went wrong: $err"; 221 | })->wait; 222 | 223 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running; 224 | 225 | =head1 DESCRIPTION 226 | 227 | L is a tiny wrapper around L and L that 228 | makes L and L a lot 229 | of fun to use with the L real-time web 230 | framework. 231 | 232 | The two DBD drivers are compatible with both MySQL and MariaDB, but they offer 233 | different L. L should have better unicode support 234 | though and might become the default in the future. 235 | 236 | Database and handles are cached automatically, so they can be reused 237 | transparently to increase performance. And you can handle connection timeouts 238 | gracefully by holding on to them only for short amounts of time. 239 | 240 | use Mojolicious::Lite; 241 | use Mojo::mysql; 242 | 243 | helper mysql => 244 | sub { state $mysql = Mojo::mysql->strict_mode('mysql://sri:s3cret@localhost/db') }; 245 | 246 | get '/' => sub { 247 | my $c = shift; 248 | my $db = $c->mysql->db; 249 | $c->render(json => $db->query('select now() as time')->hash); 250 | }; 251 | 252 | app->start; 253 | 254 | While all I/O operations are performed blocking, you can wait for long running 255 | queries asynchronously, allowing the L event loop to perform 256 | other tasks in the meantime. Since database connections usually have a very low 257 | latency, this often results in very good performance. 258 | 259 | Every database connection can only handle one active query at a time, this 260 | includes asynchronous ones. So if you start more than one, they will be put on 261 | a waiting list and performed sequentially. To perform multiple queries 262 | concurrently, you have to use multiple connections. 263 | 264 | # Performed sequentially (10 seconds) 265 | my $db = $mysql->db; 266 | $db->query('select sleep(5)' => sub {...}); 267 | $db->query('select sleep(5)' => sub {...}); 268 | 269 | # Performed concurrently (5 seconds) 270 | $mysql->db->query('select sleep(5)' => sub {...}); 271 | $mysql->db->query('select sleep(5)' => sub {...}); 272 | 273 | All cached database handles will be reset automatically if a new process has 274 | been forked, this allows multiple processes to share the same L 275 | object safely. 276 | 277 | =head1 EVENTS 278 | 279 | L inherits all events from L and can emit the 280 | following new ones. 281 | 282 | =head2 connection 283 | 284 | $mysql->on(connection => sub { 285 | my ($mysql, $dbh) = @_; 286 | ... 287 | }); 288 | 289 | Emitted when a new database connection has been established. 290 | 291 | =head1 ATTRIBUTES 292 | 293 | L implements the following attributes. 294 | 295 | =head2 abstract 296 | 297 | $abstract = $mysql->abstract; 298 | $mysql = $mysql->abstract(SQL::Abstract::mysql->new); 299 | 300 | L object used to generate CRUD queries for L. 301 | 302 | # Generate statements and bind values 303 | my ($stmt, @bind) = $mysql->abstract->select('names'); 304 | 305 | =head2 auto_migrate 306 | 307 | my $bool = $mysql->auto_migrate; 308 | $mysql = $mysql->auto_migrate($bool); 309 | 310 | Automatically migrate to the latest database schema with L, as 311 | soon as the first database connection has been established. 312 | 313 | Defaults to false. 314 | 315 | =head2 database_class 316 | 317 | $class = $mysql->database_class; 318 | $mysql = $mysql->database_class("MyApp::Database"); 319 | 320 | Class to be used by L, defaults to L. Note that this 321 | class needs to have already been loaded before L is called. 322 | 323 | =head2 dsn 324 | 325 | my $dsn = $mysql->dsn; 326 | $mysql = $mysql->dsn('dbi:mysql:dbname=foo'); 327 | 328 | Data Source Name, defaults to C. 329 | 330 | =head2 max_connections 331 | 332 | my $max = $mysql->max_connections; 333 | $mysql = $mysql->max_connections(3); 334 | 335 | Maximum number of idle database handles to cache for future use, defaults to 336 | C<5>. 337 | 338 | =head2 migrations 339 | 340 | my $migrations = $mysql->migrations; 341 | $mysql = $mysql->migrations(Mojo::mysql::Migrations->new); 342 | 343 | L object you can use to change your database schema more 344 | easily. 345 | 346 | # Load migrations from file and migrate to latest version 347 | $mysql->migrations->from_file('/Users/sri/migrations.sql')->migrate; 348 | 349 | MySQL and MariaDB does not support nested transactions and DDL transactions. 350 | DDL statements cause implicit C. C will be called if any step 351 | of migration script fails, but only DML statements after the last implicit or 352 | explicit C can be reverted. Not all storage engines (like C) 353 | support transactions. 354 | 355 | This means database will most likely be left in unknown state if migration script fails. 356 | Use this feature with caution and remember to always backup your database. 357 | 358 | =head2 options 359 | 360 | my $options = $mysql->options; 361 | $mysql = $mysql->options({mysql_use_result => 1}); 362 | 363 | Options for database handles, defaults to activating C (only 364 | for L), C, C as well as 365 | C and deactivating C. C and C 366 | are considered mandatory, so deactivating them would be very dangerous. 367 | 368 | C is never enabled, L takes care of dead connections. 369 | 370 | C cannot not be disabled, use $db->L to manage transactions. 371 | 372 | C is enabled for blocking and disabled in event loop for non-blocking queries. 373 | 374 | About C: 375 | 376 | The mysql_enable_utf8 sets the utf8 charset which only supports up to 3-byte 377 | UTF-8 encodings. mysql_enable_utf8mb4 (as of DBD::mysql 4.032) properly 378 | supports encoding unicode characters to up to 4 bytes, such as 𠜎. It means the 379 | connection charset will be utf8mb4 (supported back to at least mysql 5.5) and 380 | these unicode characters will be supported, but no other changes. 381 | 382 | See also L 383 | 384 | =head2 password 385 | 386 | my $password = $mysql->password; 387 | $mysql = $mysql->password('s3cret'); 388 | 389 | Database password, defaults to an empty string. 390 | 391 | =head2 pubsub 392 | 393 | my $pubsub = $mysql->pubsub; 394 | $mysql = $mysql->pubsub(Mojo::mysql::PubSub->new); 395 | 396 | L should be considered an EXPERIMENT! See 397 | L for more information. 398 | 399 | =head2 username 400 | 401 | my $username = $mysql->username; 402 | $mysql = $mysql->username('batman'); 403 | 404 | Database username, defaults to an empty string. 405 | 406 | =head1 METHODS 407 | 408 | L inherits all methods from L and implements the 409 | following new ones. 410 | 411 | =head2 close_idle_connections 412 | 413 | $mysql = $mysql->close_idle_connections($keep); 414 | 415 | Close all connections that are not currently active, or limit the 416 | number of idle connections to C<$keep>. 417 | 418 | =head2 db 419 | 420 | my $db = $mysql->db; 421 | 422 | Get L object for a cached or newly created database 423 | handle. The database handle will be automatically cached again when that 424 | object is destroyed, so you can handle connection timeouts gracefully by 425 | holding on to it only for short amounts of time. 426 | 427 | =head2 from_string 428 | 429 | $mysql = $mysql->from_string('mysql://user@/test'); 430 | 431 | Parse configuration from connection string. 432 | 433 | # Just a database 434 | $mysql->from_string('mysql:///db1'); 435 | 436 | # Username and database 437 | $mysql->from_string('mysql://batman@/db2'); 438 | 439 | # Username, password, host and database 440 | $mysql->from_string('mysql://batman:s3cret@localhost/db3'); 441 | 442 | # Username, domain socket and database 443 | $mysql->from_string('mysql://batman@%2ftmp%2fmysql.sock/db4'); 444 | 445 | # Username, database and additional options 446 | $mysql->from_string('mysql://batman@/db5?PrintError=1&RaiseError=0'); 447 | 448 | =head2 new 449 | 450 | my $mysql = Mojo::mysql->new; 451 | my $mysql = Mojo::mysql->new(%attrs); 452 | my $mysql = Mojo::mysql->new(\%attrs); 453 | my $mysql = Mojo::mysql->new('mysql://user@/test'); 454 | my $mysql = Mojo::mysql->new('mariadb://user@/test'); 455 | 456 | Construct a new L object either from L and or parse 457 | connection string with L if necessary. 458 | 459 | Using the "mariadb" scheme requires the optional module L version 460 | 1.21 (or later) to be installed. 461 | 462 | =head2 strict_mode 463 | 464 | my $mysql = Mojo::mysql->strict_mode('mysql://user@/test'); 465 | my $mysql = $mysql->strict_mode($boolean); 466 | 467 | This method can act as both a constructor and a method. When called as a 468 | constructor, it will be the same as: 469 | 470 | my $mysql = Mojo::mysql->new('mysql://user@/test')->strict_mode(1); 471 | 472 | Enabling strict mode will execute the following statement when a new connection 473 | is created: 474 | 475 | SET SQL_MODE = CONCAT('ANSI,TRADITIONAL,ONLY_FULL_GROUP_BY,', @@sql_mode) 476 | SET SQL_AUTO_IS_NULL = 0 477 | 478 | The idea is to set up a connection that makes it harder for MySQL to allow 479 | "invalid" data to be inserted. 480 | 481 | This method will not be removed, but the internal commands is subject to 482 | change. 483 | 484 | =head1 DEBUGGING 485 | 486 | You can set the C environment variable to get some advanced 487 | diagnostics information printed to C by L. 488 | 489 | DBI_TRACE=1 490 | DBI_TRACE=15 491 | DBI_TRACE=15=dbitrace.log 492 | DBI_TRACE=SQL 493 | DBI_PROFILE=2 494 | 495 | See also L and 496 | L. 497 | 498 | =head1 REFERENCE 499 | 500 | This is the class hierarchy of the L distribution. 501 | 502 | =over 2 503 | 504 | =item * L 505 | 506 | =item * L 507 | 508 | =item * L 509 | 510 | =item * L 511 | 512 | =item * L 513 | 514 | =item * L 515 | 516 | =back 517 | 518 | =head1 AUTHORS 519 | 520 | This project is highly inspired by Sebastian Riedel's L. 521 | 522 | =head2 Project Founder 523 | 524 | Jan Henning Thorsen - C 525 | 526 | =head2 Contributors 527 | 528 | =over 2 529 | 530 | 531 | =item * Adam Hopkins 532 | 533 | =item * Alexander Karelas 534 | 535 | =item * Curt Hochwender 536 | 537 | =item * Dan Book 538 | 539 | =item * Doug Bell 540 | 541 | =item * Florian Heyer 542 | 543 | =item * Hernan Lopes 544 | 545 | =item * Karl Rune Nilsen 546 | 547 | =item * Larry Leszczynski 548 | 549 | =item * Lucas Tiago de Moraes 550 | 551 | =item * Matt S Trout 552 | 553 | =item * Mike Magowan 554 | 555 | =item * Mohammad S Anwar 556 | 557 | =item * Rolf Stöckli 558 | 559 | =item * Sebastian Riedel 560 | 561 | =item * Svetoslav Naydenov 562 | 563 | =item * Svetoslav Naydenov 564 | 565 | =item * Tekki 566 | 567 | =back 568 | 569 | =head1 COPYRIGHT AND LICENSE 570 | 571 | Copyright (C) 2014-2019, Jan Henning Thorsen. 572 | 573 | This program is free software, you can redistribute it and/or modify it under 574 | the terms of the Artistic License version 2.0. 575 | 576 | =head1 SEE ALSO 577 | 578 | L, 579 | 580 | L Async Connector for PostgreSQL using L, L, 581 | 582 | L Pure-Perl non-blocking I/O MySQL Connector, L, 583 | 584 | L, L. 585 | 586 | =cut 587 | -------------------------------------------------------------------------------- /lib/Mojo/mysql/Database.pm: -------------------------------------------------------------------------------- 1 | package Mojo::mysql::Database; 2 | use Mojo::Base 'Mojo::EventEmitter'; 3 | 4 | use Carp; 5 | use Mojo::IOLoop; 6 | use Mojo::JSON 'to_json'; 7 | use Mojo::mysql::Results; 8 | use Mojo::mysql::Transaction; 9 | use Mojo::Promise; 10 | use Mojo::Util 'monkey_patch'; 11 | use Scalar::Util 'weaken'; 12 | 13 | has [qw(dbh mysql)]; 14 | has results_class => 'Mojo::mysql::Results'; 15 | 16 | for my $name (qw(delete insert select update)) { 17 | monkey_patch __PACKAGE__, $name, sub { 18 | my $self = shift; 19 | my @cb = ref $_[-1] eq 'CODE' ? pop : (); 20 | return $self->query($self->mysql->abstract->$name(@_), @cb); 21 | }; 22 | monkey_patch __PACKAGE__, "${name}_p", sub { 23 | my $self = shift; 24 | return $self->query_p($self->mysql->abstract->$name(@_)); 25 | }; 26 | } 27 | 28 | sub DESTROY { 29 | my $self = shift; 30 | return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; 31 | $self->_cleanup_sth; 32 | return unless (my $mysql = $self->mysql) and (my $dbh = $self->dbh); 33 | $mysql->_enqueue($dbh, $self->{handle}); 34 | } 35 | 36 | sub backlog { scalar @{shift->{waiting} || []} } 37 | 38 | sub begin { 39 | my $self = shift; 40 | my $tx = Mojo::mysql::Transaction->new(db => $self); 41 | weaken $tx->{db}; 42 | return $tx; 43 | } 44 | 45 | sub disconnect { 46 | my $self = shift; 47 | $self->_cleanup_sth; 48 | $self->_unwatch; 49 | $self->dbh->disconnect; 50 | } 51 | 52 | sub pid { shift->_dbh_attr('mysql_thread_id') } 53 | 54 | sub ping { shift->dbh->ping } 55 | 56 | sub query { 57 | my ($self, $query) = (shift, shift); 58 | my $cb = ref $_[-1] eq 'CODE' ? pop : undef; 59 | 60 | # Blocking 61 | unless ($cb) { 62 | Carp::confess('Cannot perform blocking query, while waiting for async response') if $self->backlog; 63 | my $sth = $self->dbh->prepare($query); 64 | local $sth->{HandleError} = sub { $_[0] = Carp::shortmess($_[0]); 0 }; 65 | _bind_params($sth, @_); 66 | my $rv = $sth->execute; 67 | my $res = $self->results_class->new(db => $self, is_blocking => 1, sth => $sth); 68 | $res->{affected_rows} = defined $rv && $rv >= 0 ? 0 + $rv : undef; 69 | return $res; 70 | } 71 | 72 | # Non-blocking 73 | push @{$self->{waiting}}, {args => [@_], err => Carp::shortmess('__MSG__'), cb => $cb, query => $query}; 74 | $self->$_ for qw(_next _watch); 75 | return $self; 76 | } 77 | 78 | sub query_p { 79 | my $self = shift; 80 | my $promise = Mojo::Promise->new; 81 | $self->query(@_ => sub { $_[1] ? $promise->reject($_[1]) : $promise->resolve($_[2]) }); 82 | return $promise; 83 | } 84 | 85 | sub quote { shift->dbh->quote(shift) } 86 | 87 | sub quote_id { shift->dbh->quote_identifier(shift) } 88 | 89 | sub tables { 90 | shift->query('show tables')->arrays->reduce(sub { push @$a, $b->[0]; $a }, []); 91 | } 92 | 93 | sub _bind_params { 94 | my $sth = shift; 95 | for my $i (0 .. $#_) { 96 | my $param = $_[$i]; 97 | my %attrs; 98 | if (ref $param eq 'HASH') { 99 | if (exists $param->{json}) { 100 | $param = to_json $param->{json}; 101 | } 102 | elsif (exists $param->{type} && exists $param->{value}) { 103 | ($param, $attrs{TYPE}) = @$param{qw(value type)}; 104 | } 105 | } 106 | 107 | $sth->bind_param($i + 1, $param, \%attrs); 108 | } 109 | return $sth; 110 | } 111 | 112 | sub _cleanup_sth { 113 | my $self = shift; 114 | delete $self->{done_sth}; 115 | $_->{cb}($self, 'Premature connection close', undef) for @{delete $self->{waiting} || []}; 116 | } 117 | 118 | sub _dbh_attr { 119 | my $self = shift; 120 | my $dbh = ref $self ? $self->dbh : shift; 121 | my $name = shift; 122 | $name =~ s!^mysql!{lc $dbh->{Driver}{Name}}!e; 123 | return $dbh->{$name} = shift if @_; 124 | return $dbh->{$name}; 125 | } 126 | 127 | sub _next { 128 | my $self = shift; 129 | 130 | return unless my $next = $self->{waiting}[0]; 131 | return if $next->{sth}; 132 | 133 | my $dbh = $self->dbh; 134 | my $flag = lc $dbh->{Driver}{Name} eq 'mariadb' ? 'mariadb_async' : 'async'; 135 | my $sth = $next->{sth} = $self->dbh->prepare($next->{query}, {$flag => 1}); 136 | _bind_params($sth, @{$next->{args}}); 137 | $sth->execute; 138 | } 139 | 140 | sub _unwatch { 141 | Mojo::IOLoop->singleton->reactor->remove(delete $_[0]->{handle}) if $_[0]->{handle}; 142 | } 143 | 144 | sub _watch { 145 | my $self = shift; 146 | return if $self->{handle}; 147 | 148 | my $dbh = $self->dbh; 149 | my $driver = lc $dbh->{Driver}{Name}; 150 | my $ready_method = "${driver}_async_ready"; 151 | my $result_method = "${driver}_async_result"; 152 | my $fd = $driver eq 'mariadb' ? $dbh->mariadb_sockfd : $dbh->mysql_fd; 153 | open $self->{handle}, '<&', $fd or die "Could not dup $driver fd: $!"; 154 | Mojo::IOLoop->singleton->reactor->io( 155 | $self->{handle} => sub { 156 | return unless my $waiting = $self->{waiting}; 157 | return unless @$waiting and $waiting->[0]{sth} and $waiting->[0]{sth}->$ready_method; 158 | my ($cb, $err, $sth) = @{shift @$waiting}{qw(cb err sth)}; 159 | 160 | # Do not raise exceptions inside the event loop 161 | my $rv = do { local $sth->{RaiseError} = 0; $sth->$result_method }; 162 | my $res = $self->results_class->new(db => $self, sth => $sth); 163 | 164 | $err = undef if defined $rv; 165 | $err =~ s!\b__MSG__\b!{$dbh->errstr}!e if defined $err; 166 | $res->{affected_rows} = defined $rv && $rv >= 0 ? 0 + $rv : undef; 167 | 168 | $self->$cb($err, $res); 169 | $self->_next; 170 | $self->_unwatch unless $self->backlog; 171 | } 172 | )->watch($self->{handle}, 1, 0); 173 | } 174 | 175 | 1; 176 | 177 | =encoding utf8 178 | 179 | =head1 NAME 180 | 181 | Mojo::mysql::Database - Database 182 | 183 | =head1 SYNOPSIS 184 | 185 | use Mojo::mysql::Database; 186 | 187 | my $db = Mojo::mysql::Database->new(mysql => $mysql, dbh => $dbh); 188 | 189 | =head1 DESCRIPTION 190 | 191 | L is a container for database handles used by L. 192 | 193 | =head1 ATTRIBUTES 194 | 195 | L implements the following attributes. 196 | 197 | =head2 dbh 198 | 199 | my $dbh = $db->dbh; 200 | $db = $db->dbh(DBI->new); 201 | 202 | Database handle used for all queries. 203 | 204 | =head2 mysql 205 | 206 | my $mysql = $db->mysql; 207 | $db = $db->mysql(Mojo::mysql->new); 208 | 209 | L object this database belongs to. 210 | 211 | =head2 results_class 212 | 213 | $class = $db->results_class; 214 | $db = $db->results_class("MyApp::Results"); 215 | 216 | Class to be used by L, defaults to L. Note that 217 | this class needs to have already been loaded before L is called. 218 | 219 | =head1 METHODS 220 | 221 | L inherits all methods from L and 222 | implements the following new ones. 223 | 224 | =head2 backlog 225 | 226 | my $num = $db->backlog; 227 | 228 | Number of waiting non-blocking queries. 229 | 230 | =head2 begin 231 | 232 | my $tx = $db->begin; 233 | 234 | Begin transaction and return L object, which will 235 | automatically roll back the transaction unless 236 | L has been called before it is destroyed. 237 | 238 | # Add names in a transaction 239 | eval { 240 | my $tx = $db->begin; 241 | $db->query('insert into names values (?)', 'Baerbel'); 242 | $db->query('insert into names values (?)', 'Wolfgang'); 243 | $tx->commit; 244 | }; 245 | say $@ if $@; 246 | 247 | =head2 delete 248 | 249 | my $results = $db->delete($table, \%where); 250 | 251 | Generate a C statement with L (usually an 252 | L object) and execute it with L. You can also append a 253 | callback to perform operations non-blocking. 254 | 255 | $db->delete(some_table => sub { 256 | my ($db, $err, $results) = @_; 257 | ... 258 | }); 259 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running; 260 | 261 | =head2 delete_p 262 | 263 | my $promise = $db->delete_p($table, \%where, \%options); 264 | 265 | Same as L, but performs all operations non-blocking and returns a 266 | L object instead of accepting a callback. 267 | 268 | $db->delete_p('some_table')->then(sub { 269 | my $results = shift; 270 | ... 271 | })->catch(sub { 272 | my $err = shift; 273 | ... 274 | })->wait; 275 | 276 | =head2 disconnect 277 | 278 | $db->disconnect; 279 | 280 | Disconnect database handle and prevent it from getting cached again. 281 | 282 | =head2 insert 283 | 284 | my $results = $db->insert($table, \@values || \%fieldvals, \%options); 285 | 286 | Generate an C statement with L (usually an 287 | L object) and execute it with L. You can also append a 288 | callback to perform operations non-blocking. 289 | 290 | $db->insert(some_table => {foo => 'bar'} => sub { 291 | my ($db, $err, $results) = @_; 292 | ... 293 | }); 294 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running; 295 | 296 | =head2 insert_p 297 | 298 | my $promise = $db->insert_p($table, \@values || \%fieldvals, \%options); 299 | 300 | Same as L, but performs all operations non-blocking and returns a 301 | L object instead of accepting a callback. 302 | 303 | $db->insert_p(some_table => {foo => 'bar'})->then(sub { 304 | my $results = shift; 305 | ... 306 | })->catch(sub { 307 | my $err = shift; 308 | ... 309 | })->wait; 310 | 311 | =head2 pid 312 | 313 | my $pid = $db->pid; 314 | 315 | Return the connection id of the backend server process. 316 | 317 | =head2 ping 318 | 319 | my $bool = $db->ping; 320 | 321 | Check database connection. 322 | 323 | =head2 query 324 | 325 | my $results = $db->query('select * from foo'); 326 | my $results = $db->query('insert into foo values (?, ?, ?)', @values); 327 | my $results = $db->query('insert into foo values (?)', {json => {bar => 'baz'}}); 328 | my $results = $db->query('insert into foo values (?)', {type => SQL_INTEGER, value => 42}); 329 | 330 | Execute a blocking statement and return a L object with the 331 | results. You can also append a callback to perform operation non-blocking. 332 | 333 | $db->query('select * from foo' => sub { 334 | my ($db, $err, $results) = @_; 335 | ... 336 | }); 337 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running; 338 | 339 | Hash reference arguments containing a value named C, will be encoded to 340 | JSON text with L. To accomplish the reverse, you can use 341 | the method L, which automatically decodes data back 342 | to Perl data structures. 343 | 344 | $db->query('insert into foo values (x) values (?)', {json => {bar => 'baz'}}); 345 | $db->query('select * from foo')->expand->hash->{x}{bar}; # baz 346 | 347 | Hash reference arguments containing values named C and C can be 348 | used to bind specific L data types (see L) to 349 | placeholders. This is needed to pass binary data in parameters; see 350 | L for more information. 351 | 352 | # Insert binary data 353 | use DBI ':sql_types'; 354 | $db->query('insert into bar values (?)', {type => SQL_BLOB, value => $bytes}); 355 | 356 | =head2 query_p 357 | 358 | my $promise = $db->query_p('select * from foo'); 359 | 360 | Same as L, but performs all operations non-blocking and returns a 361 | L object instead of accepting a callback. 362 | 363 | $db->query_p('insert into foo values (?, ?, ?)' => @values)->then(sub { 364 | my $results = shift; 365 | ... 366 | })->catch(sub { 367 | my $err = shift; 368 | ... 369 | })->wait; 370 | 371 | =head2 quote 372 | 373 | my $escaped = $db->quote($str); 374 | 375 | Quote a string literal for use as a literal value in an SQL statement. 376 | 377 | =head2 quote_id 378 | 379 | my $escaped = $db->quote_id($id); 380 | 381 | Quote an identifier (table name etc.) for use in an SQL statement. 382 | 383 | =head2 select 384 | 385 | my $results = $db->select($source, $fields, $where, $order); 386 | 387 | Generate a C queries with C 291 | or C clauses. So far the scalar values C and 292 | C and scalar references to pass literal SQL are supported. 293 | 294 | # "select * from some_table for update" 295 | $abstract->select('some_table', '*', undef, {for => 'update'}); 296 | 297 | # "select * from some_table lock in share mode" 298 | $abstract->select('some_table', '*', undef, {for => 'share'}); 299 | 300 | # "select * from some_table for share" 301 | $abstract->select('some_table', '*', undef, {for => \'share'}); 302 | 303 | # "select * from some_table for update skip locked" 304 | $abstract->select('some_table', '*', undef, {for => \'update skip locked'}); 305 | 306 | =head3 GROUP BY 307 | 308 | The C option can be used to generate C queries with C 321 | clauses, which takes the same values as the C<$where> argument. 322 | 323 | # "select * from t group by a having b = 'c'" 324 | $abstract->select('t', '*', undef, {group_by => ['a'], having => {b => 'c'}}); 325 | 326 | =head3 ORDER BY 327 | 328 | In addition to the C<$order> argument accepted by L you can pass 329 | a hash reference with various options. This includes C, which takes 330 | the same values as the C<$order> argument. 331 | 332 | # "select * from some_table order by foo desc" 333 | $abstract->select('some_table', '*', undef, {order_by => {-desc => 'foo'}}); 334 | 335 | =head3 LIMIT / OFFSET 336 | 337 | The C and C options can be used to generate C