Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
WIP/RFC: A new DataSet interface for DBIish
See added t/50-DataSet.t for details
  • Loading branch information
salortiz committed Mar 28, 2016
1 parent 07226c7 commit 1ed8466
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/DBDish/Connection.pm6
Expand Up @@ -40,6 +40,7 @@ method drv { $.parent }
method new(*%args) {
my \con = ::?CLASS.bless(|%args);
con.reset-err;
$*DBDDEFS<con> = con if %args<Set-Default>:exists;
%args<parent>.Connections{con.WHICH} = con;
}

Expand Down
7 changes: 6 additions & 1 deletion lib/DBDish/StatementHandle.pm6
Expand Up @@ -26,7 +26,7 @@ has @!column-type;
# My defined interface
method execute(*@ --> IntTrue) { ... }
method finish(--> Bool) { ... }
method _row(--> Array) { ... }
method _row(--> List) { ... }
method _free() { ... }

method !ftr() {
Expand Down Expand Up @@ -115,6 +115,11 @@ multi method allrows() {
}
}

method CALL-ME(*@args) {
self.execute(|@args);
::('DBIish::DataSet').new(:sth(self));
}

# Legacy
method fetchrow {
if my \r = self._row {
Expand Down
68 changes: 68 additions & 0 deletions lib/DBIish/DataSet.pm6
@@ -0,0 +1,68 @@
use v6;
unit package DBIish;
use MONKEY-TYPING;

Rakudo::Internals.REGISTER-DYNAMIC: '$*DBDDEFS', {
PROCESS::<$DBDDEFS> = Hash.new;
};

need DBDish;
class Row does Positional does Associative is export {
has $!row is required handles <list Array elems Numeric Int AT-POS EXISTS-POS>;
has $!colmap is required;
has $.idx;
has $!pds;

submethod BUILD(:$!row, :$!colmap, :$!idx) { }

method AT-KEY(Row:D: $key) {
$!row[$_] with $!colmap{$key};
}

method hash(Row:D:) {
state % = $!colmap.map: { (.key => $!row[.value]) };
}

method keys { self.hash.keys }

method of {
Any
}
method gist(::?CLASS:D:) {
self.^name ~ "[$!idx]" ~ $!row.gist;
}
}

class DataSet does Iterable is export {
has $!ri;
has $!colmap;
has $.current = -1;
has $.is-empty = False;

submethod BUILD(:$sth) {
$!ri = (gather {
while $sth._row -> \r { take r; }
}).iterator;
$!colmap = Map.new($sth.column-names Z=> (0 ... *));
}

method iterator() {
(gather {
my $row;
until ($row := $!ri.pull-one) =:= IterationEnd {
take Row.new(:$row, :$!colmap, :idx(++$!current));
}
$!is-empty = True;
}).iterator;
}

method Seq() { Seq.new(self.iterator); }

method list() { List.from-iterator(self.iterator); }
}

augment class Str {
method SQL(Str:D: DBDish::Connection $dbh = $*DBDDEFS<con>) {
$dbh.prepare(self);
}
}
166 changes: 166 additions & 0 deletions t/50-DataSet.t
@@ -0,0 +1,166 @@
use v6;
use Test;
use DBIish;
use DBIish::DataSet;

plan 48;

my %con-parms = :database<dbdishtest>, :user<testuser>, :password<testpass>;
my ($dbhP, $dbhM);

try {
$dbhM = DBIish.connect('mysql', |%con-parms),
$dbhP = DBIish.connect('Pg', |%con-parms, :user<postgres>) :Set-Default;
CATCH {
when X::DBIish::LibraryMissing | X::DBDish::ConnectionFailed {
diag "$_\nCan't continue.";
}
default { .throw; }
}
}
unless $dbhP && $dbhM {
skip-rest 'prerequisites failed';
exit;
}

for ($dbhP, $dbhM) {
.do('DROP TABLE IF EXISTS test');
.do('CREATE TABLE test (id int, name varchar(35))');
}

my $insert = "INSERT INTO test(id, name) VALUES (?, ?)";

# Ready
{
with $insert.SQL {
ok .(0, 'First'), 'Insert one';
ok .(100, 'Last'), 'Insert one';
.dispose;
}
}

with "SELECT * FROM test".SQL -> $S1 {
ok $S1, "SELECT defined";

my $ds;
lives-ok {
$ds = $S1();
}, 'Statement can be called';

ok $ds, 'DataSet created';
isa-ok $ds, DataSet;

# An important thing!
does-ok $ds, Iterable;

# I'm testing the internals, so need to go slowly.
is $ds.current, -1, 'At start';
nok $ds.is-empty, 'Not empty';

ok (my $L = $ds.list), 'Can get list';
isa-ok $L, List;

with $L[0] -> $/ {
pass "Defined";

# This List is constructed from the iterator, so DS and List are entangled
# until reified
is $ds.current, 0, 'one taken';

isa-ok $/, Row;
# Testing its properties
does-ok $/, Positional;
does-ok $/, Associative;
# Numeric forms
is +$/, 2, "A row with 2 elems";
is $/.Int, 2;
is $/.elems, 2;
# hash semantic
is %(), { id => 0, name => 'First' }, 'as Hash';
is $/.keys, <id name>, 'ordered keys';
# list semantic
is $/.list, (0, 'First'), 'as List';
is $/.values, (0, 'First'), 'also';
# array semantic
is @(), [ 0, 'First' ], 'as Array';
# Positionals
is $0, 0, "By pos(\$0): $0";
is $1, 'First', "By pos(\$1); $1";
# Named
is $<id>, 0, "By name 'id': $<id>";
is $<name>, 'First', "By name 'name' $<name>";
# As Capture
is \(|$/), \(0, 'First'), 'Positional Capture';
# TODO This is broken somehow
#is \(|%$/), \(:id(0), :name('First')), 'Named Capture';
is $/.idx, 0, 'My idx is zero';
}

nok $ds.is-empty, 'Has more';

with $L[1] {
pass 'another';
is $ds.current, 1, 'taken';
is $_.idx, $ds.current, 'The same';
is @$_, (100, 'Last'), 'expected';
is $_.gist, "DBIish::Row[1](100 Last)", 'Some util';
}

nok $L[2], 'No more';
ok $ds.is-empty, 'Empty';

is $L.elems, 2, 'Two rows';
$L=();

my $count = 0;
my $data = '';
for $S1.() { # Call again
isa-ok $_, Row;
$count += $_<id>;
$data ~= $_<name>;
}
is $count, 100, 'Expected sum';
is $data, 'FirstLast', 'Expected data';
$S1.dispose;
}

my %test-data;
my $all-ok = True;
# We need more data
with $insert.SQL -> $insertor {
my @chars = (0..9, 'A'..'Z', 'a'..'z').flat;
for 1..^20 -> $i {
my $random_chars = @chars.pick(16).join('');
%test-data{$i} = $random_chars; # save these values for later testing

unless $insertor($i, $random_chars) { $all-ok = False; }
}
$insertor.dispose;
ok $all-ok, "insert 99 rows of random chars";
}


# Move them to other DB
with $insert.SQL($dbhM) -> $insertor {
isa-ok $insertor.parent.drv, 'DBDish::mysql';
with "SELECT * FROM test where id > ? and id <= ?".SQL {
isa-ok .parent.drv, 'DBDish::Pg';
for .(1, 10) -> $/ {
# Check data
$all-ok &&= %test-data{$<id>} eq $<name>;
# Copy
$all-ok &&= $insertor(|$/);
}
}
}
ok $all-ok, "Data match and copied";

with "SELECT * FROM test WHERE id > ? and id <= ?".SQL($dbhM) {
for .(1, 10) -> $/ {
# Check data
$all-ok &&= %test-data{$<id>} eq $<name>;
}
}
ok $all-ok, "Copied data match";

diag "Continuará...";

0 comments on commit 1ed8466

Please sign in to comment.