The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;
use Test::Exception;

use FindBin;
use lib ("$FindBin::Bin/../lib" =~ m[^(/.*)])[0];

use DBI;
use Ormlette;

# ->select with null criteria
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE test ( my_int integer, my_str varchar(10) )');
  Ormlette->init($dbh, namespace => 'SelectAll');

  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (7, 'seven')));
  is_deeply(SelectAll::Test->select, [ { my_int => 7, my_str => 'seven' } ],
    'retrieved only object in table with ->select');

  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (8, 'eight')));
  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (9, 'nine')));
  is_deeply(
    [ sort { $a->{my_int} <=> $b->{my_int} } @{SelectAll::Test->select} ],
    [ { my_int => 7, my_str => 'seven' },
      { my_int => 8, my_str => 'eight' },
      { my_int => 9, my_str => 'nine' } ],
    'retrieved all objects in table with ->select');
}

# ->select with criteria
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE test ( my_int integer, my_str varchar(10) )');
  Ormlette->init($dbh, namespace => 'SelectCrit');

  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (9, 'nine')));
  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (42, 'answer')));
  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (23, 'skidoo')));
  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (99, 'bottles')));

  is_deeply(SelectCrit::Test->select('WHERE my_int = 9'),
    [ { my_int => 9, my_str => 'nine' } ],
    '->select one record by hardcoded value');
  is_deeply(SelectCrit::Test->select('WHERE my_str = ?', 'answer'),
    [ { my_int => 42, my_str => 'answer' } ],
    '->select one record by placeholder');
  is_deeply(SelectCrit::Test->select('WHERE my_int > 40 ORDER BY my_int DESC'),
    [ { my_int => 99, my_str => 'bottles' },
      { my_int => 42, my_str => 'answer' } ],
    '->select and order multiple records');
  is_deeply(SelectCrit::Test->select('WHERE 0 = 1'), [ ],
    '->select returns an empty list when no records match');
}

# select returns properly-blessed objects
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE test ( my_int integer, my_str varchar(10) )');
  Ormlette->init($dbh, namespace => 'SelectBless');

  $dbh->do(q(INSERT INTO test (my_int, my_str) VALUES (12, 'twelve')));
  isa_ok(SelectBless::Test->select->[0], 'SelectBless::Test');
}

# select from join with shared field names
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE foo ( id integer primary key)');
  $dbh->do('CREATE TABLE bar ( id integer primary key, foo_id integer )');
  Ormlette->init($dbh, namespace => 'DupJoin');

  my $foo = DupJoin::Foo->create;
  my $bar = DupJoin::Bar->create(foo_id => $foo->id);
  is_deeply(DupJoin::Foo->select('JOIN bar ON foo.id = bar.foo_id'), [ $foo ],
    'do ->select on joined tables with shared field name');
}

# create ->load method for both keyed and unkeyed tables
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE keyed ( id integer primary key )');
  $dbh->do('CREATE TABLE no_key ( id integer )');
  Ormlette->init($dbh, namespace => 'KeyCheck');
  is(ref KeyCheck::Keyed->can('load'), 'CODE',
    'create ->load if primary key is present');
  is(ref KeyCheck::NoKey->can('load'), 'CODE',
    'also create ->load without primary key');
}

# retrieve records by key with ->load
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE keyed ( id integer primary key, my_txt char(10) )');
  $dbh->do('CREATE TABLE multi_key
    ( id1 integer, id2 integer, non_key text, PRIMARY KEY (id1, id2) )');
  Ormlette->init($dbh, namespace => 'KeyLoad');

  $dbh->do(q(INSERT INTO keyed (id, my_txt) VALUES ( 18, 'eighteen' )));
  $dbh->do(q(INSERT INTO keyed (id, my_txt) VALUES ( 19, 'nineteen' )));
  $dbh->do(q(INSERT INTO multi_key (id1, id2, non_key) VALUES ( 1, 2, 'tre')));
  $dbh->do(q(INSERT INTO multi_key (id1, id2, non_key) VALUES ( 4, 5, 'six')));

  my $obj = KeyLoad::Keyed->load(18);
  isa_ok($obj, 'KeyLoad::Keyed');
  is_deeply($obj, { id => 18, my_txt => 'eighteen' },
    '->load with single-field key');
  is(KeyLoad::Keyed->load(4), undef,
    '->load with single-field key returns nothing on missing key');

  undef $obj;
  $obj = KeyLoad::MultiKey->load(id1 => 4, id2 => 5);
  isa_ok($obj, 'KeyLoad::MultiKey');
  is_deeply($obj, { id1 => 4, id2 => 5, non_key => 'six' },
    '->load with multi-field key');
  is(KeyLoad::MultiKey->load(2, 'tre'), undef,
    '->load with multi-field key returns nothing on missing key');
}

