diff --git a/cpanfile b/cpanfile index 14019ac..f4639d0 100644 --- a/cpanfile +++ b/cpanfile @@ -22,6 +22,7 @@ on develop => sub { on test => sub { requires 'version'; + requires 'Tie::IxHash'; }; feature 'test_sqlite', 'Test SQLite' => sub { diff --git a/lib/Data/ObjectDriver/Driver/DBI.pm b/lib/Data/ObjectDriver/Driver/DBI.pm index 148db0d..69ee106 100644 --- a/lib/Data/ObjectDriver/Driver/DBI.pm +++ b/lib/Data/ObjectDriver/Driver/DBI.pm @@ -12,6 +12,7 @@ use Data::ObjectDriver::Errors; use Data::ObjectDriver::SQL; use Data::ObjectDriver::Driver::DBD; use Data::ObjectDriver::Iterator; +use Scalar::Util 'blessed'; my $ForkSafe = _is_fork_safe(); my %Handles; @@ -172,14 +173,20 @@ sub prepare_fetch { sub fetch { my $driver = shift; - my($rec, $class, $orig_terms, $orig_args) = @_; + my ($rec, $class, $terms_or_stmt, $orig_args) = @_; + my ($sql, $stmt); - if ($Data::ObjectDriver::RESTRICT_IO) { - use Data::Dumper; - die "Attempted DBI I/O while in restricted mode: fetch() " . Dumper($orig_terms, $orig_args); - } + if (blessed($terms_or_stmt) && $terms_or_stmt->isa('Data::ObjectDriver::SQL')) { + $sql = $terms_or_stmt->as_sql; + $stmt = $terms_or_stmt; + } else { + if ($Data::ObjectDriver::RESTRICT_IO) { + use Data::Dumper; + die "Attempted DBI I/O while in restricted mode: fetch() " . Dumper($terms_or_stmt, $orig_args); + } - my ($sql, $bind, $stmt) = $driver->prepare_fetch($class, $orig_terms, $orig_args); + ($sql, undef, $stmt) = $driver->prepare_fetch($class, $terms_or_stmt, $orig_args); + } my @bind; my $map = $stmt->select_map; @@ -218,11 +225,11 @@ sub load_object_from_rec { } sub search { - my($driver) = shift; - my($class, $terms, $args) = @_; + my ($driver) = shift; + my ($class, $terms_or_stmt, $args) = @_; my $rec = {}; - my $sth = $driver->fetch($rec, $class, $terms, $args); + my $sth = $driver->fetch($rec, $class, $terms_or_stmt, $args); my $iter = sub { ## This is kind of a hack--we need $driver to stay in scope, diff --git a/lib/Data/ObjectDriver/SQL.pm b/lib/Data/ObjectDriver/SQL.pm index 8c43856..c37576f 100644 --- a/lib/Data/ObjectDriver/SQL.pm +++ b/lib/Data/ObjectDriver/SQL.pm @@ -3,6 +3,7 @@ package Data::ObjectDriver::SQL; use strict; use warnings; +use Scalar::Util 'blessed'; use base qw( Class::Accessor::Fast ); @@ -10,7 +11,7 @@ __PACKAGE__->mk_accessors(qw( select distinct select_map select_map_reverse from joins where bind limit offset group order having where_values column_mutator index_hint - comment + comment as is_bind_contatinated )); sub new { @@ -33,10 +34,17 @@ sub new { sub add_select { my $stmt = shift; my($term, $col) = @_; - $col ||= $term; push @{ $stmt->select }, $term; - $stmt->select_map->{$term} = $col; - $stmt->select_map_reverse->{$col} = $term; + if (blessed($term) && $term->isa('Data::ObjectDriver::SQL')) { + my $alias = $col || $term->as; + die 'Sub-query requires an alias by setting $stmt->as(...)' unless $alias; + $stmt->select_map->{$term} = $alias; + $stmt->select_map_reverse->{$alias} = $term; + } else { + $col ||= $term; + $stmt->select_map->{$term} = $col; + $stmt->select_map_reverse->{$col} = $term; + } } sub add_join { @@ -60,12 +68,25 @@ sub add_index_hint { sub as_sql { my $stmt = shift; my $sql = ''; + my @bind_for_select; + if (@{ $stmt->select }) { $sql .= 'SELECT '; $sql .= 'DISTINCT ' if $stmt->distinct; + my $select_map = $stmt->select_map; $sql .= join(', ', map { - my $alias = $stmt->select_map->{$_}; - $alias && /(?:^|\.)\Q$alias\E$/ ? $_ : "$_ $alias"; + my $col = $_; + my $alias = $select_map->{$col}; + if (blessed($col) && $col->isa('Data::ObjectDriver::SQL')) { + push @bind_for_select, @{ $col->{bind} }; + $col->as_subquery($alias); + } else { + if ($alias) { + /(?:^|\.)\Q$alias\E$/ ? $col : "$col $alias"; + } else { + $col; + } + } } @{ $stmt->select }) . "\n"; } $sql .= 'FROM '; @@ -91,8 +112,18 @@ sub as_sql { $sql .= ', ' if @from; } + my @bind_for_from; + if (@from) { - $sql .= join ', ', map { $stmt->_add_index_hint($_) } @from; + $sql .= join ', ', map { + my $from = $_; + if (blessed($from) && $from->isa('Data::ObjectDriver::SQL')) { + push @bind_for_from, @{$from->{bind}}; + $from->as_subquery; + } else { + $stmt->_add_index_hint($from); + } + } @from; } $sql .= "\n"; @@ -107,9 +138,25 @@ sub as_sql { if ($comment && $comment =~ /([ 0-9a-zA-Z.:;()_#&,]+)/) { $sql .= "-- $1" if $1; } + + unless ($stmt->is_bind_contatinated) { + @{ $stmt->{bind} } = (@bind_for_select, @bind_for_from, @{ $stmt->{bind} }); + $stmt->is_bind_contatinated(1); + } + return $sql; } +sub as_subquery { + my ($stmt, $alias) = @_; + my $subquery = '(' . $stmt->as_sql . ')'; + $alias ||= $stmt->as; + if ($alias) { + $subquery .= ' AS ' . $alias; + } + $subquery; +} + sub as_limit { my $stmt = shift; my $n = $stmt->limit or @@ -231,7 +278,11 @@ sub add_having { # Carp::croak("Invalid/unsafe column name $col") unless $col =~ /^[\w\.]+$/; if (my $orig = $stmt->select_map_reverse->{$col}) { - $col = $orig; + if (blessed($orig) && $orig->isa('Data::ObjectDriver::SQL')) { + # do nothins + } else { + $col = $orig; + } } my($term, $bind) = $stmt->_mk_term($col, $val); @@ -281,12 +332,17 @@ sub _mk_term { $term = "$c $op ? AND ?"; push @bind, @{$val->{value}}; } else { - if (ref $val->{value} eq 'SCALAR') { - $term = "$c $val->{op} " . ${$val->{value}}; + my $value = $val->{value}; + if (ref $value eq 'SCALAR') { + $term = "$c $val->{op} " . $$value; + } elsif (blessed($value) && $value->isa('Data::ObjectDriver::SQL')) { + local $value->{as} = undef; + $term = "$c $val->{op} ". $value->as_subquery; + push @bind, @{$value->{bind}}; } else { $term = "$c $val->{op} ?"; $term .= $stmt->as_escape($val->{escape}) if $val->{escape} && $op =~ /^(?:NOT\s+)?I?LIKE$/; - push @bind, $val->{value}; + push @bind, $value; } } } elsif (ref($val) eq 'SCALAR') { diff --git a/t/11-sql-with-models.t b/t/11-sql-with-models.t new file mode 100644 index 0000000..3a508d3 --- /dev/null +++ b/t/11-sql-with-models.t @@ -0,0 +1,400 @@ +# $Id$ + +use strict; +use warnings; + +use lib 't/lib'; +use lib 't/lib/sql'; +use Test::More; +use DodTestUtil; +use Tie::IxHash; + +BEGIN { DodTestUtil->check_driver } + +use Blog; +use Entry; + +sub ordered_hashref { + tie my %params, Tie::IxHash::, @_; + return \%params; +} + +setup_dbs({ + global => [qw( blog entry )], +}); + +my $blog1 = Blog->new(name => 'blog1'); +$blog1->save; +my $blog2 = Blog->new(parent_id => $blog1->id, name => 'blog2'); +$blog2->save; +my $entry11 = Entry->new(blog_id => $blog1->id, title => 'title11', text => 'first'); +$entry11->save; +my $entry12 = Entry->new(blog_id => $blog1->id, title => 'title12', text => 'second'); +$entry12->save; +my $entry21 = Entry->new(blog_id => $blog2->id, title => 'title21', text => 'first'); +$entry21->save; +my $entry22 = Entry->new(blog_id => $blog2->id, title => 'title22', text => 'second'); +$entry22->save; + +subtest 'as_subquery' => sub { + my $stmt = Blog->driver->prepare_statement('Blog', { name => 'foo' }, { fetchonly => ['id'] }); + + is(sql_normalize($stmt->as_subquery), sql_normalize(<<'EOF'), 'right sql'); +(SELECT blog.id FROM blog WHERE (blog.name = ?)) +EOF + is_deeply($stmt->{bind}, ['foo'], 'right bind values'); + + $stmt->as('mysubquery'); + + is(sql_normalize($stmt->as_subquery), sql_normalize(<<'EOF'), 'right sql'); +(SELECT blog.id FROM blog WHERE (blog.name = ?)) AS mysubquery +EOF +}; + +subtest 'do not aggregate bind twice' => sub { + + my $stmt = Blog->driver->prepare_statement('Blog', [{ name => $blog1->name }], {}); + my $subquery = Entry->driver->prepare_statement( + 'Entry', + ordered_hashref(blog_id => \'= blog.id', text => 'second'), + { fetchonly => ['id'], limit => 1 }); + $subquery->as('sub'); + $stmt->add_select($subquery); + $stmt->as_sql; + is scalar(@{ $stmt->bind }), 2; + $stmt->as_sql; + is scalar(@{ $stmt->bind }), 2; +}; + +subtest 'subquery in select clause' => sub { + + subtest 'fetch blogs and include a entry with specific text if any' => sub { + my $stmt = Blog->driver->prepare_statement('Blog', [{ name => $blog1->name }], {}); + my $subquery = Entry->driver->prepare_statement( + 'Entry', + ordered_hashref(blog_id => \'= blog.id', text => 'second'), + { fetchonly => ['id'], limit => 1 }); + $subquery->as('sub_alias'); + $stmt->add_select($subquery); + + my $expected = sql_normalize(<<'EOF'); +SELECT + blog.id, + blog.parent_id, + blog.name, + ( + SELECT entry.id + FROM entry + WHERE (entry.blog_id = blog.id) AND (entry.text = ?) + LIMIT 1 + ) AS sub_alias +FROM blog +WHERE ((name = ?)) +EOF + + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['second', $blog1->name], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 1; + is scalar(keys %{ $res[0]{column_values} }), 4; + is($res[0]{column_values}{id}, $blog1->id); + is($res[0]{column_values}{sub_alias}, $entry12->id); + }; + + subtest 'error occurs without alias' => sub { + my $stmt = Blog->driver->prepare_statement('Blog', [], {}); + my $subquery = Entry->driver->prepare_statement( + 'Entry', + [{ blog_id => \'= blog.id' }], { fetchonly => ['id'], limit => 1 }); + eval { $stmt->add_select($subquery) }; + like $@, qr/requires an alias/; + }; + + subtest 'set alias by add_select argument' => sub { + my $stmt = Blog->driver->prepare_statement('Blog', [{ name => $blog1->name }], {}); + my $subquery = Entry->driver->prepare_statement( + 'Entry', + ordered_hashref(blog_id => \'= blog.id', text => 'second'), + { fetchonly => ['id'], limit => 1 }); + $stmt->add_select($subquery, 'sub_alias'); + + my $expected = sql_normalize(<<'EOF'); +SELECT + blog.id, + blog.parent_id, + blog.name, + ( + SELECT entry.id + FROM entry + WHERE (entry.blog_id = blog.id) AND (entry.text = ?) + LIMIT 1 + ) AS sub_alias +FROM blog +WHERE ((name = ?)) +EOF + + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['second', $blog1->name], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 1; + is scalar(keys %{ $res[0]{column_values} }), 4; + is($res[0]{column_values}{id}, $blog1->id); + is($res[0]{column_values}{sub_alias}, $entry12->id); + }; +}; + +subtest 'select_map used in add_having' => sub { + my $stmt = Entry->driver->prepare_statement('Entry', {}, {}); + $stmt->add_select('count(*)', 'count'); + $stmt->group({column => 'blog_id'}); + $stmt->add_having(count => 2); + is sql_normalize($stmt->as_sql), sql_normalize(<<'EOF'); +SELECT entry.id, entry.blog_id, entry.title, entry.text, count(*) count +FROM entry +GROUP BY blog_id +HAVING (count(*) = ?) +EOF + is_deeply($stmt->{bind}, ['2'], 'right bind values'); + + my $subquery = Blog->driver->prepare_statement('Blog', {}, {}); + $stmt->add_select($subquery, 'sub'); + $stmt->add_having(sub => 3); + is sql_normalize($stmt->as_sql), sql_normalize(<<'EOF'); +SELECT + entry.id, entry.blog_id, entry.title, entry.text, count(*) count, + (SELECT blog.id, blog.parent_id, blog.name FROM blog) AS sub +FROM entry +GROUP BY blog_id +HAVING (count(*) = ?) AND (sub = ?) +EOF + is_deeply($stmt->{bind}, ['2', '3'], 'right bind values'); +}; + +subtest 'subquery in from clause' => sub { + + subtest 'blogs that has entries with specific text' => sub { + my $subquery = Entry->driver->prepare_statement( + 'Entry', + { text => 'second' }, { fetchonly => ['id', 'blog_id', 'text'] }); + $subquery->as('sub'); + my $stmt = Blog->driver->prepare_statement( + 'Blog', [ + { 'blog.id' => \'= sub.blog_id' }, + { 'blog.id' => [$blog1->id, $blog2->id] }, # FIXME: table prefix should be added automatically (MTC-30879) + { 'sub.text' => 'second' }, + ], + {}); + push @{ $stmt->from }, $subquery; + + my $expected = sql_normalize(<<'EOF'); +SELECT + blog.id, + blog.parent_id, + blog.name +FROM blog, + ( + SELECT entry.id, entry.blog_id, entry.text + FROM entry + WHERE (entry.text = ?) + ) AS sub +WHERE ((blog.id = sub.blog_id)) AND ((blog.id IN (?,?))) AND ((sub.text = ?)) +EOF + + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['second', $blog1->id, $blog2->id, 'second'], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 2; + is scalar(keys %{ $res[0]{column_values} }), 3; + is($res[0]{column_values}{id}, $blog1->id); + }; + + subtest 'select list includes sub query result' => sub { + my $subquery = Entry->driver->prepare_statement( + 'Entry', + { text => 'second' }, { fetchonly => ['id', 'blog_id'] }); + # $subquery->add_select('max(id)', 'max_entry_id'); + $subquery->as('sub'); + my $stmt = Blog->driver->prepare_statement( + 'Blog', [ + { 'blog.id' => \'= sub.blog_id' }, # FIXME: table prefix should be added automatically (MTC-30879) + { 'blog.id' => [$blog1->id, $blog2->id] }, # FIXME: table prefix should be added automatically (MTC-30879) + ], + {}); + push @{ $stmt->from }, $subquery; + $stmt->add_select('sub.id', 'entry_id'); + + my $expected = sql_normalize(<<'EOF'); +SELECT + blog.id, + blog.parent_id, + blog.name, + sub.id entry_id +FROM blog, + ( + SELECT entry.id, entry.blog_id + FROM entry + WHERE (entry.text = ?) + ) AS sub +WHERE ((blog.id = sub.blog_id)) AND ((blog.id IN (?,?))) +EOF + + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['second', $blog1->id, $blog2->id], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 2; + is scalar(keys %{ $res[0]{column_values} }), 4; + is($res[0]{column_values}{entry_id}, $entry12->id); + is($res[1]{column_values}{entry_id}, $entry22->id); + }; +}; + +subtest 'subquery in where clause' => sub { + + subtest 'entries that belongs to subquery blogs' => sub { + my $stmt = Entry->driver->prepare_statement( + 'Entry', + ordered_hashref( + text => 'first', + blog_id => { + op => 'IN', + value => Blog->driver->prepare_statement( + 'Blog', + { name => { op => 'LIKE', value => 'blog1', escape => '!' } }, + { fetchonly => ['id'] } + ), + } + ), + { limit => 4 }); + + my $expected = sql_normalize(<<'EOF'); +SELECT + entry.id, entry.blog_id, entry.title, entry.text +FROM + entry +WHERE + (entry.text = ?) + AND + (entry.blog_id IN (SELECT blog.id FROM blog WHERE (blog.name LIKE ? ESCAPE '!'))) +LIMIT 4 +EOF + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['first', 'blog1'], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 1; + is scalar(keys %{ $res[0]{column_values} }), 4; + is($res[0]{column_values}{id}, $blog1->id); + }; + + subtest 'case2' => sub { + my $stmt = Entry->driver->prepare_statement( + 'Entry', + [[ + { text => 'first' }, + '-or', + { + blog_id => { + op => 'IN', + value => Blog->driver->prepare_statement( + 'Blog', [ + { name => { op => 'LIKE', value => 'blog!%', escape => '!' } }, + { name => { op => 'LIKE', value => '!%2', escape => '!' } }, + ], + { fetchonly => ['id'] }) } + }, + '-or', + { text => 'second' }, + ], + { id => [$entry11->id, $entry12->id] }, + ], + { limit => 4 }); + + my $expected = sql_normalize(<<'EOF'); +SELECT + entry.id, entry.blog_id, entry.title, entry.text +FROM + entry +WHERE + ( + ((text = ?)) + OR + ((blog_id IN ( + SELECT blog.id + FROM blog + WHERE ((name LIKE ? ESCAPE '!')) AND ((name LIKE ? ESCAPE '!')) + ))) + OR + ((text = ?)) + ) AND ( + (id IN (?,?)) + ) +LIMIT 4 +EOF + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['first', 'blog!%', '!%2', 'second', $blog1->id, $blog2->id], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 2; + is scalar(keys %{ $res[0]{column_values} }), 4; + is($res[0]{column_values}{id}, $blog1->id); + is($res[1]{column_values}{id}, $blog2->id); + }; +}; + +subtest 'subquery in multiple clauses' => sub { + my $sub1 = Entry->driver->prepare_statement( + 'Entry', + ordered_hashref(blog_id => \'= blog.id', id => { op => '<', value => 99 }), { fetchonly => ['id'] }); + $sub1->select(['max(id)']); + my $sub2 = Entry->driver->prepare_statement('Entry', { text => 'second' }, { fetchonly => ['id'] }); + my $sub3 = Entry->driver->prepare_statement('Entry', { text => 'second' }, { fetchonly => ['blog_id'] }); + $sub1->as('sub1'); + $sub2->as('sub2'); + $sub3->as('sub3'); # this will be ommitted in where clause + my $stmt = Blog->driver->prepare_statement( + 'Blog', { id => { op => 'IN', value => $sub3 } }, + { sort => [{ column => 'blog.id' }, { column => 'sub1' }] }); + $stmt->add_select($sub1); + push @{ $stmt->from }, $sub2; + + my $expected = sql_normalize(<<'EOF'); +SELECT + blog.id, + blog.parent_id, + blog.name, + (SELECT max(id) FROM entry WHERE (entry.blog_id = blog.id) AND (entry.id < ?)) AS sub1 +FROM + blog, + (SELECT entry.id FROM entry WHERE (entry.text = ?)) AS sub2 +WHERE + (blog.id IN (SELECT entry.blog_id FROM entry WHERE (entry.text = ?))) +ORDER BY blog.id ASC, sub1 ASC +EOF + is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql'; + is_deeply($stmt->{bind}, ['99', 'second', 'second'], 'right bind values'); + my @res = Blog->driver->search('Blog', $stmt); + is scalar(@res), 4; + is($res[0]{column_values}{id}, $blog1->id); + is($res[0]{column_values}{sub1}, $entry12->id); + is($res[1]{column_values}{id}, $blog1->id); + is($res[1]{column_values}{sub1}, $entry12->id); + is($res[2]{column_values}{id}, $blog2->id); + is($res[2]{column_values}{sub1}, $entry22->id); + is($res[3]{column_values}{id}, $blog2->id); + is($res[3]{column_values}{sub1}, $entry22->id); +}; + +sub sql_normalize { + my $sql = shift; + $sql =~ s{\s+}{ }g; + $sql =~ s{ $}{}g; + $sql =~ s{\( }{(}g; + $sql =~ s{ \)}{)}g; + $sql =~ s{([\(\)]) ([\(\)])}{$1$2}g; + $sql; +} + +END { + disconnect_all(qw/Blog Entry/); + teardown_dbs(qw( global )); +} + +done_testing; diff --git a/t/11-sql.t b/t/11-sql.t index 56a21ad..6229472 100644 --- a/t/11-sql.t +++ b/t/11-sql.t @@ -3,7 +3,7 @@ use strict; use Data::ObjectDriver::SQL; -use Test::More tests => 113; +use Test::More; my $stmt = ns(); ok($stmt, 'Created SQL object'); @@ -284,15 +284,34 @@ $stmt->add_select('bar'); $stmt->from([ qw( baz ) ]); is($stmt->as_sql, "SELECT foo, bar\nFROM baz\n"); -$stmt = ns(); -$stmt->add_select('f.foo' => 'foo'); -$stmt->add_select('COUNT(*)' => 'count'); -$stmt->from([ qw( baz ) ]); -is($stmt->as_sql, "SELECT f.foo, COUNT(*) count\nFROM baz\n"); -my $map = $stmt->select_map; -is(scalar(keys %$map), 2); -is($map->{'f.foo'}, 'foo'); -is($map->{'COUNT(*)'}, 'count'); +subtest 'SQL functions' => sub { + $stmt = ns(); + $stmt->add_select('f.foo' => 'foo'); + $stmt->add_select('COUNT(*)' => 'count'); + $stmt->from([ qw( baz ) ]); + is($stmt->as_sql, "SELECT f.foo, COUNT(*) count\nFROM baz\n"); + my $map = $stmt->select_map; + is(scalar(keys %$map), 2); + is_deeply($map, {'f.foo' => 'foo', 'COUNT(*)' => 'count'}, 'right map'); + + $stmt = ns(); + $stmt->add_select('count(foo)'); + $stmt->add_select('count(bar)'); + $stmt->from([qw( baz )]); + is($stmt->as_sql, "SELECT count(foo), count(bar)\nFROM baz\n"); + my $map = $stmt->select_map; + is(scalar(keys %$map), 2); + is_deeply($map, {'count(foo)' => 'count(foo)', 'count(bar)' => 'count(bar)'}, 'right map'); + + $stmt = ns(); + $stmt->add_select('count(foo)', 'count1'); + $stmt->add_select('count(bar)', 'count2'); + $stmt->from([qw( baz )]); + is($stmt->as_sql, "SELECT count(foo) count1, count(bar) count2\nFROM baz\n"); + my $map = $stmt->select_map; + is(scalar(keys %$map), 2); + is_deeply($map, {'count(foo)' => 'count1', 'count(bar)' => 'count2'}, 'right map'); +}; # HAVING $stmt = ns(); @@ -387,3 +406,5 @@ is( ); sub ns { Data::ObjectDriver::SQL->new } + +done_testing; diff --git a/t/lib/sql/Blog.pm b/t/lib/sql/Blog.pm new file mode 100644 index 0000000..807bc71 --- /dev/null +++ b/t/lib/sql/Blog.pm @@ -0,0 +1,17 @@ +# $Id$ + +package Blog; +use strict; +use warnings; +use base 'Data::ObjectDriver::BaseObject'; +use Data::ObjectDriver::Driver::DBI; +use DodTestUtil; + +__PACKAGE__->install_properties({ + columns => ['id', 'parent_id', 'name'], + datasource => 'blog', + primary_key => 'id', + driver => Data::ObjectDriver::Driver::DBI->new(dsn => DodTestUtil::dsn('global')), +}); + +1; diff --git a/t/lib/sql/Entry.pm b/t/lib/sql/Entry.pm new file mode 100644 index 0000000..90bb474 --- /dev/null +++ b/t/lib/sql/Entry.pm @@ -0,0 +1,17 @@ +# $Id$ + +package Entry; +use strict; +use warnings; +use base 'Data::ObjectDriver::BaseObject'; +use Data::ObjectDriver::Driver::DBI; +use DodTestUtil; + +__PACKAGE__->install_properties({ + columns => ['id', 'blog_id', 'title', 'text'], + datasource => 'entry', + primary_key => 'id', + driver => Data::ObjectDriver::Driver::DBI->new(dsn => DodTestUtil::dsn('global')), +}); + +1; diff --git a/t/schemas/blog.sql b/t/schemas/blog.sql new file mode 100644 index 0000000..82ed167 --- /dev/null +++ b/t/schemas/blog.sql @@ -0,0 +1,5 @@ +CREATE TABLE blog ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + parent_id INTEGER, + name VARCHAR(50) +) diff --git a/t/schemas/entry.sql b/t/schemas/entry.sql new file mode 100644 index 0000000..7328ab7 --- /dev/null +++ b/t/schemas/entry.sql @@ -0,0 +1,6 @@ +CREATE TABLE entry ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + blog_id INTEGER, + title VARCHAR(50), + text MEDIUMTEXT +)