DB-Handy

 view release on metacpan or  search on metacpan

t/1010_fixes.t  view on Meta::CPAN

######################################################################
#
# Tests for three bug fixes:
#
#   Fix 1: CHECK constraint is now evaluated on UPDATE (not INSERT only)
#   Fix 2: Index used for AND two-sided range and BETWEEN queries
#   Fix 3: fetchrow_arrayref / NAME reflect SELECT column order
#
# All tests use Perl 5.005_03-compatible syntax (no 'our', no say,
# no given/when, no //, no qr with modifiers unavailable in 5.005).
#
######################################################################

use strict;
BEGIN { $INC{'warnings.pm'} = '' if $] < 5.006 }; use warnings; local $^W=1;
BEGIN { pop @INC if $INC[-1] eq '.' }
use FindBin ();
use lib "$FindBin::Bin/../lib";
use DB::Handy;

###############################################################################
# Embedded test harness (no Test::More dependency)
###############################################################################
my ($PASS, $FAIL, $T) = (0, 0, 0);
sub ok  { my($c,$n)=@_; $T++; $c ? ($PASS++,print "ok $T - $n\n")
                                  : ($FAIL++,print "not ok $T - $n\n") }
sub is  { my($g,$e,$n)=@_; $T++;
          defined($g) && ("$g" eq "$e")
            ? ($PASS++, print "ok $T - $n\n")
            : ($FAIL++, print "not ok $T - $n  (got='${\ (defined $g?$g:'undef')}', exp='$e')\n") }

print "1..60\n";

use File::Path ();
my $BASE = "/tmp/test_fixes_$$";
File::Path::rmtree($BASE) if -d $BASE;

###############################################################################
# Setup
###############################################################################
my $db = DB::Handy->new(base_dir => $BASE);
$db->create_database('ftest');
$db->use_database('ftest');

###############################################################################
# Fix 1 -- CHECK constraint enforced on UPDATE
###############################################################################
$db->execute("CREATE TABLE ck (id INT NOT NULL, salary INT CHECK (salary >= 0), score INT CHECK (score <= 100))");
my $r = $db->execute("INSERT INTO ck (id,salary,score) VALUES (1,50000,80)");
# ok 1
ok($r->{type} eq 'ok', "Fix1: INSERT valid row ok");

$r = $db->execute("INSERT INTO ck (id,salary,score) VALUES (2,-1,50)");
# ok 2
ok($r->{type} eq 'error', "Fix1: INSERT negative salary blocked");

# UPDATE violation: salary goes negative
$r = $db->execute("UPDATE ck SET salary=-1 WHERE id=1");
# ok 3
ok($r->{type} eq 'error', "Fix1: UPDATE salary=-1 blocked by CHECK");
# ok 4
ok($r->{message} =~ /CHECK/, "Fix1: UPDATE error mentions CHECK");

# UPDATE violation: score exceeds limit
$r = $db->execute("UPDATE ck SET score=101 WHERE id=1");
# ok 5
ok($r->{type} eq 'error', "Fix1: UPDATE score=101 blocked by CHECK");

# UPDATE to boundary value: allowed
$r = $db->execute("UPDATE ck SET salary=0 WHERE id=1");
# ok 6
ok($r->{type} eq 'ok', "Fix1: UPDATE salary=0 (boundary) ok");
$r = $db->execute("SELECT salary FROM ck WHERE id=1");
# ok 7
is($r->{data}[0]{salary}+0, 0, "Fix1: salary=0 stored correctly");

# UPDATE to boundary score: allowed
$r = $db->execute("UPDATE ck SET score=100 WHERE id=1");
# ok 8
ok($r->{type} eq 'eq' || $r->{type} eq 'ok', "Fix1: UPDATE score=100 (boundary) ok");
$r = $db->execute("UPDATE ck SET score=100 WHERE id=1");
# ok 9
ok($r->{type} eq 'ok', "Fix1: UPDATE score=100 ok");

# UPDATE valid positive salary
$r = $db->execute("UPDATE ck SET salary=99000 WHERE id=1");
# ok 10
ok($r->{type} eq 'ok', "Fix1: UPDATE salary=99000 ok");
$r = $db->execute("SELECT salary FROM ck WHERE id=1");
# ok 11
is($r->{data}[0]{salary}+0, 99000, "Fix1: salary=99000 verified");