# ->load by unique non-key values
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE keyed ( id integer primary key, my_txt char(10) )');
  $dbh->do('CREATE TABLE no_key ( foo text, bar text )');
  Ormlette->init($dbh, namespace => 'NonKeyLoad');

  $dbh->do(q(INSERT INTO keyed (id, my_txt) VALUES ( 1, 'first' )));
  $dbh->do(q(INSERT INTO keyed (id, my_txt) VALUES ( 2, 'second' )));
  $dbh->do(q(INSERT INTO no_key (foo, bar) VALUES ( 'mumble', 'frotz' )));
  $dbh->do(q(INSERT INTO no_key (foo, bar) VALUES ( 'xyzzy', 'plugh' )));

  my $obj = NonKeyLoad::Keyed->load(my_txt => 'first');
  isa_ok($obj, 'NonKeyLoad::Keyed');
  is_deeply($obj, { id => 1, my_txt => 'first' },
    '->load from keyed table by non-key field');
  is(NonKeyLoad::Keyed->load(my_txt => 'third'), undef,
    '->load from keyed table with missing non-key value returns nothing');

  undef $obj;
  $obj = NonKeyLoad::NoKey->load(foo => 'mumble');
  isa_ok($obj, 'NonKeyLoad::NoKey');
  is_deeply($obj, { foo => 'mumble', bar => 'frotz' },
    '->load from non-keyed table by single field');

  undef $obj;
  $obj = NonKeyLoad::NoKey->load(foo => 'xyzzy', bar => 'plugh');
  isa_ok($obj, 'NonKeyLoad::NoKey');
  is_deeply($obj, { foo => 'xyzzy', bar => 'plugh' },
    '->load from non-keyed table by multiple fields');

  is(NonKeyLoad::NoKey->load(foo => 'wibble'), undef,
    '->load from non-keyed table returns nothing on missing value');
}

# ->load by non-unique values
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE dupes ( foo text, bar text )');
  Ormlette->init($dbh, namespace => 'DupeLoad');

  $dbh->do(q(INSERT INTO dupes (foo, bar) VALUES ( 'foo', 'bar' )));
  $dbh->do(q(INSERT INTO dupes (foo, bar) VALUES ( 'foo', 'baz' )));

  my $obj = DupeLoad::Dupes->load(foo => 'foo');
  isa_ok($obj, 'DupeLoad::Dupes');
  is($obj->foo, 'foo',
    '->load matching multiple records gets correct value in duped field');
  like($obj->bar, qr/ba[rz]/,
    '->load matching multiple records gets a correct value in other field');
}

# die when attempting to ->load with invalid params
{
  my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '');
  $dbh->do('CREATE TABLE keyed ( id integer, my_txt text )');
  $dbh->do('CREATE TABLE multi_key
    ( id1 integer, id2 integer, non_key text, PRIMARY KEY (id1, id2) )');
  $dbh->do('CREATE TABLE no_key ( foo text, bar text )');
  Ormlette->init($dbh, namespace => 'BadParam');

  dies_ok { BadParam::Keyed->load(1, 2, 3) }
    '->load on single-field keyed table dies with 3 params';
  dies_ok { BadParam::MultiKey->load(1) }
    '->load on multi-field keyed table dies with 1 param';
  dies_ok { BadParam::MultiKey->load(1, 2, 3) }
    '->load on multi-field keyed table dies with 3 params';
  dies_ok { BadParam::NoKey->load(1) }
    '->load on non-keyed table dies with 1 param';
  dies_ok { BadParam::NoKey->load(1, 2, 3) }
    '->load on non-keyed table dies with 3 params';
}

done_testing;