The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
use strict;
BEGIN {
	$|  = 1;
	$^W = 1;
}

use t::lib::Test qw/connect_ok/;
use Test::More;
use Test::NoWarnings;

plan tests => 15;

my $dbh = connect_ok( RaiseError => 1, PrintError => 0, AutoCommit => 1 );

$dbh->sqlite_create_module(vtab => "DBD::SQLite::VirtualTable::T");

ok $dbh->do("CREATE VIRTUAL TABLE foobar USING vtab(foo INTEGER, bar INTEGER)"),
   "created foobar";

# overload functions "abs" and "substr"
$DBD::SQLite::VirtualTable::T::funcs{abs}{overloaded} 
  = sub {my $val = shift; return "fake_abs($val)" };
$DBD::SQLite::VirtualTable::T::funcs{substr}{overloaded} 
  = sub {my ($val, $offset, $len) = @_; return "fake_substr($val, $offset, $len)" };

# make a first query
my $row = $dbh->selectrow_hashref(<<"");
  SELECT abs(foo) afoo,
         abs(bar) abar,
         substr(foo, 3, 5) sfoo,
         trim(foo) tfoo
  FROM foobar

is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls},    1, "abs called";
is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 1, "substr called";
is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls},   1, "trim called";

is_deeply $row, { 'abar' => 'fake_abs(1)',
                  'afoo' => 'fake_abs(0)',
                  'sfoo' => 'fake_substr(0, 3, 5)',
                  'tfoo' => '0' }, "func results";

# new query : FIND_FUNCTION should not be called again
$row = $dbh->selectrow_hashref(<<"");
  SELECT abs(foo) afoo,
         abs(bar) abar,
         substr(foo, 3, 5) sfoo,
         trim(foo) tfoo
  FROM foobar

is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls},    1, "abs still 1";
is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 1, "substr still 1";
is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls},   1, "trim still 1";


# new table : should issue new calls to FIND_FUNCTION
ok $dbh->do("CREATE VIRTUAL TABLE barfoo USING vtab(foo INTEGER, bar INTEGER)"),
   "created barfoo"; 

$row = $dbh->selectrow_hashref(<<"");
  SELECT abs(foo) afoo,
         abs(bar) abar,
         substr(foo, 3, 5) sfoo,
         trim(foo) tfoo
  FROM barfoo

is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls},    2, "abs now 2";
is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 2, "substr now 2";
is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls},   2, "trim now 2";


# drop table : should free references to functions
ok $dbh->do("DROP TABLE foobar");

# drop connection
undef $dbh;

note "done";

package DBD::SQLite::VirtualTable::T;
use strict;
use warnings;
use base 'DBD::SQLite::VirtualTable';



sub BEST_INDEX {
  my ($self, $constraints, $order_by) = @_;

  my $ix = 0;

  foreach my $constraint (@$constraints) {
    $constraint->{argvIndex} = $ix++;
    $constraint->{omit}      = 1; # to prevent sqlite core to check values
  }

  my $outputs = {
    idxNum           => 1,
    idxStr           => "foobar",
    orderByConsumed  => 0,
    estimatedCost    => 1.0,
    estimatedRows    => undef,
   };

  return $outputs;
}

our %funcs;


sub FIND_FUNCTION {
  my ($self, $n_arg, $function_name) = @_;

  $funcs{$function_name}{calls} += 1;
  my $func = $funcs{$function_name}{overloaded};
  return $func;
}


package DBD::SQLite::VirtualTable::T::Cursor;
use strict;
use warnings;
use base 'DBD::SQLite::VirtualTable::Cursor';

sub NEW {
  my $class = shift;

  my $self = $class->DBD::SQLite::VirtualTable::Cursor::NEW(@_);
  $self->{row_count} = 5;

  return $self;
}

sub FILTER {
  my ($self, $idxNum, $idxStr, @values) = @_;

  return;
}



sub EOF {
  my $self = shift;

  return !$self->{row_count};
}

sub NEXT {
  my $self = shift;

  $self->{row_count}--;
}

sub COLUMN {
  my ($self, $idxCol) = @_;

  return $idxCol;
}

sub ROWID {
  my ($self) = @_;

  return $self->{row_count};
}


1;