Mojo-SQLite-v4.0.0000755001750001750 015205022012 13732 5ustar00grinnzgrinnz000000000000README100644001750001750 163315205022012 14676 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0Mojo::SQLite Mojo::SQLite is a tiny wrapper around DBD::SQLite that makes SQLite a lot of fun to use with the Mojolicious real-time web framework. Use all SQL features SQLite has to offer, generate CRUD queries from data structures, and manage your database schema with migrations. INSTALLATION This is a Perl module distribution. It should be installed with whichever tool you use to manage your installation of Perl, e.g. any of cpanm . cpan . cpanp -i . Consult http://www.cpan.org/modules/INSTALL.html for further instruction. Should you wish to install this module manually, the procedure is perl Build.PL ./Build ./Build test ./Build install COPYRIGHT AND LICENSE Copyright 2015, Dan Book. This library is free software; you may redistribute it and/or modify it under the terms of the Artistic License version 2.0. Changes100644001750001750 2076115205022012 15334 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0v4.0.0 2026-05-25 06:27:21 EDT - wal_mode is now off by default, but can be enabled with the wal_mode option. This will only affect newly created databases, as WAL mode is persistent once set. To disable it on an existing database, issue the query: PRAGMA journal_mode=DELETE; - Fix issue where a transaction that failed to begin would rollback and clear the database handle error. 3.009 2022-03-22 20:16:03 EDT - Fix race condition in Mojo::SQLite::Migrations where two processes could attempt to create the same migration. (#25, Oliver Kurz) 3.008 2021-11-28 11:10:24 EST - Update documentation to indicate that upsert operations are now supported via SQL::Abstract::Pg. 3.007 2021-08-01 20:17:42 EDT - Increase DBD::SQLite dependency to 1.68 to support and use sqlite_string_mode over sqlite_unicode (#22, Adam Williamson) - The default options now set sqlite_string_mode to DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, unless sqlite_unicode is specified explicitly in from_string or from_filename. This is equivalent to the previous behavior but ensures that DBD::SQLite will not return malformed strings. - Support wal_mode option to enable WAL journaling mode, which is still currently the default, but may not be set by default in a future release. 3.006 2021-06-16 00:30:47 EDT - Set default abstract generator to an SQL::Abstract::Pg object, enabling additional features. - Update IRC metadata to libera.chat 3.005 2021-02-15 21:47:59 EST - Add query_p, select_p, insert_p, update_p, delete_p Mojo::Promise-returning methods to Mojo::SQLite::Database. These are for API compatibility with Mojo::Pg and do not provide non-blocking query functionality. (#20, Stefan Adams) - Use Mojo::Promise in tests instead of the deprecated and decored Mojo::IOLoop::Delay (#20, Stefan Adams) 3.004 2020-07-22 22:50:43 EDT - The Mojo::SQLite::Migrations sqlite attribute and the Mojo::SQLite::Transaction db attribute are now weak attributes, increasing the required version of Mojolicious to 8.03. - Defer loading of Mojo::IOLoop unless the callback compatibility API is used. - Newly created mojo_migrations tables from Mojo::SQLite::Migrations will use a primary key. - Recognize -json parameters in queries. 3.003 2019-10-01 15:49:43 EDT - Increase DBD::SQLite dependency to 1.64 to support configuring double-quoted string literals. - Disable double-quoted string literals for all connections. https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted - Change default SQL::Abstract identifier back to double quote. 3.002 2019-06-21 17:41:35 EDT - Changed default SQL::Abstract identifier quoting character to `, as double quoted identifiers may be interpreted as string literals if they are not valid identifiers. https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted 3.001 2018-07-20 20:25:12 EDT - Increase DBD::SQLite dependency to 1.54 for FTS5 support. - Add db attribute to Mojo::SQLite::Results and use it to prevent connections from being cached for reuse while results are active. - Add sql_for method to Mojo::SQLite::Migrations. 3.000 2017-07-20 01:16:50 EDT - Changed default for max_connections attribute to 1. - Added support for sharing the database connection cache between multiple Mojo::SQLite objects. (based on Mojo::Pg 4.0) - Added parent attribute to Mojo::SQLite. - Fixed database connection leak with automatic migrations. - Removed deprecated Mojo::SQLite::PubSub and associated methods and attributes. SQLite's serverless nature means it does not have the ability to support client notifications, so it is not possible to implement an efficient pubsub system as in for example PostgreSQL, Redis, or websockets. 2.002 2017-06-01 14:16:34 EDT - Add no_wal option to prevent enabling WAL mode on connection. 2.001 2017-02-18 15:36:16 EST - Set name_sep in default SQL::Abstract object to support proper quoting of table and column names. 2.000 2017-02-11 17:03:53 EST - Add support for generating queries with SQL::Abstract. (based on Mojo::Pg 3.0) - Add abstract attribute to Mojo::SQLite. - Add delete, insert, select, and update methods to Mojo::SQLite::Database. 1.004 2017-01-17 00:10:51 EST - Use Mojo::File from Mojolicious 7.15 instead of deprecated Mojo::Util slurp function. (#9) 1.003 2016-12-11 16:30:31 EST - Add links to alternatives for deprecated Mojo::SQLite::PubSub. 1.002 2016-11-30 11:17:56 EST - Improved contextual caller information in query error messages. (#6) - Fix memory leak when reusing the same database handle many times. (#7) 1.001 2016-11-15 02:32:27 EST - Deprecate Mojo::SQLite::PubSub and associated methods and attributes. SQLite's serverless nature means it does not have the ability to support client notifications, so it is not possible to implement an efficient pubsub system as in for example PostgreSQL, Redis, or websockets. 1.000 2016-08-17 21:01:31 EDT - Bump version to 1.000 as this distribution is considered stable - Added database_class attribute to Mojo::SQLite and results_class attribute to Mojo::SQLite::Database to make it easier for these classes to be extended - Fixed a few fork-safety bugs and memory leaks in Mojo::SQLite::PubSub 0.022 2016-07-14 16:59:36 EDT - Improve expand performance slightly - Add support for encoding and decoding of JSON notifications - Add json method to Mojo::SQLite::PubSub - Fix bug where poll_interval in Mojo::SQLite::PubSub was not correctly used - Fix case where notifications from other connections could be lost 0.021 2016-02-15 01:58:40 EST - Lower dependency on DBI to 1.627 - Remove dependency on URI::QueryParam 0.020 2016-02-14 17:53:55 EST - Bump dependency on DBD::SQLite to 1.50 for JSON1 extension support - Add support for json parameters to query method in Mojo::SQLite::Database - Add expand method to Mojo::SQLite::Results to decode json columns - Add auto_migrate attribute to Mojo::SQLite (from Mojo::Pg) - Add tables method to Mojo::SQLite::Database (from Mojo::Pg) 0.019 2015-11-25 22:45:25 EST - Added support for removing all subscribers of a channel at once in unlisten method in Mojo::SQLite::PubSub - Mojo::SQLite::Migrations now throws an error if the currently active version is greater than the latest version - Added finish method to Mojo::SQLite::Results 0.018 2015-09-21 13:44:30 EDT - Fixes for tests under win32 0.017 2015-09-20 17:24:44 EDT - Fix parsing of options from connection strings - Allow passing a hashref of options in from_filename 0.016 2015-09-20 16:51:53 EDT - Use URI::db to parse connection strings to allow sqlite: connection strings 0.015 2015-09-12 00:13:53 EDT - Minor pubsub fixes 0.014 2015-09-11 20:25:25 EDT - Add Mojo::SQLite::PubSub and listen/notify implementation in Mojo::SQLite::Database 0.013 2015-09-02 02:44:36 EDT - Cache rowid for successful inserts in Results objects as last_insert_id 0.012 2015-08-31 18:26:47 EDT - Include better context for DB error messages from $db->query and migrations - Switch installer to Module::Build::Tiny 0.011 2015-08-13 20:43:37 EDT - Simplify Results refcounting - Pass undef instead of broken Results object to callback if statement prepare failed 0.010 2015-08-08 22:50:06 EDT - Create a tempdir for temporary databases - Use immediate locking for migrations instead of exclusive - Run query callbacks using next_tick instead of immediately - Don't call finish in Results destructor while other Results objects refer to the statement handle (GH #2) 0.009 2015-08-05 12:28:48 EDT - Simplify some code and examples with Mojo::Base::tap 0.008 2015-08-03 19:23:49 EDT - Use and recommend URI to portably parse filenames into URLs - Add from_filename method 0.007 2015-08-01 15:17:22 EDT - Clarify use of URLs in connection strings 0.006 2015-07-28 23:54:45 EDT - Fix results rows() method to return statement handle rows - Make scheme optional for from_string() (and thus the constructor) 0.005 2015-07-28 21:59:23 EDT - Allow specifying bind types for query parameters 0.004 2015-07-28 20:48:04 EDT - Depend on DBI explicitly - Remove List::Util dependency - Add blog example 0.003 2015-07-28 12:56:30 EDT - Minor migrations fixes 0.002 2015-07-28 12:39:12 EDT - Allow passing callback to query() 0.001 2015-07-28 02:48:42 EDT - First release LICENSE100644001750001750 2151515205022012 15044 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0This software is Copyright (c) 2015 by Dan Book. This is free software, licensed under: The Artistic License 2.0 (GPL Compatible) The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dist.ini100644001750001750 45415205022012 15442 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0name = Mojo-SQLite author = Dan Book license = Artistic_2_0 copyright_holder = Dan Book copyright_year = 2015 [@Author::DBOOK] :version = v1.0.3 installer = ModuleBuildTiny::Fallback irc = ircs://irc.libera.chat/#mojo pod_tests = 1 [Deprecated] module = Mojo::SQLite::PubSub t000755001750001750 015205022012 14116 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0crud.t100644001750001750 623015205022012 15401 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } use Test::More; use Mojo::SQLite; use Mojo::IOLoop; my $sql = Mojo::SQLite->new; { my $db = $sql->db; $db->query( 'create table if not exists crud_test ( id integer primary key autoincrement, name text )' ); subtest 'Create' => sub { $db->insert('crud_test', {name => 'foo'}); is_deeply $db->select('crud_test')->hashes->to_array, [{id => 1, name => 'foo'}], 'right structure'; is $db->insert('crud_test', {name => 'bar'})->last_insert_id, 2, 'right value'; is_deeply $db->select('crud_test')->hashes->to_array, [{id => 1, name => 'foo'}, {id => 2, name => 'bar'}], 'right structure'; }; subtest 'Read' => sub { is_deeply $db->select('crud_test')->hashes->to_array, [{id => 1, name => 'foo'}, {id => 2, name => 'bar'}], 'right structure'; is_deeply $db->select('crud_test', ['name'])->hashes->to_array, [{name => 'foo'}, {name => 'bar'}], 'right structure'; is_deeply $db->select('crud_test', ['name'], {name => 'foo'})->hashes->to_array, [{name => 'foo'}], 'right structure'; is_deeply $db->select('crud_test', ['name'], undef, {-desc => 'id'}) ->hashes->to_array, [{name => 'bar'}, {name => 'foo'}], 'right structure'; }; subtest 'Non-blocking read' => sub { my $result; $db->select_p('crud_test')->then(sub { $result = shift->hashes->to_array; })->wait; is_deeply $result, [{id => 1, name => 'foo'}, {id => 2, name => 'bar'}], 'right structure'; $result = undef; $db->select_p('crud_test', undef, undef, {-desc => 'id'})->then(sub { $result = shift->hashes->to_array; })->wait; is_deeply $result, [{id => 2, name => 'bar'}, {id => 1, name => 'foo'}], 'right structure'; }; subtest 'Update' => sub { $db->update('crud_test', {name => 'baz'}, {name => 'foo'}); is_deeply $db->select('crud_test', undef, undef, {-asc => 'id'}) ->hashes->to_array, [{id => 1, name => 'baz'}, {id => 2, name => 'bar'}], 'right structure'; }; subtest 'Delete' => sub { $db->delete('crud_test', {name => 'baz'}); is_deeply $db->select('crud_test', undef, undef, {-asc => 'id'}) ->hashes->to_array, [{id => 2, name => 'bar'}], 'right structure'; $db->delete('crud_test'); is_deeply $db->select('crud_test')->hashes->to_array, [], 'right structure'; }; subtest 'Quoting' => sub { $db->query( 'create table if not exists crud_test2 ( id integer primary key autoincrement, "t e s t" text )' ); $db->insert('crud_test2', {'t e s t' => 'foo'}); $db->insert('main.crud_test2', {'t e s t' => 'bar'}); is_deeply $db->select('main.crud_test2')->hashes->to_array, [{id => 1, 't e s t' => 'foo'}, {id => 2, 't e s t' => 'bar'}], 'right structure'; }; subtest 'Unresolved identifier' => sub { is_deeply $db->select('main.crud_test2', undef, {'t e s t' => 'foo'}) ->hashes->to_array, [{id => 1, 't e s t' => 'foo'}], 'right structure'; ok !eval { $db->select('main.crud_test2', undef, {'test' => 'foo'}); 1 }, 'unknown column'; }; } done_testing(); META.yml100644001750001750 346415205022012 15273 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0--- abstract: 'A tiny Mojolicious wrapper for SQLite' author: - 'Dan Book ' build_requires: File::Spec: '0' Module::Metadata: '0' Test::More: '0.96' configure_requires: Module::Build::Tiny: '0.034' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.037, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Mojo-SQLite no_index: directory: - eg - examples - inc - share - t - xt provides: Mojo::SQLite: file: lib/Mojo/SQLite.pm version: v4.0.0 Mojo::SQLite::Database: file: lib/Mojo/SQLite/Database.pm version: v4.0.0 Mojo::SQLite::Migrations: file: lib/Mojo/SQLite/Migrations.pm version: v4.0.0 Mojo::SQLite::PubSub: file: lib/Mojo/SQLite/PubSub.pm version: v4.0.0 x_deprecated: 1 Mojo::SQLite::Results: file: lib/Mojo/SQLite/Results.pm version: v4.0.0 Mojo::SQLite::Transaction: file: lib/Mojo/SQLite/Transaction.pm version: v4.0.0 requires: Carp: '0' DBD::SQLite: '1.68' DBI: '1.627' File::Spec::Functions: '0' File::Temp: '0' Mojolicious: '8.03' SQL::Abstract::Pg: '1.0' Scalar::Util: '0' URI: '1.69' URI::db: '0.15' URI::file: '4.21' perl: '5.010001' resources: IRC: ircs://irc.libera.chat/#mojo bugtracker: https://github.com/Grinnz/Mojo-SQLite/issues homepage: https://github.com/Grinnz/Mojo-SQLite repository: https://github.com/Grinnz/Mojo-SQLite.git version: v4.0.0 x_contributors: - 'Adam Williamson ' - 'Dan Book ' - 'Dan Book ' - 'Oliver Kurz ' - 'Stefan Adams ' x_generated_by_perl: v5.42.1 x_serialization_backend: 'YAML::Tiny version 1.76' x_spdx_expression: Artistic-2.0 MANIFEST100644001750001750 176715205022012 15157 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.037 Build.PL CONTRIBUTING.md Changes LICENSE MANIFEST META.json META.yml README dist.ini examples/blog/blog.conf examples/blog/lib/Blog.pm examples/blog/lib/Blog/Controller/Posts.pm examples/blog/lib/Blog/Model/Posts.pm examples/blog/migrations/blog.sql examples/blog/script/blog examples/blog/t/blog.t examples/blog/templates/layouts/blog.html.ep examples/blog/templates/posts/_form.html.ep examples/blog/templates/posts/create.html.ep examples/blog/templates/posts/edit.html.ep examples/blog/templates/posts/index.html.ep examples/blog/templates/posts/show.html.ep lib/Mojo/SQLite.pm lib/Mojo/SQLite/Database.pm lib/Mojo/SQLite/Migrations.pm lib/Mojo/SQLite/PubSub.pm lib/Mojo/SQLite/Results.pm lib/Mojo/SQLite/Transaction.pm prereqs.yml t/00-report-prereqs.dd t/00-report-prereqs.t t/connection.t t/crud.t t/database.t t/migrations.t t/migrations/test.sql t/results.t t/sqlite_lite_app.t xt/author/pod-coverage.t xt/author/pod-syntax.t Build.PL100644001750001750 707115205022012 15314 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0# This Build.PL for Mojo-SQLite was generated by # Dist::Zilla::Plugin::ModuleBuildTiny::Fallback 0.027 use strict; use warnings; my %configure_requires = ( 'Module::Build::Tiny' => '0.034', ); my %errors = map { eval "require $_; $_->VERSION($configure_requires{$_}); 1"; $_ => $@, } keys %configure_requires; if (!grep $_, values %errors) { # This section for Mojo-SQLite was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.020. use strict; use warnings; use 5.010001; # use Module::Build::Tiny 0.034; Module::Build::Tiny::Build_PL(); } else { if (not $ENV{PERL_MB_FALLBACK_SILENCE_WARNING}) { warn <<'EOW' *** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING *** If you're seeing this warning, your toolchain is really, really old* and you'll almost certainly have problems installing CPAN modules from this century. But never fear, dear user, for we have the technology to fix this! If you're using CPAN.pm to install things, then you can upgrade it using: cpan CPAN If you're using CPANPLUS to install things, then you can upgrade it using: cpanp CPANPLUS If you're using cpanminus, you shouldn't be seeing this message in the first place, so please file an issue on github. This public service announcement was brought to you by the Perl Toolchain Gang, the irc.perl.org #toolchain IRC channel, and the number 42. ---- * Alternatively, you are running this file manually, in which case you need to learn to first fulfill all configure requires prerequisites listed in META.yml or META.json -- or use a cpan client to install this distribution. You can also silence this warning for future installations by setting the PERL_MB_FALLBACK_SILENCE_WARNING environment variable, but please don't do that until you fix your toolchain as described above. Errors from configure prereqs: EOW . do { require Data::Dumper; Data::Dumper->new([ \%errors ])->Indent(2)->Terse(1)->Sortkeys(1)->Dump; }; sleep 10 if -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT)); } # This section was automatically generated by Dist::Zilla::Plugin::ModuleBuild v6.037 use strict; use warnings; require Module::Build; Module::Build->VERSION(0.28); my %module_build_args = ( "configure_requires" => { "Module::Build::Tiny" => "0.034" }, "dist_abstract" => "A tiny Mojolicious wrapper for SQLite", "dist_author" => [ "Dan Book " ], "dist_name" => "Mojo-SQLite", "dist_version" => "v4.0.0", "license" => "artistic_2", "module_name" => "Mojo::SQLite", "recursive_test_files" => 1, "requires" => { "Carp" => 0, "DBD::SQLite" => "1.68", "DBI" => "1.627", "File::Spec::Functions" => 0, "File::Temp" => 0, "Mojolicious" => "8.03", "SQL::Abstract::Pg" => "1.0", "Scalar::Util" => 0, "URI" => "1.69", "URI::db" => "0.15", "URI::file" => "4.21", "perl" => "5.010001" }, "test_requires" => { "File::Spec" => 0, "Module::Metadata" => 0, "Test::More" => "0.96" } ); my %fallback_build_requires = ( "File::Spec" => 0, "Module::Metadata" => 0, "Test::More" => "0.96" ); unless ( eval { Module::Build->VERSION(0.4004) } ) { delete $module_build_args{test_requires}; $module_build_args{build_requires} = \%fallback_build_requires; } my $build = Module::Build->new(%module_build_args); $build->create_build_script; } META.json100644001750001750 617315205022012 15443 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0{ "abstract" : "A tiny Mojolicious wrapper for SQLite", "author" : [ "Dan Book " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.037, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Mojo-SQLite", "no_index" : { "directory" : [ "eg", "examples", "inc", "share", "t", "xt" ] }, "prereqs" : { "configure" : { "requires" : { "Module::Build::Tiny" : "0.034" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "requires" : { "Carp" : "0", "DBD::SQLite" : "1.68", "DBI" : "1.627", "File::Spec::Functions" : "0", "File::Temp" : "0", "Mojolicious" : "8.03", "SQL::Abstract::Pg" : "1.0", "Scalar::Util" : "0", "URI" : "1.69", "URI::db" : "0.15", "URI::file" : "4.21", "perl" : "5.010001" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "File::Spec" : "0", "Module::Metadata" : "0", "Test::More" : "0.96" } } }, "provides" : { "Mojo::SQLite" : { "file" : "lib/Mojo/SQLite.pm", "version" : "v4.0.0" }, "Mojo::SQLite::Database" : { "file" : "lib/Mojo/SQLite/Database.pm", "version" : "v4.0.0" }, "Mojo::SQLite::Migrations" : { "file" : "lib/Mojo/SQLite/Migrations.pm", "version" : "v4.0.0" }, "Mojo::SQLite::PubSub" : { "file" : "lib/Mojo/SQLite/PubSub.pm", "version" : "v4.0.0", "x_deprecated" : 1 }, "Mojo::SQLite::Results" : { "file" : "lib/Mojo/SQLite/Results.pm", "version" : "v4.0.0" }, "Mojo::SQLite::Transaction" : { "file" : "lib/Mojo/SQLite/Transaction.pm", "version" : "v4.0.0" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/Grinnz/Mojo-SQLite/issues" }, "homepage" : "https://github.com/Grinnz/Mojo-SQLite", "repository" : { "type" : "git", "url" : "https://github.com/Grinnz/Mojo-SQLite.git", "web" : "https://github.com/Grinnz/Mojo-SQLite" }, "x_IRC" : "ircs://irc.libera.chat/#mojo" }, "version" : "v4.0.0", "x_contributors" : [ "Adam Williamson ", "Dan Book ", "Dan Book ", "Oliver Kurz ", "Stefan Adams " ], "x_generated_by_perl" : "v5.42.1", "x_serialization_backend" : "Cpanel::JSON::XS version 4.40", "x_spdx_expression" : "Artistic-2.0" } prereqs.yml100644001750001750 54515205022012 16203 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0runtime: requires: perl: '5.010001' Carp: 0 DBI: '1.627' DBD::SQLite: '1.68' # for JSON1, FTS5, DQS, sqlite_string_mode File::Spec::Functions: 0 File::Temp: 0 Mojolicious: '8.03' Scalar::Util: 0 SQL::Abstract::Pg: '1.0' URI: '1.69' URI::db: '0.15' URI::file: '4.21' test: requires: Test::More: '0.96' results.t100644001750001750 1422515205022012 16170 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; use Test::More; use Mojo::SQLite; use Mojo::IOLoop; use Mojo::Promise; package MojoSQLiteTest::Database; use Mojo::Base 'Mojo::SQLite::Database'; sub results_class {'MojoSQLiteTest::Results'} package MojoSQLiteTest::Results; use Mojo::Base 'Mojo::SQLite::Results'; sub array_test { shift->array } package main; my $sql = Mojo::SQLite->new; { my $db = $sql->db; $db->query( 'create table if not exists results_test ( id integer primary key autoincrement, name text )' ); my $results; $results = $db->query('insert into results_test (name) values (?)', $_) for qw(foo bar); is $results->last_insert_id, 2, 'right last_insert_id'; is $db->query('update results_test set name=name')->rows, 2, 'two rows affected'; subtest 'Tables' => sub { ok !!(grep {/^"main"."results_test"$/i} @{$db->tables}), 'results table exists'; ok !(grep {/^"main"."sqlite_master"$/i} @{$db->tables}), 'internal tables are hidden'; ok !(grep {/^"temp"."sqlite_temp_master"$/i} @{$db->tables}), 'internal tables are hidden'; }; subtest 'Result methods' => sub { is_deeply $db->query('select * from results_test')->columns, ['id', 'name'], 'right structure'; is_deeply $db->query('select * from results_test')->array, [1, 'foo'], 'right structure'; my $results = $db->query('select * from results_test'); is_deeply $results->arrays->to_array, [[1, 'foo'], [2, 'bar']], 'right structure'; is_deeply $results->rows, 2, 'two rows'; is_deeply $db->query('select * from results_test')->hash, {id => 1, name => 'foo'}, 'right structure'; is_deeply $db->query('select * from results_test')->hashes->to_array, [{id => 1, name => 'foo'}, {id => 2, name => 'bar'}], 'right structure'; is $sql->db->query('select * from results_test')->text, "1 foo\n2 bar\n", 'right text'; }; subtest 'Custom database and results classes' => sub { is ref $db, 'Mojo::SQLite::Database', 'right class'; $sql->database_class('MojoSQLiteTest::Database'); $db = $sql->db; is ref $db, 'MojoSQLiteTest::Database', 'right class'; is ref $db->query('select 1'), 'MojoSQLiteTest::Results', 'right class'; is_deeply $db->query('select * from results_test')->array_test, [1, 'foo'], 'right structure'; }; subtest 'JSON' => sub { is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->hash, {foo => {bar => 'baz'}}, 'right structure'; is_deeply $db->query('select ? as foo', {-json => {bar => 'baz'}}) ->expand(json => 'foo')->hash, {foo => {bar => 'baz'}}, 'right structure'; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->array, [{bar => 'baz'}], 'right structure'; my $hashes = [{foo => {one => 1}, bar => 'a'}, {foo => {two => 2}, bar => 'b'}]; is_deeply $db->query( "select 'a' as bar, ? as foo union all select 'b' as bar, ? as foo", {json => {one => 1}}, {json => {two => 2}} )->expand(json => 'foo')->hashes->to_array, $hashes, 'right structure'; my $arrays = [['a', {one => 1}], ['b', {two => 2}]]; is_deeply $db->query( "select 'a' as bar, ? as foo union all select 'b' as bar, ? as foo", {json => {one => 1}}, {json => {two => 2}} )->expand(json => 'foo')->arrays->to_array, $arrays, 'right structure'; }; subtest 'Iterate' => sub { my $results = $db->query('select * from results_test'); is_deeply $results->array, [1, 'foo'], 'right structure'; is_deeply $results->array, [2, 'bar'], 'right structure'; is $results->array, undef, 'no more results'; }; subtest 'Non-blocking query where not all results have been fetched' => sub { my ($fail, $result); my @promises = ( $db->query_p('select name from results_test')->then(sub { my $results = shift; push @$result, $results->array; $results->finish; }), $db->query_p('select name from results_test')->then(sub { my $results = shift; push @$result, $results->array_test; $results->finish; }), $db->query_p('select name from results_test')->then(sub { my $results = shift; push @$result, $results->array; }), ); Mojo::Promise->all(@promises)->catch(sub { $fail = 1 })->wait; ok !$fail, 'no error'; is_deeply $result, [['foo'], ['foo'], ['foo']], 'right structure'; }; subtest 'Transactions' => sub { { my $tx = $db->begin; $db->query("insert into results_test (name) values ('tx1')"); $db->query("insert into results_test (name) values ('tx1')"); $tx->commit; } is_deeply $db->query('select * from results_test where name = ?', 'tx1') ->hashes->to_array, [{id => 3, name => 'tx1'}, {id => 4, name => 'tx1'}], 'right structure'; { my $tx = $db->begin; $db->query("insert into results_test (name) values ('tx2')"); $db->query("insert into results_test (name) values ('tx2')"); } is_deeply $db->query('select * from results_test where name = ?', 'tx2') ->hashes->to_array, [], 'no results'; eval { my $tx = $db->begin; $db->query("insert into results_test (name) values ('tx3')"); $db->query("insert into results_test (name) values ('tx3')"); $db->query('does_not_exist'); $tx->commit; }; like $@, qr/does_not_exist/, 'right error'; is_deeply $db->query('select * from results_test where name = ?', 'tx3') ->hashes->to_array, [], 'no results'; { my $timeout = $db->dbh->sqlite_busy_timeout // 0; $db->dbh->sqlite_busy_timeout(100); my $tx; eval { $tx = $db->begin('immediate'); $db->begin('immediate'); }; ok $@, 'transaction failed'; ok $db->dbh->err, 'database error code retained'; $db->dbh->sqlite_busy_timeout($timeout); } }; subtest 'Issue #2' => sub { my $results1 = $db->query('select 1 as one'); is_deeply $results1->hashes, [{one => 1}], 'right structure'; my $results2 = $db->query('select 1 as one'); undef $results1; is_deeply $results2->hashes, [{one => 1}], 'right structure'; } } done_testing(); database.t100644001750001750 2236015205022012 16232 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } use Test::More; use Mojo::SQLite; use Mojo::IOLoop; use Mojo::JSON 'true'; use DBI ':sql_types'; use Mojo::Util 'encode'; my $sql = Mojo::SQLite->new; subtest 'Connected' => sub { ok $sql->db->ping, 'connected'; }; subtest 'Blocking select' => sub { is_deeply $sql->db->query('select 1 as one, 2 as two, 3 as three')->hash, {one => 1, two => 2, three => 3}, 'right structure'; }; subtest 'Non-blocking select' => sub { my ($fail, $result); my $same; my $db = $sql->db; $db->query( 'select 1 as one, 2 as two, 3 as three' => sub { my ($db, $err, $results) = @_; $fail = $err; $result = $results->hash; $same = $db->dbh eq $results->db->dbh; Mojo::IOLoop->stop; } ); Mojo::IOLoop->start; ok $same, 'same database handles'; ok !$fail, 'no error'; is_deeply $result, {one => 1, two => 2, three => 3}, 'right structure'; }; subtest 'Concurrent non-blocking selects' => sub { my ($fail, $result); my $one = $sql->db->query_p('select 1 as one'); my $two = $sql->db->query_p('select 2 as two'); my $again = $sql->db->query_p('select 2 as two'); Mojo::Promise->all($one, $again, $two)->then(sub { $result = [map { $_->[0]->hashes->first } @_]; })->catch(sub { $fail = 1; })->wait; ok !$fail, 'no error'; is_deeply $result, [{one => 1}, {two => 2}, {two => 2}], 'right structure'; }; subtest 'Sequential non-blocking selects' => sub { my ($fail, $result); my $db = $sql->db; $db->query_p('select 1 as one')->then(sub { push @$result, shift->hashes->first; $db->query_p('select 1 as one'); })->then(sub { push @$result, shift->hashes->first; $db->query_p('select 2 as two'); })->then(sub { push @$result, shift->hashes->first; })->catch(sub { $fail = 1; })->wait; ok !$fail, 'no error'; is_deeply $result, [{one => 1}, {one => 1}, {two => 2}], 'right structure'; }; subtest 'Connection cache' => sub { is $sql->max_connections, 1, 'right default'; my @dbhs = map { $_->dbh } $sql->db, $sql->db, $sql->db, $sql->db, $sql->db; is_deeply \@dbhs, [map { $_->dbh } $sql->db, $sql->db, $sql->db, $sql->db, $sql->db], 'same database handles'; @dbhs = (); my $dbh = $sql->max_connections(1)->db->dbh; is $sql->db->dbh, $dbh, 'same database handle'; isnt $sql->db->dbh, $sql->db->dbh, 'different database handles'; is $sql->db->dbh, $dbh, 'different database handles'; $dbh = $sql->db->dbh; is $sql->db->dbh, $dbh, 'same database handle'; $sql->db->disconnect; isnt $sql->db->dbh, $dbh, 'different database handles'; }; subtest 'Statement cache' => sub { my $db = $sql->db; my $sth = $db->query('select 3 as three')->sth; is $db->query('select 3 as three')->sth, $sth, 'same statement handle'; isnt $db->query('select 4 as four')->sth, $sth, 'different statement handles'; is $db->query('select 3 as three')->sth, $sth, 'same statement handle'; undef $db; $db = $sql->db; my $results = $db->query('select 3 as three'); is $results->sth, $sth, 'same statement handle'; isnt $db->query('select 3 as three')->sth, $sth, 'different statement handles'; $sth = $db->query('select 3 as three')->sth; is $db->query('select 3 as three')->sth, $sth, 'same statement handle'; isnt $db->query('select 5 as five')->sth, $sth, 'different statement handles'; isnt $db->query('select 6 as six')->sth, $sth, 'different statement handles'; is $db->query('select 3 as three')->sth, $sth, 'same statement handle'; }; subtest 'Connection reuse' => sub { my $db = $sql->db; my $dbh = $db->dbh; my $results = $db->query('select 1'); undef $db; my $db2 = $sql->db; isnt $db2->dbh, $dbh, 'new database handle'; undef $results; my $db3 = $sql->db; is $db3->dbh, $dbh, 'same database handle'; $results = $db3->query('select 2'); is $results->db->dbh, $dbh, 'same database handle'; is $results->array->[0], 2, 'right result'; }; subtest 'Bind types' => sub { my $db = $sql->db; is_deeply $db->query('select ? as foo', {type => SQL_VARCHAR, value => 'bar'}) ->hash, {foo => 'bar'}, 'right structure'; is_deeply $db->query('select ? as foo', {type => SQL_INTEGER, value => 5}) ->hash, {foo => 5}, 'right structure'; is_deeply $db->query('select ? as foo', {type => SQL_REAL, value => 2.5}) ->hash, {foo => 2.5}, 'right structure'; is_deeply $db->query('select ? as foo', {type => SQL_VARCHAR, value => '☃♥'}) ->hash, {foo => '☃♥'}, 'right structure'; is_deeply $db->query('select ? as foo', {type => SQL_BLOB, value => encode 'UTF-8', '☃♥'}) ->hash, {foo => encode 'UTF-8', '☃♥'}, 'right structure'; }; subtest 'JSON' => sub { my $db = $sql->db; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->hash, {foo => {bar => 'baz'}}, 'right structure'; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->array, [{bar => 'baz'}], 'right structure'; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->hashes->first, {foo => {bar => 'baz'}}, 'right structure'; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->arrays->first, [{bar => 'baz'}], 'right structure'; is_deeply $db->query('select ? as foo', {json => {bar => 'baz'}})->hash, {foo => '{"bar":"baz"}'}, 'right structure'; is_deeply $db->query('select ? as foo', {json => \1}) ->expand(json => 'foo')->hashes->first, {foo => true}, 'right structure'; is_deeply $db->query('select ? as foo', undef)->expand(json => 'foo')->hash, {foo => undef}, 'right structure'; is_deeply $db->query('select ? as foo', undef)->expand(json => 'foo')->array, [undef], 'right structure'; my $results = $db->query('select ?', undef); my $name = $results->columns->[0]; is_deeply $results->expand(json => $name)->array, [undef], 'right structure'; is_deeply $results->expand(json => $name)->array, undef, 'no more results'; is_deeply $db->query('select ? as unicode', {json => {'☃' => '♥'}}) ->expand(json => 'unicode')->hash, {unicode => {'☃' => '♥'}}, 'right structure'; is_deeply $db->query("select json_object('☃', ?) as unicode", '♥') ->expand(json => 'unicode')->hash, {unicode => {'☃' => '♥'}}, 'right structure'; is_deeply $db->query('select ? as foo, ? as bar', {json => {baz => 'foo'}}, {json => {baz => 'bar'}})->expand(json => 'foo')->hash, {foo => {baz => 'foo'}, bar => '{"baz":"bar"}'}, 'right structure'; is_deeply $db->query('select ? as foo, ? as bar', {json => {baz => 'foo'}}, {json => {baz => 'bar'}})->expand(json => ['foo','bar'])->hash, {foo => {baz => 'foo'}, bar => {baz => 'bar'}}, 'right structure'; }; subtest 'Fork-safety' => sub { my $dbh = $sql->db->dbh; my ($connections, $current) = @_; $sql->on( connection => sub { my ($sql, $dbh) = @_; $connections++; $current = $dbh; } ); is $sql->db->dbh, $dbh, 'same database handle'; ok !$connections, 'no new connections'; { local $$ = -23; isnt $sql->db->dbh, $dbh, 'different database handles'; is $sql->db->dbh, $current, 'same database handle'; is $connections, 1, 'one new connection'; } $sql->unsubscribe('connection'); }; subtest 'Shared connection cache' => sub { my $sql2 = Mojo::SQLite->new($sql); is $sql2->parent, $sql, 'right parent'; my $dbh = $sql->db->dbh; is $sql->db->dbh, $dbh, 'same database handle'; is $sql2->db->dbh, $dbh, 'same database handle'; is $sql->db->dbh, $dbh, 'same database handle'; is $sql2->db->dbh, $dbh, 'same database handle'; my $db = $sql->db; is_deeply $db->query('select 1 as one')->hashes->to_array, [{one => 1}], 'right structure'; $dbh = $db->dbh; $db->disconnect; $db = $sql2->db; is_deeply $db->query('select 1 as one')->hashes->to_array, [{one => 1}], 'right structure'; isnt $db->dbh, $dbh, 'different database handle'; }; subtest 'Blocking error' => sub { eval { $sql->db->query('does_not_exist') }; like $@, qr/does_not_exist.*database\.t/s, 'right error'; }; subtest 'Non-blocking error' => sub { my ($fail, $result); my $db = $sql->db; $db->query( 'does_not_exist' => sub { my ($db, $err, $results) = @_; ($fail, $result) = ($err, $results); Mojo::IOLoop->stop; } ); Mojo::IOLoop->start; like $fail, qr/does_not_exist/, 'right error'; is $db->dbh->errstr, $fail, 'same error'; }; subtest 'Error context' => sub { eval { $sql->db->query('select * from table_does_not_exist') }; like $@, qr/database\.t/, 'right error'; }; subtest 'Double-quoted literal' => sub { ok !eval { $sql->db->query('select "does_not_exist"') }, 'no double-quoted string literals'; }; subtest 'WAL mode option' => sub { my $journal_mode = $sql->db->query('pragma journal_mode')->arrays->first->[0]; is uc $journal_mode, 'DELETE', 'right journal mode'; my $sql = Mojo::SQLite->new; $sql->options->{wal_mode} = 1; $journal_mode = $sql->db->query('pragma journal_mode')->arrays->first->[0]; is uc $journal_mode, 'WAL', 'right journal mode'; $sql = Mojo::SQLite->new; $sql->options->{no_wal} = 1; $journal_mode = $sql->db->query('pragma journal_mode')->arrays->first->[0]; is uc $journal_mode, 'DELETE', 'right journal mode'; }; done_testing(); connection.t100644001750001750 1021515205022012 16621 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; use Test::More; use DBD::SQLite::Constants ':dbd_sqlite_string_mode'; use Mojo::SQLite; use URI::file; subtest 'Defaults' => sub { my $sql = Mojo::SQLite->new; like $sql->dsn, qr/^dbi:SQLite:dbname=/, 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Minimal connection string with file' => sub { my $sql = Mojo::SQLite->new('test.db'); is $sql->dsn, 'dbi:SQLite:dbname=test.db', 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Minimal connection string with in-memory database and option' => sub { my $sql = Mojo::SQLite->new('sqlite::memory:?PrintError=1'); is $sql->dsn, 'dbi:SQLite:dbname=:memory:', 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 1, RaiseError => 1, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Connection string with absolute filename and options' => sub { my $uri = URI::file->new('/tmp/sqlite.db?#', 'unix') ->Mojo::Base::tap(query_form => {PrintError => 1, RaiseError => 0}); my $sql; { # Force unix interpretation local %URI::file::OS_CLASS = (); $sql = Mojo::SQLite->new($uri); } is $sql->dsn, 'dbi:SQLite:dbname=/tmp/sqlite.db?#', 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 1, RaiseError => 0, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Connection string with lots of zeros' => sub { my $sql = Mojo::SQLite->new('0?RaiseError=0'); is $sql->dsn, 'dbi:SQLite:dbname=0', 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 0, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Parse filename' => sub { my $sql = Mojo::SQLite->new->from_filename('/foo#/bar?.db', {PrintError => 1}); is $sql->dsn, 'dbi:SQLite:dbname=/foo#/bar?.db', 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 1, RaiseError => 1, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Invalid connection string' => sub { eval { Mojo::SQLite->new('http://localhost:3000/test') }; like $@, qr/Invalid SQLite connection string/, 'right error'; }; subtest 'Legacy sqlite_unicode enabled' => sub { my $sql = Mojo::SQLite->new('test.db?sqlite_unicode=1'); like $sql->dsn, qr/^dbi:SQLite:dbname=/, 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1, sqlite_unicode => 1, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; subtest 'Legacy sqlite_unicode disabled' => sub { my $sql = Mojo::SQLite->new('test.db?sqlite_unicode=0'); like $sql->dsn, qr/^dbi:SQLite:dbname=/, 'right data source'; my $options = { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1, sqlite_unicode => 0, wal_mode => 0, }; is_deeply $sql->options, $options, 'right options'; }; done_testing(); migrations.t100644001750001750 1665715205022012 16656 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; use Test::More; use File::Spec::Functions 'catfile'; use File::Temp; use FindBin; use Mojo::SQLite; my $tempdir = File::Temp->newdir; my $tempfile = catfile($tempdir, 'test.db'); # Clean up before start my $sql = Mojo::SQLite->new->from_filename($tempfile); $sql->db->query('drop table if exists mojo_migrations'); subtest 'Defaults' => sub { is $sql->migrations->name, 'migrations', 'right name'; is $sql->migrations->latest, 0, 'latest version is 0'; is $sql->migrations->active, 0, 'active version is 0'; }; subtest 'Create migrations table' => sub { ok !(grep {/^"main"."mojo_migrations"$/i} @{$sql->db->tables}), 'migrations table does not exist'; is $sql->migrations->migrate->active, 0, 'active version is 0'; ok !(grep {/^"main"."mojo_migrations"$/i} @{$sql->db->tables}), 'migrations table does not exist'; is $sql->migrations->from_string("-- 1 up\n\n")->migrate->active, 1, 'active version is 1'; ok !!(grep {/^"main"."mojo_migrations"$/i} @{$sql->db->tables}), 'migrations table exists'; is $sql->migrations->migrate(0)->active, 0, 'active version is 0'; }; subtest 'Migrations from DATA section' => sub { is $sql->migrations->from_data->latest, 0, 'latest version is 0'; is $sql->migrations->from_data(__PACKAGE__)->latest, 0, 'latest version is 0'; is $sql->migrations->name('test1')->from_data->latest, 10, 'latest version is 10'; is $sql->migrations->name('test2')->from_data->latest, 2, 'latest version is 2'; is $sql->migrations->name('migrations')->from_data(__PACKAGE__, 'test1') ->latest, 10, 'latest version is 10'; is $sql->migrations->name('test2')->from_data(__PACKAGE__)->latest, 2, 'latest version is 2'; }; subtest 'Different syntax variations' => sub { $sql->migrations->name('migrations_test')->from_string(<migrations->latest, 10, 'latest version is 10'; is $sql->migrations->active, 0, 'active version is 0'; is $sql->migrations->migrate->active, 10, 'active version is 10'; ok !!(grep {/^"main"."migration_test_one"$/i} @{$sql->db->tables}), 'first table exists'; ok !!(grep {/^"main"."migration_test_two"$/i} @{$sql->db->tables}), 'second table exists'; is_deeply $sql->db->query('select * from migration_test_one')->hash, {foo => 'works ♥'}, 'right structure'; is $sql->migrations->migrate->active, 10, 'active version is 10'; is $sql->migrations->migrate(1)->active, 1, 'active version is 1'; is $sql->db->query('select * from migration_test_one')->hash, undef, 'no result'; is $sql->migrations->migrate(3)->active, 3, 'active version is 3'; is $sql->db->query('select * from migration_test_two')->hash, undef, 'no result'; is $sql->migrations->migrate->active, 10, 'active version is 10'; is_deeply $sql->db->query('select * from migration_test_two')->hash, {bar => 'works too'}, 'right structure'; is $sql->migrations->migrate(0)->active, 0, 'active version is 0'; }; subtest 'Bad and concurrent migrations' => sub { my $sql2 = Mojo::SQLite->new->from_filename($tempfile); $sql2->migrations->name('migrations_test2') ->from_file(catfile($FindBin::Bin, 'migrations', 'test.sql')); is $sql2->migrations->latest, 4, 'latest version is 4'; is $sql2->migrations->active, 0, 'active version is 0'; eval { $sql2->migrations->migrate }; like $@, qr/does_not_exist/, 'right error'; is $sql2->migrations->migrate(3)->active, 3, 'active version is 3'; is $sql2->migrations->migrate(2)->active, 2, 'active version is 2'; is $sql->migrations->active, 0, 'active version is still 0'; is $sql->migrations->migrate->active, 10, 'active version is 10'; is_deeply $sql2->db->query('select * from migration_test_three') ->hashes->to_array, [{baz => 'just'}, {baz => 'works ♥'}], 'right structure'; is $sql->migrations->migrate(0)->active, 0, 'active version is 0'; is $sql2->migrations->migrate(0)->active, 0, 'active version is 0'; }; subtest 'Migrate automatically' => sub { my $sql3 = Mojo::SQLite->new->from_filename($tempfile); $sql3->migrations->name('migrations_test')->from_string(<<'EOF'); -- 5 up create table if not exists migration_test_six (foo text); -- 6 up insert into migration_test_six values ('works!'); -- 5 down drop table if exists migration_test_six; -- 6 down delete from migration_test_six; EOF $sql3->auto_migrate(1)->db; is $sql3->migrations->active, 6, 'active version is 6'; is_deeply $sql3->db->query('select * from migration_test_six')->hashes, [{foo => 'works!'}], 'right structure'; is $sql3->migrations->migrate(5)->active, 5, 'active version is 5'; is_deeply $sql3->db->query('select * from migration_test_six')->hashes, [], 'right structure'; is $sql3->migrations->migrate(0)->active, 0, 'active version is 0'; is $sql3->migrations->sql_for(0, 5), <migrations->sql_for(6, 0), <migrations->sql_for(6, 5), <migrations->sql_for(6, 6), '', 'right SQL'; is $sql3->migrations->sql_for(2, 3), '', 'right SQL'; }; subtest 'Migrate automatically with shared connection cache' => sub { my $sql4 = Mojo::SQLite->new->from_filename($tempfile); my $sql5 = Mojo::SQLite->new($sql4); $sql4->auto_migrate(1)->migrations->name('test1')->from_data; $sql5->auto_migrate(1)->migrations->name('test3')->from_data; is_deeply $sql5->db->query('select * from migration_test_four') ->hashes->to_array, [{test => 10}], 'right structure'; is_deeply $sql5->db->query('select * from migration_test_six')->hashes->to_array, [], 'right structure'; }; subtest 'Unknown version' => sub { eval { $sql->migrations->migrate(23) }; like $@, qr/Version 23 has no migration/, 'right error'; }; subtest 'Version mismatch' => sub { my $newer = <migrations->name('migrations_test3')->from_string($newer); is $sql->migrations->migrate->active, 2, 'active version is 2'; $sql->migrations->from_string(<migrations->migrate }; like $@, qr/Active version 2 is greater than the latest version 1/, 'right error'; eval { $sql->migrations->migrate(0) }; like $@, qr/Active version 2 is greater than the latest version 1/, 'right error'; is $sql->migrations->from_string($newer)->migrate(0)->active, 0, 'active version is 0'; }; done_testing(); __DATA__ @@ test1 -- 7 up create table migration_test_four (test integer); -- 10 up insert into migration_test_four values (10); @@ test2 -- 2 up create table migration_test_five (test integer); @@ test3 -- 2 up create table migration_test_six (test integer); CONTRIBUTING.md100644001750001750 1050015205022012 16260 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0# HOW TO CONTRIBUTE Thank you for considering contributing to this distribution. This file contains instructions that will help you work with the source code. The distribution is managed with [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla). This means that many of the usual files you might expect are not in the repository, but are generated at release time. Some generated files are kept in the repository as a convenience (e.g. Build.PL/Makefile.PL and META.json). Generally, **you do not need Dist::Zilla to contribute patches**. You may need Dist::Zilla to create a tarball. See below for guidance. ## Getting dependencies If you have App::cpanminus 1.6 or later installed, you can use [cpanm](https://metacpan.org/pod/cpanm) to satisfy dependencies like this: $ cpanm --installdeps --with-develop . You can also run this command (or any other cpanm command) without installing App::cpanminus first, using the fatpacked `cpanm` script via curl or wget: $ curl -L https://cpanmin.us | perl - --installdeps --with-develop . $ wget -qO - https://cpanmin.us | perl - --installdeps --with-develop . Otherwise, look for either a `cpanfile`, `prereqs.json`/`prereqs.yml`, or `META.json` file for a list of dependencies to satisfy. ## Running tests You can run tests directly using the `prove` tool: $ prove -l $ prove -lv t/some_test_file.t For most of my distributions, `prove` is entirely sufficient for you to test any patches you have. I use `prove` for 99% of my testing during development. ## Code style and tidying Please try to match any existing coding style. If there is a `.perltidyrc` file, please install Perl::Tidy and use perltidy before submitting patches. ## Installing and using Dist::Zilla [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla) is a very powerful authoring tool, optimized for maintaining a large number of distributions with a high degree of automation, but it has a large dependency chain, a bit of a learning curve and requires a number of author-specific plugins. To install it from CPAN, I recommend one of the following approaches for the quickest installation: # using CPAN.pm, but bypassing non-functional pod tests $ cpan TAP::Harness::Restricted $ PERL_MM_USE_DEFAULT=1 HARNESS_CLASS=TAP::Harness::Restricted cpan Dist::Zilla # using cpanm, bypassing *all* tests $ cpanm -n Dist::Zilla In either case, it's probably going to take about 10 minutes. Go for a walk, go get a cup of your favorite beverage, take a bathroom break, or whatever. When you get back, Dist::Zilla should be ready for you. Then you need to install any plugins specific to this distribution: $ dzil authordeps --missing | cpanm You can use Dist::Zilla to install the distribution's dependencies if you haven't already installed them with cpanm: $ dzil listdeps --missing --develop | cpanm You can instead combine these two steps into one command by installing Dist::Zilla::App::Command::installdeps then running: $ dzil installdeps Once everything is installed, here are some dzil commands you might try: $ dzil build $ dzil test $ dzil regenerate You can learn more about Dist::Zilla at http://dzil.org/ ## Other notes This distribution maintains the generated `META.json` and either `Makefile.PL` or `Build.PL` in the repository. This allows the distribution or its dependencies to be installed directly from GitHub or a local git repository without needing Dist::Zilla for testing or [CI](https://en.wikipedia.org/wiki/Continuous_integration). $ cpanm git://github.com/Author/Distribution-Name.git $ cd Distribution-Name; cpanm --installdeps . Contributions are preferred in the form of a GitHub pull request. See [Using pull requests](https://help.github.com/articles/using-pull-requests/) for further information. You can use the GitHub issue tracker to report issues without an accompanying patch. # CREDITS This file was adapted from an initial `CONTRIBUTING.mkdn` file from David Golden under the terms of the [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/), with inspiration from the contributing documents from [Dist::Zilla::Plugin::Author::KENTNL::CONTRIBUTING](https://metacpan.org/pod/Dist::Zilla::Plugin::Author::KENTNL::CONTRIBUTING) and [Dist::Zilla::PluginBundle::Author::ETHER](https://metacpan.org/pod/Dist::Zilla::PluginBundle::Author::ETHER). Mojo000755001750001750 015205022012 15325 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/libSQLite.pm100644001750001750 4543215205022012 17214 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/lib/Mojopackage Mojo::SQLite; use Mojo::Base 'Mojo::EventEmitter'; use Carp 'croak'; use DBI; use DBD::SQLite; use DBD::SQLite::Constants qw(:database_connection_configuration_options :dbd_sqlite_string_mode); use File::Spec::Functions 'catfile'; use File::Temp; use Mojo::SQLite::Database; use Mojo::SQLite::Migrations; use Scalar::Util qw(blessed weaken); use SQL::Abstract::Pg; use URI; use URI::db; our $VERSION = 'v4.0.0'; has abstract => sub { SQL::Abstract::Pg->new(name_sep => '.', quote_char => '"') }; has 'auto_migrate'; has database_class => 'Mojo::SQLite::Database'; has dsn => sub { _url_from_file(shift->_tempfile)->dbi_dsn }; has max_connections => 1; has migrations => sub { Mojo::SQLite::Migrations->new(sqlite => shift) }; has options => sub { { AutoCommit => 1, AutoInactiveDestroy => 1, PrintError => 0, RaiseError => 1, sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_FALLBACK, wal_mode => 0, }; }; has 'parent'; sub new { @_ > 1 ? shift->SUPER::new->from_string(@_) : shift->SUPER::new } sub db { $_[0]->database_class->new(dbh => $_[0]->_prepare, sqlite => $_[0]) } sub from_filename { shift->from_string(_url_from_file(shift, shift)) } sub from_string { my ($self, $str) = @_; return $self unless $str; return $self->parent($str) if blessed $str and $str->isa('Mojo::SQLite'); my $url = URI->new($str); # Options my %options = $url->query_form; $url->query(undef); # don't set default string_mode if sqlite_unicode legacy option is set delete $self->options->{sqlite_string_mode} if exists $options{sqlite_unicode}; @{$self->options}{keys %options} = values %options; # Parse URL based on scheme $url->scheme('file') unless $url->has_recognized_scheme; if ($url->scheme eq 'file') { $url = _url_from_file($url->file); } elsif ($url->scheme ne 'db') { $url = URI::db->new($url); } croak qq{Invalid SQLite connection string "$str"} unless $url->has_recognized_engine and $url->canonical_engine eq 'sqlite' and (($url->host // '') eq '' or $url->host eq 'localhost'); # Temp database file $url->dbname($self->_tempfile) if $url->dbname eq ':temp:'; return $self->dsn($url->dbi_dsn); } sub _dequeue { my $self = shift; # Fork-safety delete @$self{qw(pid queue)} unless ($self->{pid} //= $$) eq $$; while (my $dbh = shift @{$self->{queue} || []}) { return $dbh if $dbh->ping } my $dbh = DBI->connect($self->dsn, undef, undef, $self->options) // croak "DBI connection to @{[$self->dsn]} failed: $DBI::errstr"; # RaiseError disabled $dbh->sqlite_db_config(SQLITE_DBCONFIG_DQS_DDL, 0); $dbh->sqlite_db_config(SQLITE_DBCONFIG_DQS_DML, 0); if ($self->options->{wal_mode} and !$self->options->{no_wal}) { $dbh->do('pragma journal_mode=WAL'); $dbh->do('pragma synchronous=NORMAL'); } # Cache the last insert rowid on inserts weaken(my $weakdbh = $dbh); $dbh->sqlite_update_hook(sub { $weakdbh->{private_mojo_last_insert_id} = $_[3] if $_[0] == DBD::SQLite::INSERT; }); $self->emit(connection => $dbh); return $dbh; } sub _enqueue { my ($self, $dbh) = @_; if (my $parent = $self->parent) { return $parent->_enqueue($dbh) } my $queue = $self->{queue} ||= []; push @$queue, $dbh if $dbh->{Active}; shift @$queue while @$queue > $self->max_connections; } sub _prepare { my $self = shift; # Automatic migrations ++$self->{migrated} and $self->migrations->migrate if !$self->{migrated} && $self->auto_migrate; my $parent = $self->parent; return $parent ? $parent->_prepare : $self->_dequeue; } sub _tempfile { catfile(shift->{tempdir} = File::Temp->newdir, 'sqlite.db') } sub _url_from_file { my $url = URI::db->new; $url->engine('sqlite'); $url->dbname(shift); if (my $options = shift) { $url->query_form($options) } return $url; } 1; =head1 NAME Mojo::SQLite - A tiny Mojolicious wrapper for SQLite =head1 SYNOPSIS use Mojo::SQLite; # Select the library version my $sql = Mojo::SQLite->new('sqlite:test.db'); say $sql->db->query('select sqlite_version() as version')->hash->{version}; # Use migrations to create a table $sql->migrations->name('my_names_app')->from_string(<migrate; -- 1 up create table names (id integer primary key autoincrement, name text); -- 1 down drop table names; EOF # Use migrations to drop and recreate the table $sql->migrations->migrate(0)->migrate; # Get a database handle from the cache for multiple queries my $db = $sql->db; # Use SQL::Abstract to generate simple CRUD queries for you $db->insert('names', {name => 'Isabel'}); my $id = $db->select('names', ['id'], {name => 'Isabel'})->hash->{id}; $db->update('names', {name => 'Bel'}, {id => $id}); $db->delete('names', {name => 'Bel'}); # Insert a few rows in a transaction with SQL and placeholders eval { my $tx = $db->begin; $db->query('insert into names (name) values (?)', 'Sara'); $db->query('insert into names (name) values (?)', 'Stefan'); $tx->commit; }; say $@ if $@; # Insert another row with SQL::Abstract and return the generated id say $db->insert('names', {name => 'Daniel'})->last_insert_id; # JSON roundtrip say $db->query('select ? as foo', {json => {bar => 'baz'}}) ->expand(json => 'foo')->hash->{foo}{bar}; # Select one row at a time my $results = $db->query('select * from names'); while (my $next = $results->hash) { say $next->{name}; } # Select all rows with SQL::Abstract say $_->{name} for $db->select('names')->hashes->each; =head1 DESCRIPTION L is a tiny wrapper around L that makes L a lot of fun to use with the L real-time web framework. Use all L SQLite has to offer, generate CRUD queries from data structures, and manage your database schema with migrations. =head1 BASICS Database and statement handles are cached automatically, so they can be reused transparently to increase performance. And you can handle connection timeouts gracefully by holding on to them only for short amounts of time. use Mojolicious::Lite; use Mojo::SQLite; helper sqlite => sub { state $sql = Mojo::SQLite->new('sqlite:test.db') }; get '/' => sub ($c) { my $db = $c->sqlite->db; $c->render(json => $db->query(q{select datetime('now','localtime') as now})->hash); }; app->start; In this example application, we create a C helper to store a L object. Our action calls that helper and uses the method L to dequeue a L object from the connection pool. Then we use the method L to execute an L statement, which returns a L object. And finally we call the method L to retrieve the first row as a hash reference. All I/O and queries are performed synchronously, and SQLite's default journal mode only supports concurrent reads from multiple processes while the database is not being written. The "Write-Ahead Log" journal mode allows multiple processes to read and write concurrently to the same database file (but only one can write at a time). WAL mode is enabled by the C option, and persists when opening that same database in the future. # Performed concurrently (concurrent with writing only with WAL journaling mode) my $pid = fork || die $!; say $sql->db->query(q{select datetime('now','localtime') as time})->hash->{time}; exit unless $pid; C was enabled by default until L version v4.0.0; the C option (supported since version 2.002) can be specified to prevent it when creating new databases, but to disable it in an existing database the C must be set manually back to the default of C. See L and L for more information. # Disable WAL mode on a database where it was previously enabled $sql->db->query('PRAGMA journal_mode=DELETE'); The L is disabled for all connections since Mojo::SQLite 3.003; use single quotes for string literals and double quotes for identifiers, as is normally recommended. All cached database handles will be reset automatically if a new process has been forked, this allows multiple processes to share the same L object safely. Any database errors will throw an exception as C is automatically enabled, so use C or L to catch them. This makes transactions with L easy. While passing a file path of C<:memory:> (or a custom L with C) will create a temporary database, in-memory databases cannot be shared between connections, so subsequent calls to L may return connections to completely different databases. For a temporary database that can be shared between connections and processes, pass a file path of C<:temp:> to store the database in a temporary directory (this is the default), or consider constructing a temporary directory yourself with L if you need to reuse the filename. A temporary directory allows SQLite to create L safely. use File::Spec::Functions 'catfile'; use File::Temp; use Mojo::SQLite; my $tempdir = File::Temp->newdir; # Deleted when object goes out of scope my $tempfile = catfile $tempdir, 'test.db'; my $sql = Mojo::SQLite->new->from_filename($tempfile); =head1 EXAMPLES This distribution also contains a well-structured example L you can use for inspiration. This application shows how to apply the MVC design pattern in practice. =head1 EVENTS L inherits all events from L and can emit the following new ones. =head2 connection $sql->on(connection => sub ($sql, $dbh) { $dbh->do('pragma journal_size_limit=1000000'); }); Emitted when a new database connection has been established. =head1 ATTRIBUTES L implements the following attributes. =head2 abstract my $abstract = $sql->abstract; $sql = $sql->abstract(SQL::Abstract->new); L object used to generate CRUD queries for L, defaults to a L object with C set to C<.> and C set to C<">. # Generate WHERE clause and bind values my($stmt, @bind) = $sql->abstract->where({foo => 'bar', baz => 'yada'}); L provides additional features to the L query methods in L such as C<-json> and C/C. The C feature is not applicable to SQLite queries. $sql->db->select(['some_table', ['other_table', foo_id => 'id']], ['foo', [bar => 'baz'], \q{datetime('now') as dt}], {foo => 'value'}, {order_by => 'foo', limit => 10, offset => 5, group_by => ['foo'], having => {baz => 'value'}}); # Upsert supported since SQLite 3.24.0 $sql->db->insert('some_table', {name => $name, value => $value}, {on_conflict => [name => {value => \'"excluded"."value"'}]}); =head2 auto_migrate my $bool = $sql->auto_migrate; $sql = $sql->auto_migrate($bool); Automatically migrate to the latest database schema with L, as soon as L has been called for the first time. =head2 database_class my $class = $sql->database_class; $sql = $sql->database_class('MyApp::Database'); Class to be used by L, defaults to L. Note that this class needs to have already been loaded before L is called. =head2 dsn my $dsn = $sql->dsn; $sql = $sql->dsn('dbi:SQLite:uri=file:foo.db'); Data source name, defaults to C followed by a path to a temporary file. =head2 max_connections my $max = $sql->max_connections; $sql = $sql->max_connections(3); Maximum number of idle database handles to cache for future use, defaults to C<1>. =head2 migrations my $migrations = $sql->migrations; $sql = $sql->migrations(Mojo::SQLite::Migrations->new); L object you can use to change your database schema more easily. # Load migrations from file and migrate to latest version $sql->migrations->from_file('/home/dbook/migrations.sql')->migrate; =head2 options my $options = $sql->options; $sql = $sql->options({AutoCommit => 1, RaiseError => 1}); Options for database handles, defaults to setting C to C, setting C, C and C, and deactivating C. Note that C and C are considered mandatory, so deactivating them would be very dangerous. See L and L for more information on available options. =head2 parent my $parent = $sql->parent; $sql = $sql->parent(Mojo::SQLite->new); Another L object to use for connection management, instead of establishing and caching our own database connections. =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 new my $sql = Mojo::SQLite->new; my $sql = Mojo::SQLite->new('file:test.db); my $sql = Mojo::SQLite->new('sqlite:test.db'); my $sql = Mojo::SQLite->new(Mojo::SQLite->new); Construct a new L object and parse connection string with L if necessary. # Customize configuration further my $sql = Mojo::SQLite->new->dsn('dbi:SQLite:dbname=test.db'); my $sql = Mojo::SQLite->new->dsn('dbi:SQLite:uri=file:test.db?mode=memory'); # Pass filename directly my $sql = Mojo::SQLite->new->from_filename($filename); =head2 db my $db = $sql->db; Get a database object based on L (which is usually L) for a cached or newly established database connection. The L database handle will be automatically cached again when that object is destroyed, so you can handle problems like connection timeouts gracefully by holding on to it only for short amounts of time. # Add up all the money say $sql->db->select('accounts') ->hashes->reduce(sub { $a->{money} + $b->{money} }); =head2 from_filename $sql = $sql->from_filename('C:\\Documents and Settings\\foo & bar.db', $options); Parse database filename directly. Unlike L, the filename is parsed as a local filename and not a URL. A hashref of L may be passed as the second argument. # Absolute filename $sql->from_filename('/home/fred/data.db'); # Relative to current directory $sql->from_filename('data.db'); # Temporary file database (default) $sql->from_filename(':temp:'); # In-memory temporary database (single connection only) my $db = $sql->from_filename(':memory:')->db; # Additional options $sql->from_filename($filename, { PrintError => 1 }); # Readonly connection $sql->from_filename($filename, { ReadOnly => 1 }); # Strict unicode strings and WAL mode use DBD::SQLite::Constants ':dbd_sqlite_string_mode'; $sql->from_filename($filename, { sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_STRICT, wal_mode => 1 }); =head2 from_string $sql = $sql->from_string('test.db'); $sql = $sql->from_string('file:test.db'); $sql = $sql->from_string('file:///C:/foo/bar.db'); $sql = $sql->from_string('sqlite:C:%5Cfoo%5Cbar.db'); $sql = $sql->from_string(Mojo::SQLite->new); Parse configuration from connection string or use another L object as L. Connection strings are parsed as URLs, so you should construct them using a module like L, L, or L. For portability on non-Unix-like systems, either construct the URL with the C scheme, or use L to construct a URL with the C scheme. A URL with no scheme will be parsed as a C URL, and C URLs are parsed according to the current operating system. If specified, the hostname must be C. If the URL has a query string, it will be parsed and applied to L. # Absolute filename $sql->from_string('sqlite:////home/fred/data.db'); $sql->from_string('sqlite://localhost//home/fred/data.db'); $sql->from_string('sqlite:/home/fred/data.db'); $sql->from_string('file:///home/fred/data.db'); $sql->from_string('file://localhost/home/fred/data.db'); $sql->from_string('file:/home/fred/data.db'); $sql->from_string('///home/fred/data.db'); $sql->from_string('//localhost/home/fred/data.db'); $sql->from_string('/home/fred/data.db'); # Relative to current directory $sql->from_string('sqlite:data.db'); $sql->from_string('file:data.db'); $sql->from_string('data.db'); # Connection string must be a valid URL $sql->from_string(Mojo::URL->new->scheme('sqlite')->path($filename)); $sql->from_string(URI::db->new->Mojo::Base::tap(engine => 'sqlite')->Mojo::Base::tap(dbname => $filename)); $sql->from_string(URI::file->new($filename)); # Temporary file database (default) $sql->from_string(':temp:'); # In-memory temporary database (single connection only) my $db = $sql->from_string(':memory:')->db; # Additional options $sql->from_string('data.db?PrintError=1&sqlite_allow_multiple_statements=1'); $sql->from_string(Mojo::URL->new->scheme('sqlite')->path($filename)->query(sqlite_see_if_its_a_number => 1)); $sql->from_string(URI::file->new($filename)->Mojo::Base::tap(query_form => {PrintError => 1})); # Readonly connection with WAL mode $sql->from_string('data.db?ReadOnly=1&wal_mode=1'); # String unicode strings and WAL mode use DBD::SQLite::Constants ':dbd_sqlite_string_mode'; $sql->from_string(Mojo::URL->new->scheme('sqlite')->path('data.db') ->query(sqlite_string_mode => DBD_SQLITE_STRING_MODE_UNICODE_STRICT, wal_mode => 1)); =head1 DEBUGGING You can set the C environment variable to get some advanced diagnostics information printed by L. DBI_TRACE=1 DBI_TRACE=15 DBI_TRACE=SQL =head1 REFERENCE This is the class hierarchy of the L distribution. =over 2 =item * L =item * L =item * L =item * L =item * L =back =head1 BUGS Report any issues on the public bugtracker. =head1 AUTHOR Dan Book, C =head1 CREDITS Sebastian Riedel, author of L, which this distribution is based on. =head1 COPYRIGHT AND LICENSE Copyright 2015, Dan Book. This library is free software; you may redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 SEE ALSO L, L, L sqlite_lite_app.t100644001750001750 213415205022012 17621 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tuse Mojo::Base -strict; BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } use Test::More; use Mojo::SQLite; use Mojolicious::Lite; use Scalar::Util 'refaddr'; use Test::Mojo; helper sqlite => sub { state $sql = Mojo::SQLite->new }; app->sqlite->db->query('create table if not exists app_test (stuff text)'); app->sqlite->db->query('insert into app_test values (?)', 'I ♥ Mojolicious!'); get '/blocking' => sub { my $c = shift; my $db = $c->sqlite->db; $c->res->headers->header('X-Ref' => refaddr $db->dbh); $c->render(text => $db->query('select * from app_test')->hash->{stuff}); }; my $t = Test::Mojo->new; subtest 'Make sure migrations are not served as static files' => sub { $t->get_ok('/app_test')->status_is(404); }; subtest 'Blocking select (with connection reuse)' => sub { $t->get_ok('/blocking')->status_is(200)->content_is('I ♥ Mojolicious!'); my $ref = $t->tx->res->headers->header('X-Ref'); $t->get_ok('/blocking')->status_is(200)->header_is('X-Ref', $ref) ->content_is('I ♥ Mojolicious!'); $t->app->sqlite->db->query('drop table app_test'); }; done_testing(); 00-report-prereqs.t100644001750001750 1362515205022012 17701 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/t#!perl use strict; use warnings; # This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.029 use Test::More tests => 1; use Module::Metadata; use File::Spec; # from $version::LAX my $lax_version_re = qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )? | (?:\.[0-9]+) (?:_[0-9]+)? ) | (?: v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )? | (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)? ) )/x; # hide optional CPAN::Meta modules from prereq scanner # and check if they are available my $cpan_meta = "CPAN::Meta"; my $cpan_meta_pre = "CPAN::Meta::Prereqs"; my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic # Verify requirements? my $DO_VERIFY_PREREQS = 1; sub _max { my $max = shift; $max = ( $_ > $max ) ? $_ : $max for @_; return $max; } sub _merge_prereqs { my ($collector, $prereqs) = @_; # CPAN::Meta::Prereqs object if (ref $collector eq $cpan_meta_pre) { return $collector->with_merged_prereqs( CPAN::Meta::Prereqs->new( $prereqs ) ); } # Raw hashrefs for my $phase ( keys %$prereqs ) { for my $type ( keys %{ $prereqs->{$phase} } ) { for my $module ( keys %{ $prereqs->{$phase}{$type} } ) { $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module}; } } } return $collector; } my @include = qw( ); my @exclude = qw( ); # Add static prereqs to the included modules list my $static_prereqs = do './t/00-report-prereqs.dd'; # Merge all prereqs (either with ::Prereqs or a hashref) my $full_prereqs = _merge_prereqs( ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ), $static_prereqs ); # Add dynamic prereqs to the included modules list (if we can) my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; my $cpan_meta_error; if ( $source && $HAS_CPAN_META && (my $meta = eval { CPAN::Meta->load_file($source) } ) ) { $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs); } else { $cpan_meta_error = $@; # capture error from CPAN::Meta->load_file($source) $source = 'static metadata'; } my @full_reports; my @dep_errors; my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs; # Add static includes into a fake section for my $mod (@include) { $req_hash->{other}{modules}{$mod} = 0; } for my $phase ( qw(configure build test runtime develop other) ) { next unless $req_hash->{$phase}; next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING}); for my $type ( qw(requires recommends suggests conflicts modules) ) { next unless $req_hash->{$phase}{$type}; my $title = ucfirst($phase).' '.ucfirst($type); my @reports = [qw/Module Want Have/]; for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) { next if grep { $_ eq $mod } @exclude; my $want = $req_hash->{$phase}{$type}{$mod}; $want = "undef" unless defined $want; $want = "any" if !$want && $want == 0; if ($mod eq 'perl') { push @reports, ['perl', $want, $]]; next; } my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required"; my $file = $mod; $file =~ s{::}{/}g; $file .= ".pm"; my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC; if ($prefix) { my $have = Module::Metadata->new_from_file( File::Spec->catfile($prefix, $file) )->version; $have = "undef" unless defined $have; push @reports, [$mod, $want, $have]; if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) { if ( $have !~ /\A$lax_version_re\z/ ) { push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)"; } elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) { push @dep_errors, "$mod version '$have' is not in required range '$want'"; } } } else { push @reports, [$mod, $want, "missing"]; if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) { push @dep_errors, "$mod is not installed ($req_string)"; } } } if ( @reports ) { push @full_reports, "=== $title ===\n\n"; my $ml = _max( map { length $_->[0] } @reports ); my $wl = _max( map { length $_->[1] } @reports ); my $hl = _max( map { length $_->[2] } @reports ); if ($type eq 'modules') { splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl]; push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports; } else { splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl]; push @full_reports, map { sprintf(" %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports; } push @full_reports, "\n"; } } } if ( @full_reports ) { diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports; } if ( $cpan_meta_error || @dep_errors ) { diag "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n"; } if ( $cpan_meta_error ) { my ($orig_source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; diag "\nCPAN::Meta->load_file('$orig_source') failed with: $cpan_meta_error\n"; } if ( @dep_errors ) { diag join("\n", "\nThe following REQUIRED prerequisites were not satisfied:\n", @dep_errors, "\n" ); } pass('Reported prereqs'); # vim: ts=4 sts=4 sw=4 et: migrations000755001750001750 015205022012 16272 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/ttest.sql100644001750001750 41215205022012 20107 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/t/migrations-- 1 up create table if not exists migration_test_three (baz text); -- 1 down drop table if exists migration_test_three; -- 2 up insert into migration_test_three values ('just'); insert into migration_test_three values ('works ♥'); -- 3 up -- 4 up does_not_exist; author000755001750001750 015205022012 15610 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/xtpod-syntax.t100644001750001750 25115205022012 20221 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/xt/author#!perl # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); 00-report-prereqs.dd100644001750001750 344415205022012 20003 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/tdo { my $x = { 'configure' => { 'requires' => { 'Module::Build::Tiny' => '0.034' } }, 'develop' => { 'requires' => { 'Pod::Coverage::TrustPod' => '0', 'Test::Pod' => '1.41', 'Test::Pod::Coverage' => '1.08' } }, 'runtime' => { 'requires' => { 'Carp' => '0', 'DBD::SQLite' => '1.68', 'DBI' => '1.627', 'File::Spec::Functions' => '0', 'File::Temp' => '0', 'Mojolicious' => '8.03', 'SQL::Abstract::Pg' => '1.0', 'Scalar::Util' => '0', 'URI' => '1.69', 'URI::db' => '0.15', 'URI::file' => '4.21', 'perl' => '5.010001' } }, 'test' => { 'recommends' => { 'CPAN::Meta' => '2.120900' }, 'requires' => { 'File::Spec' => '0', 'Module::Metadata' => '0', 'Test::More' => '0.96' } } }; $x; }t000755001750001750 015205022012 16657 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blogblog.t100644001750001750 346115205022012 20133 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blog/tuse Mojo::Base -strict; use Test::More; use Mojo::SQLite; use Mojo::URL; use Test::Mojo; # Override configuration for testing my $t = Test::Mojo->new(Blog => {sqlite => ':temp:', secrets => ['test_s3cret']}); $t->ua->max_redirects(10); # No posts yet $t->get_ok('/')->status_is(200)->text_is('title' => 'Blog') ->text_is('body > a' => 'New post')->element_exists_not('h2'); # Create a new post $t->get_ok('/posts/create')->status_is(200)->text_is('title' => 'New post') ->element_exists('form input[name=title]') ->element_exists('form textarea[name=body]'); $t->post_ok('/posts' => form => {title => 'Testing', body => 'This is a test.'}) ->status_is(200)->text_is('title' => 'Testing')->text_is('h2' => 'Testing') ->text_like('p' => qr/This is a test/); # Read the post $t->get_ok('/')->status_is(200)->text_is('title' => 'Blog') ->text_is('h2 a' => 'Testing')->text_like('p' => qr/This is a test/); $t->get_ok('/posts/1')->status_is(200)->text_is('title' => 'Testing') ->text_is('h2' => 'Testing')->text_like('p' => qr/This is a test/) ->text_is('body > a' => 'Edit'); # Update the post $t->get_ok('/posts/1/edit')->status_is(200)->text_is('title' => 'Edit post') ->element_exists('form input[name=title][value=Testing]') ->text_like('form textarea[name=body]' => qr/This is a test/) ->element_exists('form input[value=Remove]'); $t->post_ok( '/posts/1?_method=PUT' => form => {title => 'Again', body => 'It works.'}) ->status_is(200)->text_is('title' => 'Again')->text_is('h2' => 'Again') ->text_like('p' => qr/It works/); $t->get_ok('/posts/1')->status_is(200)->text_is('title' => 'Again') ->text_is('h2' => 'Again')->text_like('p' => qr/It works/); # Delete the post $t->post_ok('/posts/1?_method=DELETE')->status_is(200) ->text_is('title' => 'Blog')->element_exists_not('h2'); done_testing(); blog000755001750001750 015205022012 16414 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examplesblog.conf100644001750001750 6515205022012 20307 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blog{ sqlite => 'test.db', secrets => ['s3cret'], } pod-coverage.t100644001750001750 36415205022012 20473 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/xt/author#!perl # This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests use strict; use warnings; use Test::Pod::Coverage 1.08; use Pod::Coverage::TrustPod; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); lib000755001750001750 015205022012 17162 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blogBlog.pm100644001750001750 217715205022012 20552 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blog/libpackage Blog; use Mojo::Base 'Mojolicious'; use Blog::Model::Posts; use Mojo::SQLite; sub startup { my $self = shift; # Configuration $self->plugin('Config'); $self->secrets($self->config('secrets')); # Model $self->helper(sqlite => sub { state $sql = Mojo::SQLite->new->from_filename(shift->config('sqlite')) }); $self->helper( posts => sub { state $posts = Blog::Model::Posts->new(sqlite => shift->sqlite) }); # Migrate to latest version if necessary my $path = $self->home->child('migrations', 'blog.sql'); $self->sqlite->auto_migrate(1)->migrations->name('blog')->from_file($path); # Controller my $r = $self->routes; $r->get('/' => sub { shift->redirect_to('posts') }); $r->get('/posts')->to('posts#index'); $r->get('/posts/create')->to('posts#create')->name('create_post'); $r->post('/posts')->to('posts#store')->name('store_post'); $r->get('/posts/:id')->to('posts#show')->name('show_post'); $r->get('/posts/:id/edit')->to('posts#edit')->name('edit_post'); $r->put('/posts/:id')->to('posts#update')->name('update_post'); $r->delete('/posts/:id')->to('posts#remove')->name('remove_post'); } 1; script000755001750001750 015205022012 17720 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blogblog100755001750001750 34515205022012 20713 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/examples/blog/script#!/usr/bin/env perl use strict; use warnings; use FindBin; BEGIN { unshift @INC, "$FindBin::Bin/../lib" } # Start command line interface for application require Mojolicious::Commands; Mojolicious::Commands->start_app('Blog'); SQLite000755001750001750 015205022012 16466 5ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/lib/MojoPubSub.pm100644001750001750 146115205022012 20366 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/lib/Mojo/SQLitepackage Mojo::SQLite::PubSub; use Mojo::Base -strict; use Mojo::Util 'deprecated'; our $VERSION = 'v4.0.0'; deprecated 'Mojo::SQLite::PubSub is deprecated and should no longer be used'; 1; =encoding utf8 =head1 NAME Mojo::SQLite::PubSub - (DEPRECATED) Publish/Subscribe =head1 DESCRIPTION L is DEPRECATED and now an empty package. It was originally written as a toy following the API of L, but as SQLite is serverless and has no ability to notify clients, it is not possible to implement an efficient pubsub system as in for example PostgreSQL, Redis, or websockets. Consider instead using the pubsub facilities of L, L, or L. =head1 SEE ALSO L, L, L =for Pod::Coverage *EVERYTHING* Results.pm100644001750001750 1251415205022012 20650 0ustar00grinnzgrinnz000000000000Mojo-SQLite-v4.0.0/lib/Mojo/SQLitepackage Mojo::SQLite::Results; use Mojo::Base -base; use Mojo::Collection; use Mojo::JSON 'from_json'; use Mojo::Util 'tablify'; our $VERSION = 'v4.0.0'; has [qw(db sth)]; sub new { my $self = shift->SUPER::new(@_); ($self->{sth}{private_mojo_refcount} //= 0)++; return $self; } sub DESTROY { my $self = shift; return() unless my $sth = $self->{sth}; $sth->finish unless --$sth->{private_mojo_refcount}; } sub array { ($_[0]->_expand($_[0]->sth->fetchrow_arrayref))[0] } sub arrays { _collect($_[0]->_expand(@{$_[0]->sth->fetchall_arrayref})) } sub columns { shift->sth->{NAME} } sub expand { my ($self, %expands) = @_; for my $type (keys %expands) { my @cols = ref $expands{$type} eq 'ARRAY' ? @{$expands{$type}} : $expands{$type}; ++$self->{expand}{$type}{$_} for @cols; } return $self; } sub finish { shift->sth->finish } sub hash { ($_[0]->_expand($_[0]->sth->fetchrow_hashref))[0] } sub hashes { _collect($_[0]->_expand(@{$_[0]->sth->fetchall_arrayref({})})) } sub last_insert_id { shift->{last_insert_id} // 0 } sub rows { shift->sth->rows } sub text { tablify shift->arrays } sub _collect { Mojo::Collection->new(@_) } sub _expand { my ($self, @rows) = @_; return @rows unless $self->{expand} and $rows[0]; if (ref $rows[0] eq 'HASH') { my @json_names = keys %{$self->{expand}{json}}; for my $r (@rows) { $r->{$_} = from_json $r->{$_} for grep { $r->{$_} } @json_names } } else { my $cols = $self->columns; my @json_idxs = grep { $self->{expand}{json}{$cols->[$_]} } 0..$#$cols; for my $r (@rows) { $r->[$_] = from_json $r->[$_] for grep { $r->[$_] } @json_idxs } } return @rows; } 1; =head1 NAME Mojo::SQLite::Results - Results =head1 SYNOPSIS use Mojo::SQLite::Results; my $results = Mojo::SQLite::Results->new(sth => $sth); $results->hashes->map(sub { $_->{foo} })->shuffle->join("\n")->say; =head1 DESCRIPTION L is a container for L statement handles used by L. =head1 ATTRIBUTES L implements the following attributes. =head2 db my $db = $results->db; $results = $results->db(Mojo::SQLite::Database->new); L object these results belong to. =head2 sth my $sth = $results->sth; $results = $results->sth($sth); L statement handle results are fetched from. =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 new my $results = Mojo::SQLite::Results->new; my $results = Mojo::SQLite::Results->new(sth => $sth); my $results = Mojo::SQLite::Results->new({sth => $sth}); Construct a new L object. =head2 array my $array = $results->array; Fetch next row from L and return it as an array reference. Note that L needs to be called if you are not fetching all the possible rows. # Process one row at a time while (my $next = $results->array) { say $next->[3]; } =head2 arrays my $collection = $results->arrays; Fetch all rows from L and return them as a L object containing array references. # Process all rows at once say $results->arrays->reduce(sub { $a + $b->[3] }, 0); =head2 columns my $columns = $results->columns; Return column names as an array reference. # Names of all columns say for @{$results->columns}; =head2 expand $results = $results->expand(json => 'some_json'); $results = $results->expand(json => ['some_json','other_json']); Decode specified fields from a particular format to Perl values for all rows. Currently only the C text format is recognized. The names must exactly match the column names as returned by L; it is recommended to use explicit aliases in the query for consistent column names. # Expand JSON $results->expand(json => 'json_field')->hashes->map(sub { $_->{foo}{bar} })->join("\n")->say; =head2 finish $results->finish; Indicate that you are finished with L and will not be fetching all the remaining rows. =head2 hash my $hash = $results->hash; Fetch next row from L and return it as a hash reference. Note that L needs to be called if you are not fetching all the possible rows. # Process one row at a time while (my $next = $results->hash) { say $next->{money}; } =head2 hashes my $collection = $results->hashes; Fetch all rows from L and return them as a L object containing hash references. # Process all rows at once say $results->hashes->reduce(sub { $a + $b->{money} }, 0); =head2 last_insert_id my $id = $results->last_insert_id; Returns the L of the most recent successful C. =head2 rows my $num = $results->rows; Number of rows. Note that for C statement with L (usually an L object) and execute it with L. You can also append a callback for API compatibility with L; the query is still executed in a blocking manner. $db->select(some_table => ['foo'] => {bar => 'yada'} => sub ($db, $err, $results) { ... }); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; Use all the same argument variations you would pass to the C