--- /dev/null
+package DBIx::Data::Store::CRUD;
+
+use strictures 1;
+
+sub new {
+ my $proto = shift;
+ bless({ %{$_[0]} }, ref($proto)||$proto);
+}
+
+sub _sql { shift->{sql} }
+sub _raw { shift->{raw} }
+
+sub _run {
+ my $self = shift;
+ my ($run_type, $sql_type, @args) = @_;
+ my $sql = $self->_sql->{$sql_type}||die "No such sql type ${sql_type}";
+ $self->_raw->${\"run_${run_type}"}($sql, @args);
+}
+
+sub flatten { @{shift->_run('rowset', 'select_all', @_)} }
+
+sub to_stream { shift->_run('rowstream', 'select_all', @_) }
+
+sub clear { shift->_run('row','delete_all',@_) }
+
+sub get { shift->_run('row','select_one',@_) }
+
+sub replace {
+ my $self = shift;
+ $self->_run('row','update_one', [ @{$_[1]}, @{$_[0]} ]);
+}
+
+sub add { shift->_run('row','insert_one',@_) }
+sub remove { shift->_run('row','delete_one',@_) }
+
+1;
--- /dev/null
+package DBIx::Data::Store::Raw;
+
+use strictures 1;
+
+use DBIx::Connector;
+
+sub new {
+ my $proto = shift;
+ bless({ %{$_[0]} }, ref($proto)||$proto);
+}
+
+sub connect {
+ my $class = shift;
+ $class->new({ connect_info => [ @_ ] });
+}
+
+sub connect_info { shift->{connect_info} }
+
+sub _connector { $_[0]->{_connector} ||= $_[0]->_build__connector }
+
+sub _build__connector {
+ DBIx::Connector->new(@{$_[0]->connect_info});
+}
+
+sub run_row { shift->_exec_calling('fetchrow_arrayref', @_) }
+
+sub run_rowset { shift->_exec_calling('fetchall_arrayref', @_) }
+
+sub run_rowstream {
+ shift->_exec_calling(sub {
+ DBIx::Data::Stream::STH->new({ sth => $_[0] })
+ }, @_);
+}
+
+sub _exec_calling {
+ my ($self, $call, @sth_args) = @_;
+ $self->_exec(sub {
+ $self->_sth_for($_[0], @sth_args)->$call
+ });
+}
+
+sub _exec {
+ $_[0]->_connector->run($_[1]);
+}
+
+sub _sth_for {
+ my ($self, $dbh, $sql, $args) = @_;
+ my $sth = $dbh->prepare_cached($sql, {}, 3);
+ $sth->execute(@$args);
+ return $sth;
+}
+
+1;
--- /dev/null
+use strictures 1;
+use Test::More;
+
+use DBIx::Data::Store::CRUD;
+use DBIx::Data::Store::Raw;
+
+use DBI;
+use Scalar::Util qw(refaddr);
+
+sub sort_set {
+ sort { $a->{name} cmp $b->{name} } @_
+}
+
+my $dsn = 'dbi:SQLite:tmp.db';
+
+sub setup_dbh {
+ unlink('tmp.db');
+ return DBI->connect($dsn, undef, undef, { RaiseError => 1 })
+}
+
+sub setup_db {
+ my $dbh = setup_dbh;
+ $dbh->do(q{
+ CREATE TABLE names (
+ id INTEGER NOT NULL PRIMARY KEY,
+ name VARCHAR(255) NOT NULL
+ )
+ });
+ #my $pop = $dbh->prepare(q{INSERT INTO person (name) VALUES (?)});
+ #my @names = qw(Joe Jim Bob Pterry);
+ #$pop->execute($_) for @names;
+ #return sort_set do {
+ # my $id = 0; map +{ id => ++$id, name => $_ }, @names
+ #};
+}
+
+my $db_store = DBIx::Data::Store::Raw->connect($dsn);
+
+sub raw_store { $db_store }
+
+sub make_store {
+ my ($crud) = @_;
+ DBIx::Data::Store::CRUD->new({
+ raw => $db_store,
+ sql => {
+ select_all => 'SELECT id, name FROM names',
+ delete_all => 'DELETE FROM names',
+ select_one => 'SELECT id, name FROM names WHERE id = ?',
+ insert_one => 'INSERT INTO names (name) VALUES (?)',# RETURNING (id)',
+ update_one => 'UPDATE names SET name = ? WHERE id = ?',
+ delete_one => 'DELETE FROM names WHERE id = ?',
+ },
+ });
+}
+
+setup_db;
+
+my $store = make_store;
+
+is_deeply([$store->flatten], [], 'Empty set');
+
+is_deeply($store->add(['Bob']), [1], 'Add record');
+
+is_deeply([$store->flatten], [[1,'Bob']], 'One member');
+
+is_deeply($store->get([1]), [1,'Bob'], 'Retrieve by key');
+
+$store->replace([1],['Robert']);
+
+is_deeply([$store->flatten], [[1,'Robert']], 'Name changed (all)');
+
+is_deeply($store->get([1]), [1,'Robert'], 'Retrieve by key');
+
+$store->add([$_]) for qw(Joe James Jim);
+
+my $flatsort = sub {
+ [ sort { $a->[0] <=> $b->[0] } $store->flatten ]
+};
+
+is_deeply($flatsort->(), [
+ [1,'Robert'],[2,'Joe'],[3,'James'],[4,'Jim']
+], 'Four members');
+
+is_deeply($store->get([3]),[3,'James'], 'Retrieve by key');
+
+$store->remove([3]);
+
+is_deeply($flatsort->(), [
+ [1,'Robert'],[2,'Joe'],[4,'Jim']
+], 'Three members left');
+
+done_testing;