[![Actions Status](https://github.com/nukopian/DBIx-Squirrel/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/nukopian/DBIx-Squirrel/actions?workflow=test) [![CPAN](https://img.shields.io/cpan/v/DBIx-Squirrel)](https://metacpan.org/release/DBIx-Squirrel) [![Kwalitee](https://cpants.cpanauthors.org/dist/DBIx-Squirrel.svg)](https://cpants.cpanauthors.org/dist/DBIx-Squirrel) # DBIx-Squirrel DBI, minus the boilerplate, plus a pipeline. With DBIx::Squirrel, you still write SQL, as you do with the DBI. It takes away the friction around it: placeholders in whatever style you like, iterators over results, and a pipeline of stages written beside the query. It never hides the SQL, and it isn't an ORM. ```perl my @names = $dbh->iterate( 'SELECT name FROM track WHERE plays > :plays ORDER BY plays DESC', plays => 6000, sub { $_->[0] }, # each row's name sub { uc }, # in capitals )->all; ``` It's a subclass of the DBI, so everything the DBI does still works. Where the two do the same thing, they give the same result. ## Why use it The DBI is the right foundation, and it's low-level on purpose. Most code written around it does the same few chores, by hand, each time. DBIx::Squirrel does them for you. - **Placeholders you can read.** With `?`, values go in the order the marks appear. A value used twice is passed twice, and changing the SQL can mean changing the code that calls it. Named placeholders bind by name, with each value given once. They work on any driver, whatever placeholders it supports itself, so SQL written with PostgreSQL's `$1` runs unchanged on SQLite. Values that don't match the placeholders croak, rather than being bound in the wrong place. - **Rows that come out ready to use.** There's no fetch loop to write, and no array to push onto. The stages that map, filter and expand the rows sit beside the query. You can read what a result holds next to the SQL that makes it. Rows are fetched in batches, so you can work through a large result a row at a time without holding all of it in memory. Ready-made stages do the common jobs, such as taking the first few rows, picking out a column, or handing rows out in batches. - **Queries with names.** A statement and its stages can become a function with a name of its own. The SQL is written in one place, and the code that uses it reads as plainly as `titles_by 2`. - **Nothing to unlearn, and nothing to undo.** Your handles, attributes, drivers and existing code all still work, because it's still the DBI. Use as much as helps, and stop there. It isn't an ORM. You write the SQL, so everything your database can do is still there, and it never runs a query you didn't write. ## One step at a time You can take it up a step at a time, and stop at any step. Each works on its own, and code written for the DBI keeps working at every one. ### 1. Connect with it. ```perl use DBIx::Squirrel; my $dbh = DBIx::Squirrel->connect('dbi:SQLite:dbname=music.db', '', ''); ``` Your DBI code runs as it did. `connect` and `prepare` croak if they fail, so there's no `or die` to write. ### 2. Write placeholders in the style you like. ```perl my $sth = $dbh->prepare('SELECT title FROM album WHERE artist_id = :artist AND year > :year'); $sth->execute(artist => 2, year => 1994); my $album = $dbh->selectrow_hashref('SELECT * FROM album WHERE id = $1', 2); ``` `?`, `?1`, `:1`, `$1`, `:name` and `$name` are each rewritten as `?` for the DBI. A numbered or named placeholder takes one value, wherever it appears. `do` and the select methods need no `undef` before the values. ```perl my $of_years = $dbh->selectall_arrayref('SELECT * FROM album WHERE year IN (...:years)', years => [1967, 1994]); ``` A placeholder with `...` before it takes a list. It's written as a `?` for each value in the array reference given. ### 3. Iterate over the results. ```perl my $albums = $dbh->iterate('SELECT title, year FROM album ORDER BY year'); while (my($row) = $albums->next) { say "$row->[0] ($row->[1])"; } ``` Rows are fetched in batches, and the statement is finished when they run out. `all` takes the rest at once, nearly as quickly as plain DBI. `first` takes the next one, and `single` the only one, warning if there's another. ### 4. Add stages. ```perl my @hits = $dbh->iterate( 'SELECT name, plays FROM track ORDER BY plays DESC', sub { $_->[1] >= 5000 ? $_ : () }, # leave out the rest sub { "$_->[0] ($_->[1])" }, # make each a string )->all; ``` Each row goes through the stages, given as `$_`. A stage returns nothing to drop the row, one value in its place, or several. It can also stop the iteration early. A stage made with `gather` can hold rows, and give them out at the end, in batches or as one value. `pipe` runs anything else through stages the same way. Ready-made stages come with it, for jobs that come up again and again. They're in four modules: `take` and `drop` in [DBIx::Squirrel::Stages::Flow](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AStages%3A%3AFlow), with `take_if`, `drop_if`, `take_while` and more on request, `pluck` and `as_hash` in [DBIx::Squirrel::Stages::Rows](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AStages%3A%3ARows), `batch` and `collect` in [DBIx::Squirrel::Stages::Batch](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AStages%3A%3ABatch), and `peek` and `count_into` in [DBIx::Squirrel::Stages::Taps](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AStages%3A%3ATaps). A sink, given last, says what the call gives in place of an iterator: `keep_all` every result, `count_items` how many, `keep_first` the first, and others. They're in [Ekorn::Pipeline::Sinks](https://metacpan.org/pod/Ekorn%3A%3APipeline%3A%3ASinks). [DBIx::Squirrel::Tooling](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3ATooling) exports every stage, sink and word from one module, by tags, as `:stages` and `:sinks`, or `:all`: ```perl use DBIx::Squirrel::Tooling qw(:all); my @top_ten = $dbh->iterate( 'SELECT name, plays FROM track ORDER BY plays DESC', take_if { $_->[1] >= 5000 }, # leave out the rest pluck('name'), take(10), keep_all, ); my $hits = $dbh->iterate('SELECT * FROM track WHERE plays > ?', 6000, count_items); ``` Where [Ekorn::Pipeline::Operators](https://metacpan.org/pod/Ekorn%3A%3APipeline%3A%3AOperators) is in scope, `|` chains them too, as in `$it | count_items`. The reference says how to write your own stages to work alongside them. The pipeline is [Ekorn::Pipeline](https://github.com/nukopian/Ekorn-Pipeline). It began here, and is now a distribution of its own, so that any function can take stages from its caller. DBIx::Squirrel uses it as it is, and keeps its own names for its modules of functions, such as `DBIx::Squirrel::Stop`. The stages that need a database, such as `pluck`, are DBIx::Squirrel's own. ### 5. Have rows as objects. ```perl my $album = $dbh->results('SELECT * FROM album WHERE id = :id', id => 2)->single; say $album->title, ' (', $album->year, ')'; ``` Each row has a method for each column, and stages can work on the objects. ### 6. Give your databases and statements names. ```perl use DBIx::Squirrel::Tooling qw(keep_all); use DBIx::Squirrel::Sugar database => {music => ['dbi:SQLite:dbname=music.db', '', '']}, statement => { titles_by => [music => 'SELECT title FROM album WHERE artist_id = ?', sub { $_->[0] }], }; my @titles = titles_by 2, keep_all; ``` [DBIx::Squirrel::Sugar](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3ASugar) makes each a function with its own name. A statement's stages are declared beside its SQL. Called, it gives an iterator over the results, or, with a sink last, the sink's result. ## Documentation [The reference](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AManual%3A%3AReference) describes every feature in detail, with examples. [The user guide](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AManual%3A%3AHowTo) is a tour of the package, a layer at a time, from the DBI underneath to the shortcuts on top, with recipes that work. Each module's POD, starting with [DBIx::Squirrel](https://metacpan.org/pod/DBIx%3A%3ASquirrel), says the same more briefly. ## Installing ``` cpanm DBIx::Squirrel ``` It needs Perl 5.36 or later. ## Upgrading This is version 3. It builds on Ekorn::Pipeline, and makes one rule of what a call with stages gives: an iterator, unless a sink given last says otherwise. Most code written for 2.x runs as it did, and [Upgrading from 2.x](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AManual%3A%3AReference#Upgrading-from-2.x), in the reference, lists what changes. Version 2 was a rewrite. Code written for 1.x needs changes to run, and [Upgrading from 1.x](https://metacpan.org/pod/DBIx%3A%3ASquirrel%3A%3AManual%3A%3AReference#Upgrading-from-1.x) lists them. To stay on 1.x, which also runs on older Perls, install its last version: ``` cpanm DBIx::Squirrel@1.6.5 ``` Its code is in the archived [DBIx-Squirrel-1](https://github.com/nukopian/DBIx-Squirrel-1) repository. The repository was called DBIx-Squirrel-2 while version 2 was in development. It took the original name, DBIx-Squirrel, before its first release. ## Licence Copyright (c) 2024-2026 by Iain Campbell. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. See the file `LICENSE`. ## Acknowledgements Designed and created by Iain Campbell, in collaboration with Claude, Anthropic's AI model.