Build.bat
META.yml
Makefile
+Makefile.old
README
_build/
blib/
test_requires 'Test::Builder' => 0.33;
test_requires 'Test::Warn' => 0.11;
test_requires 'Test::Exception' => 0;
+test_requires 'Test::Deep' => 0;
install_script 'script/dbicadmin';
--- /dev/null
+NOTE: do not merge this file but DELETE IT on merge
+
+New in this branch:
+
+ * Converted test cases to use SQL::Abstract::Test.
+ * that way the number of extra parens does not matter).
+
+ * Removed a wrong TODO test: 95sql_maker_quote.t/"order_by with quoting needs fixing (ash/castaway)"
+ * it never passed and never will (SQLA should not parse the strings in order_by)
+ * asked by mst
+
+ * Made a TODO test non-TODO: 95sql_maker_quote.t/"select attr with star needs fixing (mst/nate)"
+ * this works with SQLA 1.50
+
+ * Removed dead code from DBI::Class::Storage::DBI (sub _RowNumberOver).
+ * neither DBIC, nor SQLA, nor SQLA::Limit uses this (at least not the current versions)
+
+ * Added test cases for every supported order_by syntax.
+ * Made DBIC::SQL::Abstract pass on order_by hashref ({-desc => 'colname'}) to SQL::Abstract.
+ * this is the blessed way of doing order by
+ * new SQLA supports it
+ * formerly DBIC considered this as an error
+
+ * Bumped SQL::Abstract version dependency.
+ * the testcase changes broke compatibility with old SQLA
+
+ * Added test cases to test if arrayref bind values in insert/update are passed through sql_maker intact.
+ * Added test cases to test if arrayref bind values work with a PostgreSQL array type.
+ * Added 'array_datatypes' parameter to the sql_maker constructor.
+ * formerly SQLA considered these as literal SQL with bind values, now that is \['literal SQL', @bind]
+ * the new syntax is consistent (works the same in insert/update and where conditions)
+ * fortunately 'array_datatypes' is simply ignored by old SQLA (at least with current version..)
+ * DBD::Pg can use arrayref bind values for PostgreSQL array types
The limit dialect can also be set at connect time by specifying a
C<limit_dialect> key in the final hash as shown above.
+=head2 Working with PostgreSQL array types
+
+If your SQL::Abstract version (>= 1.50) supports it, you can assign to
+PostgreSQL array values by passing array references in the C<\%columns>
+(C<\%vals>) hashref of the L<DBIx::Class::ResultSet/create> and
+L<DBIx::Class::Row/update> family of methods:
+
+ $resultset->create({
+ numbers => [1, 2, 3]
+ });
+
+ $row->update(
+ {
+ numbers => [1, 2, 3]
+ },
+ {
+ day => '2008-11-24'
+ }
+ );
+
+In conditions (eg. C<\%cond> in the L<DBIx::Class::ResultSet/search> family of
+methods) you cannot directly use array references (since this is interpreted as
+a list of values to be C<OR>ed), but you can use the following syntax to force
+passing them as bind values:
+
+ $resultset->search(
+ {
+ numbers => \[ '= ?', [1, 2, 3] ]
+ }
+ );
+
+See L<SQL::Abstract/array_datatypes> and L<SQL::Abstract/Literal SQL with
+placeholders and bind values (subqueries)> for more explanation.
+
=head1 BOOTSTRAPPING/MIGRATING
=head2 Easy migration from class-based to schema-based setup
specify an order. (The scalar ref causes it to be passed as raw sql to the DB,
so you will need to manually quote things as appropriate.)
+If your L<SQL::Abstract> version supports it (>=1.50), you can also use
+C<{-desc => 'year'}>, which takes care of the quoting for you. This is the
+recommended syntax.
+
=head2 columns
=over 4
$self;
}
-sub _RowNumberOver {
- my ($self, $sql, $order, $rows, $offset ) = @_;
-
- $offset += 1;
- my $last = $rows + $offset;
- my ( $order_by ) = $self->_order_by( $order );
-
- $sql = <<"";
-SELECT * FROM
-(
- SELECT Q1.*, ROW_NUMBER() OVER( ) AS ROW_NUM FROM (
- $sql
- $order_by
- ) Q1
-) Q2
-WHERE ROW_NUM BETWEEN $offset AND $last
-
- return $sql;
-}
-
-
# While we're at it, this should make LIMIT queries more efficient,
# without digging into things too deeply
use Scalar::Util 'blessed';
if (defined $_[0]->{order_by}) {
$ret .= $self->_order_by($_[0]->{order_by});
}
+ if (grep { $_ =~ /^-(desc|asc)/i } keys %{$_[0]}) {
+ return $self->SUPER::_order_by($_[0]);
+ }
} elsif (ref $_[0] eq 'SCALAR') {
$ret = $self->_sqlcase(' order by ').${ $_[0] };
} elsif (ref $_[0] eq 'ARRAY' && @{$_[0]}) {
sub _sql_maker_args {
my ($self) = @_;
- return ( bindtype=>'columns', limit_dialect => $self->dbh, %{$self->_sql_maker_opts} );
+ return ( bindtype=>'columns', array_datatypes => 1, limit_dialect => $self->dbh, %{$self->_sql_maker_opts} );
}
sub sql_maker {
}
foreach my $data (@data) {
- $data = ref $data ? ''.$data : $data; # stringify args
+ my $ref = ref $data;
+ $data = $ref && $ref ne 'ARRAY' ? ''.$data : $data; # stringify args (except arrayrefs)
$sth->bind_param($placeholder_index, $data, $attributes);
$placeholder_index++;
use Test::More;
use IO::File;
+use DBIC::SqlMakerTest;
BEGIN {
eval "use DBD::SQLite";
plan $@
? ( skip_all => 'needs DBD::SQLite for testing' )
- : ( tests => 6 );
+ : ( tests => 7 );
}
use lib qw(t/lib);
use_ok('DBICTest');
+use_ok('DBIC::DebugObj');
my $schema = DBICTest->init_schema();
-my $orig_debugcb = $schema->storage->debugcb;
-my $orig_debug = $schema->storage->debug;
-
diag('Testing against ' . join(' ', map { $schema->storage->dbh->get_info($_) } qw/17 18/));
$schema->storage->sql_maker->quote_char('`');
$schema->storage->sql_maker->name_sep('.');
-my $sql = '';
-
-$schema->storage->debugcb(sub { $sql = $_[1] });
+my ($sql, @bind) = ('');
+$schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind));
$schema->storage->debug(1);
my $rs;
{ 'me.year' => 2001, 'artist.name' => 'Caterwauler McCrae' },
{ join => 'artist' });
eval { $rs->count };
-like($sql, qr/\QSELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )\E/, 'got correct SQL for count query with quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ "SELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
+ 'got correct SQL for count query with quoting'
+);
my $order = 'year DESC';
$rs = $schema->resultset('CD')->search({},
{ 'me.year' => 2001, 'artist.name' => 'Caterwauler McCrae' },
{ join => 'artist' });
eval { $rs->count };
-like($sql, qr/\QSELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )\E/, 'got correct SQL for count query with bracket quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ "SELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
+ 'got correct SQL for count query with bracket quoting'
+);
my %data = (
name => 'Bill',
$schema->storage->sql_maker->name_sep('.');
is($schema->storage->sql_maker->update('group', \%data), 'UPDATE `group` SET `name` = ?, `order` = ?', 'quoted table names for UPDATE');
-
-$schema->storage->debugcb($orig_debugcb);
-$schema->storage->debug($orig_debug);
use Test::More;
use IO::File;
+use DBIC::SqlMakerTest;
BEGIN {
eval "use DBD::SQLite";
plan $@
? ( skip_all => 'needs DBD::SQLite for testing' )
- : ( tests => 6 );
+ : ( tests => 7 );
}
use lib qw(t/lib);
use_ok('DBICTest');
-my $schema = DBICTest->init_schema();
+use_ok('DBIC::DebugObj');
-my $orig_debugcb = $schema->storage->debugcb;
-my $orig_debug = $schema->storage->debug;
+my $schema = DBICTest->init_schema();
diag('Testing against ' . join(' ', map { $schema->storage->dbh->get_info($_) } qw/17 18/));
{ quote_char => '`', name_sep => '.' },
);
-my $sql = '';
-$schema->storage->debugcb(sub { $sql = $_[1] });
+my ($sql, @bind) = ('');
+$schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind)),
$schema->storage->debug(1);
my $rs;
{ 'me.year' => 2001, 'artist.name' => 'Caterwauler McCrae' },
{ join => 'artist' });
eval { $rs->count };
-like($sql, qr/\QSELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )\E/, 'got correct SQL for count query with quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ "SELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
+ 'got correct SQL for count query with quoting'
+);
my $order = 'year DESC';
$rs = $schema->resultset('CD')->search({},
undef,
{ AutoCommit => 1, quote_char => [qw/[ ]/], name_sep => '.' }
);
-$schema->storage->debugcb(sub { $sql = $_[1] });
+
+$schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind)),
$schema->storage->debug(1);
$rs = $schema->resultset('CD')->search(
{ 'me.year' => 2001, 'artist.name' => 'Caterwauler McCrae' },
{ join => 'artist' });
eval { $rs->count };
-like($sql, qr/\QSELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )\E/, 'got correct SQL for count query with bracket quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ "SELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
+ 'got correct SQL for count query with bracket quoting'
+);
my %data = (
name => 'Bill',
);
is($schema->storage->sql_maker->update('group', \%data), 'UPDATE `group` SET `name` = ?, `order` = ?', 'quoted table names for UPDATE');
-
-$schema->storage->debugcb($orig_debugcb);
-$schema->storage->debug($orig_debug);
use Test::More;
#use DBIx::Class::Storage::DBI;
+use DBIC::SqlMakerTest;
use DBIx::Class::Storage::DBI::Oracle::WhereJoins;
plan tests => 4;
# search with undefined or empty $cond
# my ($self, $table, $fields, $where, $order, @rest) = @_;
-is($sa->select([
+my ($sql, @bind) = $sa->select(
+ [
{ me => "cd" },
[
{ "-join_type" => "LEFT", artist => "artist" },
],
[ 'cd.cdid', 'cd.artist', 'cd.title', 'cd.year', 'artist.artistid', 'artist.name' ],
undef,
- undef),
- 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( artist.artistid(+) = me.artist )', 'WhereJoins search with empty where clause');
+ undef
+);
+is_same_sql_bind(
+ $sql, \@bind,
+ 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( artist.artistid(+) = me.artist )', [],
+ 'WhereJoins search with empty where clause'
+);
-is($sa->select([
+($sql, @bind) = $sa->select(
+ [
{ me => "cd" },
[
{ "-join_type" => "", artist => "artist" },
],
[ 'cd.cdid', 'cd.artist', 'cd.title', 'cd.year', 'artist.artistid', 'artist.name' ],
{ 'artist.artistid' => 3 },
- undef),
- 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( ( ( artist.artistid = me.artist ) AND ( artist.artistid = ? ) ) )', 'WhereJoins search with where clause');
+ undef
+);
+is_same_sql_bind(
+ $sql, \@bind,
+ 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( ( ( artist.artistid = me.artist ) AND ( artist.artistid = ? ) ) )', [3],
+ 'WhereJoins search with where clause'
+);
-is($sa->select([
+($sql, @bind) = $sa->select(
+ [
{ me => "cd" },
[
{ "-join_type" => "LEFT", artist => "artist" },
],
[ 'cd.cdid', 'cd.artist', 'cd.title', 'cd.year', 'artist.artistid', 'artist.name' ],
[{ 'artist.artistid' => 3 }, { 'me.cdid' => 5 }],
- undef),
- 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( ( ( artist.artistid(+) = me.artist ) AND ( ( ( artist.artistid = ? ) OR ( me.cdid = ? ) ) ) ) )', 'WhereJoins search with or in where clause');
+ undef
+);
+is_same_sql_bind(
+ $sql, \@bind,
+ 'SELECT cd.cdid, cd.artist, cd.title, cd.year, artist.artistid, artist.name FROM cd me, artist artist WHERE ( ( ( artist.artistid(+) = me.artist ) AND ( ( ( artist.artistid = ? ) OR ( me.cdid = ? ) ) ) ) )', [3, 5],
+ 'WhereJoins search with or in where clause'
+);
use warnings;
use Test::More;
+use Test::Exception;
use lib qw(t/lib);
use DBICTest;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw/Core/);
- __PACKAGE__->table('casecheck');
+ __PACKAGE__->table('testschema.casecheck');
__PACKAGE__->add_columns(qw/id name NAME uc_name/);
__PACKAGE__->column_info_from_storage(1);
__PACKAGE__->set_primary_key('id');
}
+{
+ package DBICTest::Schema::ArrayTest;
+
+ use strict;
+ use warnings;
+ use base 'DBIx::Class';
+
+ __PACKAGE__->load_components(qw/Core/);
+ __PACKAGE__->table('testschema.array_test');
+ __PACKAGE__->add_columns(qw/id arrayfield/);
+ __PACKAGE__->column_info_from_storage(1);
+ __PACKAGE__->set_primary_key('id');
+
+}
+
my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_PG_${_}" } qw/DSN USER PASS/};
#warn "$dsn $user $pass";
-plan skip_all => 'Set $ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test'
- . ' (note: creates and drops tables named artist and casecheck!)' unless ($dsn && $user);
+plan skip_all => 'Set $ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test '.
+ '(note: This test drops and creates tables called \'artist\', \'casecheck\', \'array_test\' and \'sequence_test\''.
+ ' as well as following sequences: \'pkid1_seq\', \'pkid2_seq\' and \'nonpkid_seq\''.
+ ' as well as following schemas: \'testschema\'!)'
+ unless ($dsn && $user && $pass);
-plan tests => 32;
-DBICTest::Schema->load_classes( 'Casecheck' );
+plan tests => 37;
+
+DBICTest::Schema->load_classes( 'Casecheck', 'ArrayTest' );
my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
# Check that datetime_parser returns correctly before we explicitly connect.
{
local $SIG{__WARN__} = sub {};
$dbh->do("CREATE SCHEMA testschema;");
- $dbh->do("CREATE TABLE testschema.artist (artistid serial PRIMARY KEY, name VARCHAR(100), rank INTEGER NOT NULL DEFAULT '13', charfield CHAR(10));");
+ $dbh->do("CREATE TABLE testschema.artist (artistid serial PRIMARY KEY, name VARCHAR(100), rank INTEGER NOT NULL DEFAULT '13', charfield CHAR(10), arrayfield INTEGER[]);");
$dbh->do("CREATE TABLE testschema.sequence_test (pkid1 integer, pkid2 integer, nonpkid integer, name VARCHAR(100), CONSTRAINT pk PRIMARY KEY(pkid1, pkid2));");
$dbh->do("CREATE SEQUENCE pkid1_seq START 1 MAXVALUE 999999 MINVALUE 0");
$dbh->do("CREATE SEQUENCE pkid2_seq START 10 MAXVALUE 999999 MINVALUE 0");
$dbh->do("CREATE SEQUENCE nonpkid_seq START 20 MAXVALUE 999999 MINVALUE 0");
ok ( $dbh->do('CREATE TABLE testschema.casecheck (id serial PRIMARY KEY, "name" VARCHAR(1), "NAME" VARCHAR(2), "UC_NAME" VARCHAR(3));'), 'Creation of casecheck table');
+ ok ( $dbh->do('CREATE TABLE testschema.array_test (id serial PRIMARY KEY, arrayfield INTEGER[]);'), 'Creation of array_test table');
}
# This is in Core now, but it's here just to test that it doesn't break
'size' => 10,
'default_value' => undef,
},
+ 'arrayfield' => {
+ 'data_type' => 'integer[]',
+ 'is_nullable' => 1,
+ 'size' => undef,
+ 'default_value' => undef,
+ },
};
is_deeply($type_info, $test_type_info,
'columns_info_for - column data types');
+SKIP: {
+ skip "SQL::Abstract < 1.49 does not pass through arrayrefs", 4
+ if $SQL::Abstract::VERSION < 1.49;
+
+ lives_ok {
+ $schema->resultset('ArrayTest')->create({
+ arrayfield => [1, 2],
+ });
+ } 'inserting arrayref as pg array data';
+
+ lives_ok {
+ $schema->resultset('ArrayTest')->update({
+ arrayfield => [3, 4],
+ });
+ } 'updating arrayref as pg array data';
+
+ $schema->resultset('ArrayTest')->create({
+ arrayfield => [5, 6],
+ });
+
+ my $count;
+ lives_ok {
+ $count = $schema->resultset('ArrayTest')->search({
+ arrayfield => \[ '= ?' => [3, 4] ], #TODO anything less ugly than this?
+ })->count;
+ } 'comparing arrayref to pg array data does not blow up';
+ is($count, 1, 'comparing arrayref to pg array data gives correct result');
+}
+
+
my $name_info = $schema->source('Casecheck')->column_info( 'name' );
is( $name_info->{size}, 1, "Case sensitive matching info for 'name'" );
$dbh->do("DROP TABLE testschema.artist;");
$dbh->do("DROP TABLE testschema.casecheck;");
$dbh->do("DROP TABLE testschema.sequence_test;");
+ $dbh->do("DROP TABLE testschema.array_test;");
$dbh->do("DROP SEQUENCE pkid1_seq");
$dbh->do("DROP SEQUENCE pkid2_seq");
$dbh->do("DROP SEQUENCE nonpkid_seq");
use lib qw(t/lib);
use DBICTest;
use Data::Dumper;
+use DBIC::SqlMakerTest;
my $schema = DBICTest->init_schema();
. 'child.father_id ) JOIN person mother ON ( mother.person_id '
. '= child.mother_id )'
;
-is( $sa->_recurse_from(@j), $match, 'join 1 ok' );
+is_same_sql_bind(
+ $sa->_recurse_from(@j), [],
+ $match, [],
+ 'join 1 ok'
+);
my @j2 = (
{ mother => 'person' },
. ' father.person_id = child.father_id )) ON ( mother.person_id = '
. 'child.mother_id )'
;
-is( $sa->_recurse_from(@j2), $match, 'join 2 ok' );
+is_same_sql_bind(
+ $sa->_recurse_from(@j2), [],
+ $match, [],
+ 'join 2 ok'
+);
+
my @j3 = (
{ child => 'person' },
. '= child.mother_id )'
;
-is( $sa->_recurse_from(@j3), $match, 'join 3 (inner join) ok');
+is_same_sql_bind(
+ $sa->_recurse_from(@j3), [],
+ $match, [],
+ 'join 3 (inner join) ok'
+);
my @j4 = (
{ mother => 'person' },
. ' father.person_id = child.father_id )) ON ( mother.person_id = '
. 'child.mother_id )'
;
-is( $sa->_recurse_from(@j4), $match, 'join 4 (nested joins + join types) ok');
+is_same_sql_bind(
+ $sa->_recurse_from(@j4), [],
+ $match, [],
+ 'join 4 (nested joins + join types) ok'
+);
my @j5 = (
{ child => 'person' },
. 'child.father_id ) JOIN person mother ON ( mother.person_id '
. '= child.mother_id )'
;
-is( $sa->_recurse_from(@j5), $match, 'join 5 (SCALAR reference for ON statement) ok' );
+is_same_sql_bind(
+ $sa->_recurse_from(@j5), [],
+ $match, [],
+ 'join 5 (SCALAR reference for ON statement) ok'
+);
my @j6 = (
{ child => 'person' },
use Test::More;
use lib qw(t/lib);
use DBICTest;
+use DBIC::DebugObj;
+use DBIC::SqlMakerTest;
my $schema = DBICTest->init_schema();
-plan tests => 6;
+plan tests => 7;
ok ( $schema->storage->debug(1), 'debug' );
ok ( defined(
# test trace output correctness for bind params
{
- my $sql = '';
+ my ($sql, @bind) = ('');
$schema->storage->debugcb( sub { $sql = $_[1] } );
my @cds = $schema->resultset('CD')->search( { artist => 1, cdid => { -between => [ 1, 3 ] }, } );
- like(
- $sql,
- qr/\QSELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track FROM cd me WHERE ( artist = ? AND cdid BETWEEN ? AND ? ): '1', '1', '3'\E/,
- 'got correct SQL with all bind parameters'
+ is_same_sql_bind(
+ $sql, [],
+ "SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track FROM cd me WHERE ( artist = ? AND cdid BETWEEN ? AND ? ): '1', '1', '3'", [],
+ 'got correct SQL with all bind parameters (debugcb)'
+ );
+
+ $schema->storage->debugcb(undef);
+ $schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind));
+ @cds = $schema->resultset('CD')->search( { artist => 1, cdid => { -between => [ 1, 3 ] }, } );
+ is_same_sql_bind(
+ $sql, \@bind,
+ "SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track FROM cd me WHERE ( artist = ? AND cdid BETWEEN ? AND ? )", ["'1'", "'1'", "'3'"],
+ 'got correct SQL with all bind parameters (debugobj)'
);
}
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More;
+use DBIC::SqlMakerTest;
+
+
+BEGIN {
+ eval "use DBD::SQLite";
+ plan $@
+ ? ( skip_all => 'needs DBD::SQLite for testing' )
+ : ( tests => 3 );
+}
+
+use lib qw(t/lib);
+
+use_ok('DBICTest');
+
+my $schema = DBICTest->init_schema();
+
+my $sql_maker = $schema->storage->sql_maker;
+
+
+SKIP: {
+ skip "SQL::Abstract < 1.49 does not pass through arrayrefs", 2
+ if $SQL::Abstract::VERSION < 1.49;
+
+ my ($sql, @bind) = $sql_maker->insert(
+ 'lottery',
+ {
+ 'day' => '2008-11-16',
+ 'numbers' => [13, 21, 34, 55, 89]
+ }
+ );
+
+ is_same_sql_bind(
+ $sql, \@bind,
+ q/INSERT INTO lottery (day, numbers) VALUES (?, ?)/,
+ [ ['day' => '2008-11-16'], ['numbers' => [13, 21, 34, 55, 89]] ],
+ 'sql_maker passes arrayrefs in insert'
+ );
+
+
+ ($sql, @bind) = $sql_maker->update(
+ 'lottery',
+ {
+ 'day' => '2008-11-16',
+ 'numbers' => [13, 21, 34, 55, 89]
+ }
+ );
+
+ is_same_sql_bind(
+ $sql, \@bind,
+ q/UPDATE lottery SET day = ?, numbers = ?/,
+ [ ['day' => '2008-11-16'], ['numbers' => [13, 21, 34, 55, 89]] ],
+ 'sql_maker passes arrayrefs in update'
+ );
+}
use warnings;
use Test::More;
+use DBIC::SqlMakerTest;
BEGIN {
eval "use DBD::SQLite";
plan $@
? ( skip_all => 'needs DBD::SQLite for testing' )
- : ( tests => 8 );
+ : ( tests => 12 );
}
use lib qw(t/lib);
$sql_maker->quote_char('`');
$sql_maker->name_sep('.');
-my ($sql,) = $sql_maker->select(
+my ($sql, @bind) = $sql_maker->select(
[
{
'me' => 'cd'
undef
);
-is($sql,
- q/SELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )/,
- 'got correct SQL for count query with quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT COUNT( * ) FROM `cd` `me` JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )/, [ ['artist.name' => 'Caterwauler McCrae'], ['me.year' => 2001] ],
+ 'got correct SQL and bind parameters for count query with quoting'
+);
+
-($sql,) = $sql_maker->select(
+($sql, @bind) = $sql_maker->select(
[
{
'me' => 'cd'
'me.year'
],
undef,
+ 'year DESC',
+ undef,
+ undef
+);
+
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY `year DESC`/, [],
+ 'scalar ORDER BY okay (single value)'
+);
+
+
+($sql, @bind) = $sql_maker->select(
[
- 'year DESC'
+ {
+ 'me' => 'cd'
+ }
+ ],
+ [
+ 'me.cdid',
+ 'me.artist',
+ 'me.title',
+ 'me.year'
+ ],
+ undef,
+ [
+ 'year DESC',
+ 'title ASC'
],
undef,
undef
);
-TODO: {
- local $TODO = "order_by with quoting needs fixing (ash/castaway)";
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY `year DESC`, `title ASC`/, [],
+ 'scalar ORDER BY okay (multiple values)'
+);
+
+SKIP: {
+ skip "SQL::Abstract < 1.49 does not support hashrefs in order_by", 2
+ if $SQL::Abstract::VERSION < 1.49;
+
+ ($sql, @bind) = $sql_maker->select(
+ [
+ {
+ 'me' => 'cd'
+ }
+ ],
+ [
+ 'me.cdid',
+ 'me.artist',
+ 'me.title',
+ 'me.year'
+ ],
+ undef,
+ { -desc => 'year' },
+ undef,
+ undef
+ );
+
+ is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY `year` DESC/, [],
+ 'hashref ORDER BY okay (single value)'
+ );
+
+
+ ($sql, @bind) = $sql_maker->select(
+ [
+ {
+ 'me' => 'cd'
+ }
+ ],
+ [
+ 'me.cdid',
+ 'me.artist',
+ 'me.title',
+ 'me.year'
+ ],
+ undef,
+ [
+ { -desc => 'year' },
+ { -asc => 'title' }
+ ],
+ undef,
+ undef
+ );
+
+ is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY `year` DESC, `title` ASC/, [],
+ 'hashref ORDER BY okay (multiple values)'
+ );
- is($sql,
- q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY `year` DESC/,
- 'quoted ORDER BY with DESC okay');
}
-TODO: {
- local $TODO = "select attr with star needs fixing (mst/nate)";
- ($sql,) = $sql_maker->select(
+($sql, @bind) = $sql_maker->select(
[
{
'me' => 'cd'
}
],
[
- 'me.*'
+ 'me.cdid',
+ 'me.artist',
+ 'me.title',
+ 'me.year'
],
undef,
- [],
+ \'year DESC',
undef,
- undef
- );
+ undef
+);
- is($sql, q/SELECT `me`.* FROM `cd` `me`/, 'select attr with me.* is right');
-}
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY year DESC/, [],
+ 'did not quote ORDER BY with scalarref (single value)'
+);
-($sql,) = $sql_maker->select(
+
+($sql, @bind) = $sql_maker->select(
[
{
'me' => 'cd'
],
undef,
[
- \'year DESC'
+ \'year DESC',
+ \'title ASC'
],
undef,
undef
);
-is($sql,
- q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY year DESC/,
- 'did not quote ORDER BY with scalarref');
-
-my %data = (
- name => 'Bill',
- order => 12
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.`cdid`, `me`.`artist`, `me`.`title`, `me`.`year` FROM `cd` `me` ORDER BY year DESC, title ASC/, [],
+ 'did not quote ORDER BY with scalarref (multiple values)'
);
-my @binds;
-($sql,@binds) = $sql_maker->update(
+($sql, @bind) = $sql_maker->update(
'group',
{
'order' => '12',
}
);
-is($sql,
- q/UPDATE `group` SET `name` = ?, `order` = ?/,
- 'quoted table names for UPDATE');
+is_same_sql_bind(
+ $sql, \@bind,
+ q/UPDATE `group` SET `name` = ?, `order` = ?/, [ ['name' => 'Bill'], ['order' => '12'] ],
+ 'quoted table names for UPDATE'
+);
+
+SKIP: {
+ skip "select attr with star does not work in SQL::Abstract < 1.49", 1
+ if $SQL::Abstract::VERSION < 1.49;
+
+ ($sql, @bind) = $sql_maker->select(
+ [
+ {
+ 'me' => 'cd'
+ }
+ ],
+ [
+ 'me.*'
+ ],
+ undef,
+ [],
+ undef,
+ undef
+ );
+
+ is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT `me`.* FROM `cd` `me`/, [],
+ 'select attr with me.* is right'
+ );
+}
+
$sql_maker->quote_char([qw/[ ]/]);
-($sql,) = $sql_maker->select(
+($sql, @bind) = $sql_maker->select(
[
{
'me' => 'cd'
undef
);
-is($sql,
- q/SELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )/,
- 'got correct SQL for count query with bracket quoting');
+is_same_sql_bind(
+ $sql, \@bind,
+ q/SELECT COUNT( * ) FROM [cd] [me] JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )/, [ ['artist.name' => 'Caterwauler McCrae'], ['me.year' => 2001] ],
+ 'got correct SQL and bind parameters for count query with bracket quoting'
+);
-($sql,@binds) = $sql_maker->update(
+($sql, @bind) = $sql_maker->update(
'group',
{
'order' => '12',
}
);
-is($sql,
- q/UPDATE [group] SET [name] = ?, [order] = ?/,
- 'bracket quoted table names for UPDATE');
+is_same_sql_bind(
+ $sql, \@bind,
+ q/UPDATE [group] SET [name] = ?, [order] = ?/, [ ['name' => 'Bill'], ['order' => '12'] ],
+ 'bracket quoted table names for UPDATE'
+);
BEGIN {
eval "use DBD::mysql; use SQL::Translator 0.09;";
plan $@
- ? ( skip_all => 'needs SQL::Translator 0.09 for testing' )
+ ? ( skip_all => 'needs DBD::mysql and SQL::Translator 0.09 for testing' )
: ( tests => 114 );
}
--- /dev/null
+package # hide from PAUSE
+ DBIC::DebugObj;
+
+use strict;
+use warnings;
+
+use Exporter;
+use Class::C3;
+
+use base qw/DBIx::Class::Storage::Statistics/;
+use base qw/Exporter/;
+use base qw/Class::Accessor::Fast/;
+
+__PACKAGE__->mk_accessors( qw/dbictest_sql_ref dbictest_bind_ref/ );
+
+
+=head2 new(PKG, SQL_REF, BIND_REF, ...)
+
+Creates a new instance that on subsequent queries will store
+the generated SQL to the scalar pointed to by SQL_REF and bind
+values to the array pointed to by BIND_REF.
+
+=cut
+
+sub new {
+ my $pkg = shift;
+ my $sql_ref = shift;
+ my $bind_ref = shift;
+
+ my $self = $pkg->SUPER::new(@_);
+
+ $self->debugfh(undef);
+
+ $self->dbictest_sql_ref($sql_ref);
+ $self->dbictest_bind_ref($bind_ref);
+
+ return $self;
+}
+
+sub query_start {
+ my $self = shift;
+
+ (${$self->dbictest_sql_ref}, @{$self->dbictest_bind_ref}) = @_;
+}
+
+sub query_end { }
+
+sub txn_start { }
+
+sub txn_commit { }
+
+sub txn_rollback { }
+
+1;
--- /dev/null
+package # hide from PAUSE
+ DBIC::SqlMakerTest;
+
+use strict;
+use warnings;
+
+use base qw/Test::Builder::Module Exporter/;
+
+use Exporter;
+
+our @EXPORT = qw/
+ &is_same_sql_bind
+ &eq_sql
+ &eq_bind
+/;
+
+
+{
+ package # hide from PAUSE
+ DBIC::SqlMakerTest::SQLATest;
+
+ # replacement for SQL::Abstract::Test if not available
+
+ use strict;
+ use warnings;
+
+ use base qw/Test::Builder::Module Exporter/;
+
+ use Scalar::Util qw(looks_like_number blessed reftype);
+ use Data::Dumper;
+ use Test::Builder;
+ use Test::Deep qw(eq_deeply);
+
+ our $tb = __PACKAGE__->builder;
+
+ sub is_same_sql_bind
+ {
+ my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = @_;
+
+ my $same_sql = eq_sql($sql1, $sql2);
+ my $same_bind = eq_bind($bind_ref1, $bind_ref2);
+
+ $tb->ok($same_sql && $same_bind, $msg);
+
+ if (!$same_sql) {
+ $tb->diag("SQL expressions differ\n"
+ . " got: $sql1\n"
+ . "expected: $sql2\n"
+ );
+ }
+ if (!$same_bind) {
+ $tb->diag("BIND values differ\n"
+ . " got: " . Dumper($bind_ref1)
+ . "expected: " . Dumper($bind_ref2)
+ );
+ }
+ }
+
+ sub eq_sql
+ {
+ my ($left, $right) = @_;
+
+ $left =~ s/\s+//g;
+ $right =~ s/\s+//g;
+
+ return $left eq $right;
+ }
+
+ # lifted from SQL::Abstract::Test
+ sub eq_bind
+ {
+ my ($bind_ref1, $bind_ref2) = @_;
+
+ return eq_deeply($bind_ref1, $bind_ref2);
+ }
+}
+
+eval "use SQL::Abstract::Test;";
+if ($@ eq '') {
+ # SQL::Abstract::Test available
+
+ *is_same_sql_bind = \&SQL::Abstract::Test::is_same_sql_bind;
+ *eq_sql = \&SQL::Abstract::Test::eq_sql;
+ *eq_bind = \&SQL::Abstract::Test::eq_bind;
+} else {
+ # old SQL::Abstract
+
+ *is_same_sql_bind = \&DBIC::SqlMakerTest::SQLATest::is_same_sql_bind;
+ *eq_sql = \&DBIC::SqlMakerTest::SQLATest::eq_sql;
+ *eq_bind = \&DBIC::SqlMakerTest::SQLATest::eq_bind;
+}
+
+
+1;
+
+__END__
+
+
+=head1 NAME
+
+DBIC::SqlMakerTest - Helper package for testing sql_maker component of DBIC
+
+=head1 SYNOPSIS
+
+ use Test::More;
+ use DBIC::SqlMakerTest;
+
+ my ($sql, @bind) = $schema->storage->sql_maker->select(%args);
+ is_same_sql_bind(
+ $sql, \@bind,
+ $expected_sql, \@expected_bind,
+ 'foo bar works'
+ );
+
+=head1 DESCRIPTION
+
+Exports functions that can be used to compare generated SQL and bind values.
+
+If L<SQL::Abstract::Test> (packaged in L<SQL::Abstract> versions 1.50 and
+above) is available, then it is used to perform the comparisons (all functions
+are delegated to id). Otherwise uses simple string comparison for the SQL
+statements and simple L<Data::Dumper>-like recursive stringification for
+comparison of bind values.
+
+
+=head1 FUNCTIONS
+
+=head2 is_same_sql_bind
+
+ is_same_sql_bind(
+ $given_sql, \@given_bind,
+ $expected_sql, \@expected_bind,
+ $test_msg
+ );
+
+Compares given and expected pairs of C<($sql, \@bind)>, and calls
+L<Test::Builder/ok> on the result, with C<$test_msg> as message.
+
+=head2 eq_sql
+
+ my $is_same = eq_sql($given_sql, $expected_sql);
+
+Compares the two SQL statements. Returns true IFF they are equivalent.
+
+=head2 eq_bind
+
+ my $is_same = eq_sql(\@given_bind, \@expected_bind);
+
+Compares two lists of bind values. Returns true IFF their values are the same.
+
+
+=head1 SEE ALSO
+
+L<SQL::Abstract::Test>, L<Test::More>, L<Test::Builder>.
+
+=head1 AUTHOR
+
+Norbert Buchmuller, <norbi@nix.hu>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 by Norbert Buchmuller.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.