# UPDATE only non-CHECK column: not blocked
$db->execute("CREATE TABLE ck2 (id INT NOT NULL, val INT CHECK (val >= 10), note VARCHAR(20))");
$db->execute("INSERT INTO ck2 (id,val,note) VALUES (1,50,'ok')");
$r = $db->execute("UPDATE ck2 SET note='updated' WHERE id=1");
# ok 12
ok($r->{type} eq 'ok', "Fix1: UPDATE non-CHECK column not blocked");
$r = $db->execute("UPDATE ck2 SET val=9 WHERE id=1");
# ok 13
ok($r->{type} eq 'error', "Fix1: UPDATE CHECK column to invalid value blocked");

###############################################################################
# Fix 2 -- Index used for AND range and BETWEEN
###############################################################################
$db->execute("CREATE TABLE rng (id INT, v FLOAT)");
$db->execute("CREATE INDEX idx_rng_id ON rng (id)");
$db->execute("CREATE INDEX idx_rng_v  ON rng (v)");
for my $i (1..50) {
    my $v = $i * 0.5;
    $db->execute("INSERT INTO rng (id,v) VALUES ($i,$v)");
}

# AND range: id > 10 AND id < 20  (exclusive: 11..19 = 9 rows)
$r = $db->execute("SELECT id FROM rng WHERE id > 10 AND id < 20");
# ok 14
is(scalar @{$r->{data}}, 9, "Fix2: id > 10 AND id < 20 returns 9 rows");

# AND range: id >= 10 AND id <= 20  (inclusive: 10..20 = 11 rows)
$r = $db->execute("SELECT id FROM rng WHERE id >= 10 AND id <= 20");
# ok 15
is(scalar @{$r->{data}}, 11, "Fix2: id >= 10 AND id <= 20 returns 11 rows");

# AND range reversed: id < 20 AND id > 10
$r = $db->execute("SELECT id FROM rng WHERE id < 20 AND id > 10");
# ok 16
is(scalar @{$r->{data}}, 9, "Fix2: reversed AND (id < 20 AND id > 10) returns 9 rows");

# BETWEEN inclusive: id BETWEEN 10 AND 20  (10..20 = 11 rows)
$r = $db->execute("SELECT id FROM rng WHERE id BETWEEN 10 AND 20");
# ok 17
is(scalar @{$r->{data}}, 11, "Fix2: id BETWEEN 10 AND 20 returns 11 rows");

# BETWEEN single value: id BETWEEN 5 AND 5 (1 row)
$r = $db->execute("SELECT id FROM rng WHERE id BETWEEN 5 AND 5");
# ok 18
is(scalar @{$r->{data}}, 1, "Fix2: id BETWEEN 5 AND 5 returns 1 row");
# ok 19
is($r->{data}[0]{id}+0, 5, "Fix2: BETWEEN 5 AND 5 -> id=5");

# BETWEEN empty range (inverted): id BETWEEN 20 AND 10 (0 rows)
$r = $db->execute("SELECT id FROM rng WHERE id BETWEEN 20 AND 10");
# ok 20
is(scalar @{$r->{data}}, 0, "Fix2: BETWEEN inverted range returns 0 rows");

# Correctness: AND range values match
$r = $db->execute("SELECT id FROM rng WHERE id >= 3 AND id <= 5");
my @ids = sort { $a <=> $b } map { $_->{id}+0 } @{$r->{data}};
# ok 21
is(join(',', @ids), '3,4,5', "Fix2: AND range values correct (3,4,5)");

# FLOAT index with AND
$r = $db->execute("SELECT v FROM rng WHERE v >= 2.5 AND v <= 5.0");
my @vs = sort { $a <=> $b } map { $_->{v}+0 } @{$r->{data}};
# ok 22
is(scalar @vs, 6, "Fix2: FLOAT AND range returns 6 rows");
# ok 23
ok($vs[0] > 2.4 && $vs[0] < 2.6, "Fix2: FLOAT AND range lower bound ~2.5");

# Verify index is used correctly: indexed and non-indexed tables return
# identical results for AND range queries (correctness cross-check).



( run in 1.220 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )