The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

use lib 't/lib';

use Fey::Test;
use Test::More;

use Fey::SQL;

my $s   = Fey::Test->mock_test_schema_with_fks();
my $dbh = Fey::Test->mock_dbh();

{
    my $q = Fey::SQL->new_select();

    eval { $q->from() };
    like(
        $@, qr/from\(\) called with invalid parameters \(\)/,
        'from() without any parameters is an error'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User') );

    is( $q->from_clause($dbh), q{FROM "User"},
        'from_clause() for one table' );
}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from('foo') };
    like(
        $@, qr/from\(\) called with invalid parameters \(foo\)/,
        'from() called with one non-table argument'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $alias = $s->table('User')->alias( alias_name => 'UserA' );
    $q->from($alias);

    is(
        $q->from_clause($dbh), q{FROM "User" AS "UserA"},
        'from_clause() for one table alias'
    );

}

{
    my $q = Fey::SQL->new_select();

    my $user_t = $s->table('User');

    my $alias = $user_t->alias( alias_name => 'UserA' );

    my $fk = Fey::FK->new(
        source_columns => $user_t->column('user_id'),
        target_columns => $alias->column('user_id'),
    );

    $q->from( $user_t, $alias, $fk );

    is(
        $q->from_clause($dbh),
        q{FROM "User" JOIN "User" AS "UserA" ON ("User"."user_id" = "UserA"."user_id")},
        'from_clause() with self-join to alias using fake FK'
    );

}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( $s->table('User'), $s->table('Group') ) };
    like(
        $@, qr/do not share a foreign key/,
        'Cannot join two tables without a foreign key'
    );
}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( $s->table('User'), 'foo' ) };
    like(
        $@,
        qr/\Qthe first two arguments to from() were not valid (not tables or something else joinable)/,
        'from() called with two args, one not a table'
    );

    eval { $q->from( 'foo', $s->table('User') ) };
    like(
        $@,
        qr/\Qthe first two arguments to from() were not valid (not tables or something else joinable)/,
        'from() called with two args, one not a table'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), $s->table('UserGroup') );

    my $sql
        = q{FROM "User" JOIN "UserGroup" ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables, fk not provided'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'),      $s->table('UserGroup') );
    $q->from( $s->table('UserGroup'), $s->table('Group') );

    my $sql
        = q{FROM "UserGroup" JOIN "Group" ON ("UserGroup"."group_id" = "Group"."group_id")};
    $sql .= q{ JOIN "User" ON ("UserGroup"."user_id" = "User"."user_id")};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two joins'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User') );
    $q->from( $s->table('User'), $s->table('UserGroup') );

    my $sql
        = q{FROM "User" JOIN "UserGroup" ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for table alone first, then table in join'
    );
}

{
    my $frag = Fey::SQL::Fragment::Join->new(
        table1 => $s->table('User'),
        table2 => $s->table('UserGroup'),
    );

    is(
        $frag->sql_with_alias(
            'Fey::FakeDBI', {
                $s->table('User')->id()      => 1,
                $s->table('UserGroup')->id() => 1,
            },
        ),
        q{},
        'join fragment ignores tables already seen'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'),  $s->table('UserGroup') );
    $q->from( $s->table('Group'), $s->table('UserGroup') );

    my $sql
        = q{FROM "Group" JOIN "UserGroup" ON ("UserGroup"."group_id" = "Group"."group_id")};
    $sql .= q{ JOIN "User" ON ("UserGroup"."user_id" = "User"."user_id")};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two joins, seen table comes second in second clause'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User') );
    $q->from( $s->table('UserGroup') );
    $q->from( $s->table('Group') );

    my $sql = q{FROM "Group", "User", "UserGroup"};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for three tables with no joins'
    );
}

{
    my $q = Fey::SQL->new_select();

    my @t = ( $s->table('User'), $s->table('UserGroup') );
    my ($fk) = $s->foreign_keys_between_tables(@t);
    $q->from( @t, $fk );

    my $sql
        = q{FROM "User" JOIN "UserGroup" ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with fk provided'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $fk = Fey::FK->new(
        source_columns => $s->table('User')->column('user_id'),
        target_columns => $s->table('UserGroup')->column('group_id'),
    );
    $s->add_foreign_key($fk);

    eval { $q->from( $s->table('User'), $s->table('UserGroup') ) };
    like(
        $@, qr/more than one foreign key/,
        'Cannot auto-join two tables with >1 foreign key'
    );

    $s->remove_foreign_key($fk);
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), 'left', $s->table('UserGroup') );

    my $sql = q{FROM "User" LEFT OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with left outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $user_alias = $s->table('User')->alias( alias_name => 'U' );
    my $user_group_alias
        = $s->table('UserGroup')->alias( alias_name => 'UG' );

    $q->from( $user_alias, 'left', $user_group_alias );

    my $sql = q{FROM "User" AS "U" LEFT OUTER JOIN "UserGroup" AS "UG"};
    $sql .= q{ ON ("UG"."user_id" = "U"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two table aliases with left outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), $s->table('UserGroup') );
    $q->from( $s->table('UserGroup'), 'left', $s->table('Group') );

    my $sql = q{FROM "UserGroup" LEFT OUTER JOIN "Group" ON};
    $sql .= q{ ("UserGroup"."group_id" = "Group"."group_id")};
    $sql .= q{ JOIN "User" ON ("UserGroup"."user_id" = "User"."user_id")};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for regular join + left outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    my @t = ( $s->table('User'), $s->table('UserGroup') );
    my ($fk) = $s->foreign_keys_between_tables(@t);

    $q->from( $t[0], 'left', $t[1], $fk );

    my $sql = q{FROM "User" LEFT OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with left outer join with explicit fk'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), 'right', $s->table('UserGroup') );

    my $sql = q{FROM "User" RIGHT OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with right outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), 'full', $s->table('UserGroup') );

    my $sql = q{FROM "User" FULL OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with full outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User'), 'full', $s->table('UserGroup') );

    my $sql = q{FROM "User" FULL OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for two tables with full outer join'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $q2 = Fey::SQL->new_where( auto_placeholders => 0 );
    $q2->where( $s->table('User')->column('user_id'), '=', 2 );

    $q->from( $s->table('User'), 'left', $s->table('UserGroup'), $q2 );

    my $sql = q{FROM "User" LEFT OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id"};
    $sql .= q{ AND "User"."user_id" = 2)};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for outer join with where clause'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $q2 = Fey::SQL->new_where( auto_placeholders => 0 );
    $q2->where( $s->table('User')->column('user_id'), '=', 2 );

    my @t = ( $s->table('User'), $s->table('UserGroup') );
    my ($fk) = $s->foreign_keys_between_tables(@t);

    $q->from( $t[0], 'left', $t[1], $fk, $q2 );

    my $sql = q{FROM "User" LEFT OUTER JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id"};
    $sql .= q{ AND "User"."user_id" = 2)};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for outer join with where clause() and explicit fk'
    );
}

{
    my $q = Fey::SQL->new_select();

    my $alias = $s->table('User')->alias( alias_name => 'UserA' );
    $q->from( $s->table('User'), $s->table('UserGroup') );
    $q->from( $alias,            $s->table('UserGroup') );

    my $sql = q{FROM "User" JOIN "UserGroup"};
    $sql .= q{ ON ("UserGroup"."user_id" = "User"."user_id")};
    $sql .= q{ JOIN "User" AS "UserA"};
    $sql .= q{ ON ("UserGroup"."user_id" = "UserA"."user_id")};

    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for one table alias'
    );

}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( $s->table('User')->column('user_id') ) };
    like(
        $@, qr/\Qfrom() called with invalid parameters/,
        'passing just a column to from()'
    );
}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( $s->table('User'), 'foobar', $s->table('UserGroup') ) };
    like(
        $@, qr/invalid outer join type/,
        'invalid outer join type causes an error'
    );
}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( 'not a table', 'left', $s->table('UserGroup') ) };
    like(
        $@, qr/from\(\) was called with invalid arguments/,
        'invalid outer join type causes an error'
    );
}

{
    my $q = Fey::SQL->new_select();

    eval { $q->from( $s->table('UserGroup'), 'left', 'not a table' ) };
    like(
        $@, qr/from\(\) was called with invalid arguments/,
        'invalid outer join type causes an error'
    );
}

{
    my $q = Fey::SQL->new_select();

    eval {
        $q->from( $s->table('User'), 'full', $s->table('UserGroup'),
            'invalid' );
    };
    like(
        $@, qr/\Qfrom() called with invalid parameters/,
        'passing invalid parameter to from() with outer join'
    );
}

{
    my $q         = Fey::SQL->new_select();
    my $subselect = Fey::SQL->new_select();
    $subselect->select( $s->table('User')->column('user_id') )
        ->from( $s->table('User') );

    $q->from($subselect);

    my $sql = q{FROM ( SELECT "User"."user_id" FROM "User" ) AS "SUBSELECT0"};
    is(
        $q->from_clause($dbh), $sql,
        'from_clause() for subselect'
    );
    is(
        $subselect->alias_name, 'SUBSELECT0',
        'subselect alias_name is set after use in from()'
    );

}

{
    my $q = Fey::SQL->new_select();
    my $table = Fey::Table->new( name => 'NewTable' );

    eval { $q->from($table) };
    like(
        $@, qr/\Qfrom() called with invalid parameters/,
        'cannot pass a table without a schema to from()'
    );
}

{
    my $q = Fey::SQL->new_select();
    my $table = Fey::Table->new( name => 'NewTable' );

    eval { $q->from( $table, $s->table('User') ) };
    like(
        $@,
        qr/\Qthe first two arguments to from() were not valid (not tables or something else joinable)/,
        'cannot pass a table without a schema to from() as part of a join'
    );
}

{
    my $q = Fey::SQL->new_select();
    my $table = Fey::Table->new( name => 'NewTable' );

    my $non_table = bless {}, 'Thingy';

    eval { $q->from( $table, $non_table ) };
    like(
        $@,
        qr/\Qthe first two arguments to from() were not valid (not tables or something else joinable)/,
        'cannot pass a table without a schema to from()'
    );
}

{
    my $q = Fey::SQL->new_select->from( $s->table('User') );

    # The bug this exercised was that two aliases were created, but
    # since they had the same name, we only ended up with one join
    # fragment. Then the column from the second table alias ended up
    # going out of scope.
    for ( 0 .. 1 ) {
        my $table = $s->table('UserGroup')->alias('UserGroup1');
        $q->from( $s->table('User'), $table );
        $q->where( $table->column('group_id'), '=', 1 );
    }

    $q->select(1);

    my $sql = q{SELECT 1 FROM "User" JOIN "UserGroup" AS "UserGroup1" ON};
    $sql .= q{ ("UserGroup1"."user_id" = "User"."user_id")};
    $sql
        .= q{ WHERE "UserGroup1"."group_id" = ? AND "UserGroup1"."group_id" = ?};

    is( $q->sql($dbh), $sql, 'alias shows up in join once and where twice' );
}

{
    my $q = Fey::SQL->new_select();

    $q->from( $s->table('User') );
    $q->from( $s->table('User'), 'left', $s->table('UserGroup') );

    my $sql = q{FROM "User" LEFT OUTER JOIN "UserGroup" ON};
    $sql .= q{ ("UserGroup"."user_id" = "User"."user_id")};

    is( $q->from_clause($dbh), $sql,
        'table only shows up once in from, not twice' );
}

{
    my $t1    = $s->table('User');
    my $t2    = $s->table('UserGroup');
    my $where = Fey::SQL->new_where( auto_placeholders => 0 );
    $where->where( $t1->column('user_id'), '=', 2 );
    my ($fk) = $s->foreign_keys_between_tables( $t1, $t2 );

    my $sql = q{FROM "User" JOIN "UserGroup" ON};
    $sql .= q{ ("UserGroup"."user_id" = "User"."user_id"};
    $sql .= q{ AND "User"."user_id" = 2)};

    {
        my $q = Fey::SQL->new_select();
        $q->from( $t1, $t2, $where );
        is(
            $q->from_clause($dbh), $sql,
            'from_clause() for inner join with where clause'
        );
    }
    {
        my $q = Fey::SQL->new_select();
        $q->from( $t1, $t2, $fk, $where );
        is(
            $q->from_clause($dbh), $sql,
            'from_clause() for inner join with explicit fk and where clause'
        );
    }
}

done_testing();