Red.git | lib/Red/Driver/ | Mock.rakumod
use Test;
use Red::AST;
use Red::Driver;
use Red::Statement;
use Red::Driver::SQLite;
use Red::Driver::CommonSQL;
unit class Red::Driver::Mock does Red::Driver;
method schema-reader {}
multi prepare-sql(Str:U $_) { Str }
multi prepare-sql(Str:D $_) {
.lc
.subst(/<[\w."']>+/, { " $_ " }, :g)
.subst(
/[\s* "(" \s*] ~ [\s* ")" \s*] ([<-[()]>+|"(" ~ ")" [\s*.*?\s*]])+?/,
-> $/ {
"( { $0.Str.split(/\s* "," \s*/).sort.join(" , ") } )"
},
:g
)
.subst(/\s+/, " ", :g)
.trim
}
has Hash %.when-str{Str};
has Hash %.when-re{Regex};
has Bool $!die-on-unexpected = False;
has Red::Driver $.driver-obj = Red::Driver::SQLite.new;
multi method default-type-for(Red::Column $a --> Str:D) { $!driver-obj.default-type-for($a) }
multi method is-valid-table-name(|c) { $!driver-obj.is-valid-table-name(|c) }
multi method type-by-name(|c) { $!driver-obj.type-by-name(|c) }
multi method inflate(|c) { $!driver-obj.inflate(|c) }
method translate(|c) { $!driver-obj.translate(|c) }
multi method join-type(|c) { $!driver-obj.join-type(|c) }
class Statement does Red::Statement {
has Iterator $.iterator;
has Str $.should-run;
method stt-exec($stt, *@bind) { }
method stt-row($stt) { $!iterator.pull-one }
}
method die-on-unexpected() {
$!die-on-unexpected = True
}
proto method when(
$when,
:$times,
Bool :$once,
Bool :$twice
) {
my $*times = $times // ($once ?? 1 !! $twice ?? 2 !! Inf);
{*};
self
}
multi method when($when where Str | Regex, Bool :$never! where * === True) {
self.when: $when, :run{ die "This should never be called" }, :0times;
}
multi method when(Str $when, :&run!) {
%!when-str{$when.&prepare-sql} = {:&run, :$*times, :0counter};
}
multi method when(Regex $when, :&run!) {
%!when-re{$when} = {:&run, :$*times, :0counter};
}
multi method when(Str $when, :@return!) {
self.when: $when.&prepare-sql, :run{ @return }, :$*times;
}
multi method when(Regex $when, :@return!) {
self.when: $when, :run{ @return }, :$*times;
}
multi method when(Str $when, :$throw!) {
self.when: $when.&prepare-sql, :run{ die $throw }, :$*times;
}
multi method when(Regex $when, :$throw!) {
self.when: $when, :run{ die $throw }, :$*times;
}
multi method prepare(Red::AST $query) {
do for |self.translate: self.optimize: $query -> Pair \data {
my ($sql, @bind) := do given data { .key, .value }
self.prepare: $sql;
}
}
multi method prepare(Str $query) {
my $t-query = $query.trans(("\n", "\t") => < ␉>);
self.debug: $query;
given $query.&prepare-sql {
with %!when-str{$_} -> % (:$times!, :$counter! is rw, :&run!) {
$counter++;
die "The query '$t-query' should never be called" unless $times;
die "The query '$t-query' should run $times time(s) but was ran $counter times" if $counter > $times;
return Statement.new: :driver(self), :iterator(run.iterator)
}
my $size = 0;
my %data;
for %!when-re.kv -> Regex $re, %value {
if .match($re) && $/.Str.chars > $size {
$size = $/.Str.chars;
%data := %value;
}
}
if %data {
%data<counter>++;
die "The query '$t-query' should never be called" unless %data<times> > 0;
die "The query '$t-query' should run %data<times> time(s) but was ran %data<counter> times" if %data<counter> > %data<times>;
return Statement.new: :driver(self), :iterator(%data<run>.().iterator)
}
flunk "Unexpected query: $_" if $!die-on-unexpected;
}
Statement.new: :driver(self), :iterator([].iterator)
}
method verify { #is test-assertion {
subtest {
plan %!when-str + %!when-re;
for %!when-str.kv -> Str $str, % (:$counter = 0, :$times, |) {
ok ($times == Inf or $counter == $times),
"Query '$str' should be called $times times and was called $counter time(s)";
}
for %!when-re.kv -> Regex $re, % (:$counter = 0, :$times, |) {
ok ($times == Inf or $counter == $times),
"Query that matches '$re.raku()' should be called $times times and was called $counter time(s)";
}
}, "Red Mock verify"
}