Skip Menu |

This queue is for tickets about the DBD-SQLite CPAN distribution.

Report information
The Basics
Id: 49716
Status: resolved
Priority: 0/
Queue: DBD-SQLite

People
Owner: Nobody in particular
Requestors: VLYON [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: 1.26_03
Fixed in: (no value)



Subject: column_info is broken
Currently column_info() does not adhere to the spec defined in the DBI, you can't search for columns using % and _ (LIKE match) and it doesn't work with attached databases. I've attached a patch to DBD-SQLite-1.26_03 that fixes this problem, and changed the test script t/16_column_info.t to test the column_info() method correctly.
Index: t/16_column_info.t =================================================================== --- t/16_column_info.t (revision 9042) +++ t/16_column_info.t (working copy) @@ -7,7 +7,7 @@ } use t::lib::Test; -use Test::More tests => 7; +use Test::More tests => 10; use Test::NoWarnings; my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:',undef,undef,{RaiseError => 1}); @@ -19,27 +19,45 @@ ); END_SQL -my $sth = $dbh->column_info(undef,undef,'test',undef); +ok( $dbh->do(<<'END_SQL'), 'Created temp test table' ); + CREATE TEMP TABLE test2 ( + id INTEGER PRIMARY KEY NOT NULL, + flag INTEGER + ); +END_SQL + +my $sth = $dbh->column_info(undef, undef, 'test', undef); is $@, '', 'No error creating the table'; ok $sth, 'We can get column information'; my %expected = ( - TYPE_NAME => [qw[ INTEGER VARCHAR ]], - COLUMN_NAME => [qw[ ID NAME ]], + TYPE_NAME => [qw( INTEGER VARCHAR )], + COLUMN_NAME => [qw( ID NAME )], ); SKIP: { - if ($sth) { - my $info = $sth->fetchall_arrayref({}); + skip( "The table didn't get created correctly or we can't get column information.", 5 ) unless $sth; - is( scalar @$info, 2, 'We got information on two columns' ); + my $info = $sth->fetchall_arrayref({}); + + is( scalar @$info, 2, 'We got information on two columns' ); - foreach my $item (qw( TYPE_NAME COLUMN_NAME )) { - my @info = map { uc $_->{$item} } (@$info); - is_deeply( \@info, $expected{$item}, "We got the right info in $item" ); - }; - } else { - skip( "The table didn't get created correctly or we can't get column information.", 3 ); + foreach my $item (qw( TYPE_NAME COLUMN_NAME )) { + my @info = map { uc $_->{$item} } (@$info); + is_deeply( \@info, $expected{$item}, "We got the right info in $item" ); } -}; + + $info = $dbh->column_info(undef, undef, 'test%', '%a%')->fetchall_arrayref({}); + + is( scalar @$info, 2, 'We matched information from multiple databases' ); + + my @fields = qw( TYPE_NAME COLUMN_NAME COLUMN_SIZE ); + my @info = map [ @$_{@fields} ], @$info; + my $expected = [ + [ 'VARCHAR', 'name', 255 ], + [ 'INTEGER', 'flag', undef ] + ]; + + is_deeply( \@info, $expected, 'We got the right info from multiple databases' ); +} Index: lib/DBD/SQLite.pm =================================================================== --- lib/DBD/SQLite.pm (revision 9042) +++ lib/DBD/SQLite.pm (working copy) @@ -267,11 +267,11 @@ FROM ( SELECT 'main' TABLE_SCHEM, tbl_name, upper(type) TABLE_TYPE, sql FROM sqlite_master - WHERE type IN ( 'table','view') + WHERE type IN ('table','view') UNION ALL SELECT 'temp' TABLE_SCHEM, tbl_name, 'LOCAL TEMPORARY' TABLE_TYPE, sql FROM sqlite_temp_master - WHERE type IN ( 'table','view') + WHERE type IN ('table','view') END_SQL for my $db_name (_attached_database_list($dbh)) { @@ -306,8 +306,8 @@ if ($table_type !~ /^'.*'$/) { $table_type = "'" . $table_type . "'"; } - $table_type_list = join(", ", @ttype_list); } + $table_type_list = join(', ', @ttype_list); push @where, "TABLE_TYPE IN (\U$table_type_list)" if $table_type_list; } $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where; @@ -324,7 +324,7 @@ # This is a hack but much simpler than using pragma index_list etc # also the pragma doesn't list 'INTEGER PRIMARY KEY' autoinc PKs! my @pk_info; - my $sth_tables = $dbh->table_info($catalog, $schema, $table, ''); + my $sth_tables = $dbh->table_info($catalog, $schema, $table, undef); while ( my $row = $sth_tables->fetchrow_hashref ) { my $sql = $row->{sqlite_sql} or next; next unless $sql =~ /(.*?)\s*PRIMARY\s+KEY\s*(?:\(\s*(.*?)\s*\))?/si; @@ -423,60 +423,105 @@ IS_NULLABLE ); -# Taken from Fey::Loader::SQLite sub column_info { - my($dbh, $catalog, $schema, $table, $column) = @_; + my ($dbh, $cat_val, $sch_val, $tbl_val, $col_val) = @_; - if ( defined $column and $column eq '%' ) { - $column = undef; + if ( defined $col_val and $col_val eq '%' ) { + $col_val = undef; } - my @cols = (); - my $position = 0; - my $sth_columns = $dbh->prepare("PRAGMA table_info('$table')"); - $sth_columns->execute; - while ( my $col_info = $sth_columns->fetchrow_hashref ) { - $position++; - next if defined $column && $column ne $col_info->{name}; + # Get a list of all tables ordered by TABLE_SCHEM, TABLE_NAME + my $sql = <<'END_SQL'; +SELECT TABLE_SCHEM, tbl_name TABLE_NAME +FROM ( + SELECT 'main' TABLE_SCHEM, tbl_name + FROM sqlite_master + WHERE type IN ('table','view') +UNION ALL + SELECT 'temp' TABLE_SCHEM, tbl_name + FROM sqlite_temp_master + WHERE type IN ('table','view') +END_SQL - my %col = ( - TABLE_NAME => $table, - COLUMN_NAME => $col_info->{name}, - ORDINAL_POSITION => $position, - ); + for my $db_name (_attached_database_list($dbh)) { + $sql .= <<"END_SQL"; +UNION ALL + SELECT '$db_name' TABLE_SCHEM, tbl_name + FROM "$db_name".sqlite_master + WHERE type IN ('table','view') +END_SQL + } - my $type = $col_info->{type}; - if ( $type =~ s/(\w+)\((\d+)(?:,(\d+))?\)/$1/ ) { - $col{COLUMN_SIZE} = $2; - $col{DECIMAL_DIGITS} = $3; - } + $sql .= <<'END_SQL'; +UNION ALL + SELECT 'main' TABLE_SCHEM, 'sqlite_master' tbl_name +UNION ALL + SELECT 'temp' TABLE_SCHEM, 'sqlite_temp_master' tbl_name +) +END_SQL - $col{TYPE_NAME} = $type; + my @where; + if ( defined $sch_val ) { + push @where, "TABLE_SCHEM LIKE '$sch_val'"; + } + if ( defined $tbl_val ) { + push @where, "TABLE_NAME LIKE '$tbl_val'"; + } + $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where; + $sql .= " ORDER BY TABLE_SCHEM, TABLE_NAME\n"; + my $sth_tables = $dbh->prepare($sql) or return undef; + $sth_tables->execute or return undef; - if ( defined $col_info->{dflt_value} ) { - $col{COLUMN_DEF} = $col_info->{dflt_value} - } + # Taken from Fey::Loader::SQLite + my @cols; + while ( my ($schema, $table) = $sth_tables->fetchrow_array ) { + my $sth_columns = $dbh->prepare(qq{PRAGMA "$schema".table_info("$table")}); + $sth_columns->execute; - if ( $col_info->{notnull} ) { - $col{NULLABLE} = 0; - $col{IS_NULLABLE} = 'NO'; - } else { - $col{NULLABLE} = 1; - $col{IS_NULLABLE} = 'YES'; - } + for ( my $position = 0; my $col_info = $sth_columns->fetchrow_hashref; $position++ ) { + if ( defined $col_val ) { + # This must do a LIKE comparison + my $sth = $dbh->prepare("SELECT '$col_info->{name}' LIKE '$col_val'") or return undef; + $sth->execute or return undef; + # Skip columns that don't match $col_val + next unless ($sth->fetchrow_array)[0]; + } - foreach my $key ( @COLUMN_INFO ) { - next if exists $col{$key}; - $col{$key} = undef; + my %col = ( + TABLE_NAME => $table, + COLUMN_NAME => $col_info->{name}, + ORDINAL_POSITION => $position, + ); + + my $type = $col_info->{type}; + if ( $type =~ s/(\w+)\((\d+)(?:,(\d+))?\)/$1/ ) { + $col{COLUMN_SIZE} = $2; + $col{DECIMAL_DIGITS} = $3; + } + + $col{TYPE_NAME} = $type; + + if ( defined $col_info->{dflt_value} ) { + $col{COLUMN_DEF} = $col_info->{dflt_value} + } + + if ( $col_info->{notnull} ) { + $col{NULLABLE} = 0; + $col{IS_NULLABLE} = 'NO'; + } else { + $col{NULLABLE} = 1; + $col{IS_NULLABLE} = 'YES'; + } + + push @cols, \%col; } - - push @cols, \%col; + $sth_columns->finish; } - $sth_columns->finish; + $sth_tables->finish; my $sponge = DBI->connect("DBI:Sponge:", '','') or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); - my $sth = $sponge->prepare( "column_info $table", { + $sponge->prepare( "column_info", { rows => [ map { [ @{$_}{@COLUMN_INFO} ] } @cols ], NUM_OF_FIELDS => scalar @COLUMN_INFO, NAME => [ @COLUMN_INFO ], @@ -484,8 +529,6 @@ $sponge->err, $sponge->errstr, ); - - return $sth; } Index: Changes =================================================================== --- Changes (revision 9042) +++ Changes (working copy) @@ -9,6 +9,8 @@ under a very, very slow (virtual) machine. (ISHIGAKI) - Added a code to look for a compiler from Module::Install::Can. (ISHIGAKI) + - Fixed $dbh->column_info to work according to the spec in DBI and + added support for attached daatabases. (VLYON) 1.26_03 Wed 12 Aug 2009 - Updated to SQLite 3.6.17 (ISHIGAKI)
I would have checked it in, but I don't have access. ;)
In my haste I left out the TABLE_SCHEM field. New patch attached with better tests for the schema in t/16_column_info.t
Index: t/16_column_info.t =================================================================== --- t/16_column_info.t (revision 9042) +++ t/16_column_info.t (working copy) @@ -7,11 +7,12 @@ } use t::lib::Test; -use Test::More tests => 7; +use Test::More tests => 12; use Test::NoWarnings; my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:',undef,undef,{RaiseError => 1}); +# 1. Create a table ok( $dbh->do(<<'END_SQL'), 'Created test table' ); CREATE TABLE test ( id INTEGER PRIMARY KEY NOT NULL, @@ -19,27 +20,63 @@ ); END_SQL -my $sth = $dbh->column_info(undef,undef,'test',undef); +# 2. Create a temporary table +ok( $dbh->do(<<'END_SQL'), 'Created temp test table' ); + CREATE TEMP TABLE test2 ( + id INTEGER PRIMARY KEY NOT NULL, + flag INTEGER + ); +END_SQL + +# 3. Attach a memory database +ok( $dbh->do('ATTACH DATABASE ":memory:" AS db3'), 'ATTACH DATABASE ":memory:" AS db3' ); + +# 4. Create a table on the attached database +ok( $dbh->do(<<'END_SQL'), 'CREATE TABLE db3.three' ); + CREATE TABLE db3.three ( + id INTEGER NOT NULL, + name CHAR (64) NOT NULL + ) +END_SQL + +# 5. No errors from column_info() +my $sth = $dbh->column_info(undef, undef, 'test', undef); is $@, '', 'No error creating the table'; +# 6. Get column information ok $sth, 'We can get column information'; my %expected = ( - TYPE_NAME => [qw[ INTEGER VARCHAR ]], - COLUMN_NAME => [qw[ ID NAME ]], + TYPE_NAME => [qw( INTEGER VARCHAR )], + COLUMN_NAME => [qw( id name )], ); SKIP: { - if ($sth) { - my $info = $sth->fetchall_arrayref({}); + skip( "The table didn't get created correctly or we can't get column information.", 5 ) unless $sth; - is( scalar @$info, 2, 'We got information on two columns' ); + my $info = $sth->fetchall_arrayref({}); + + # 7. Found 2 columns + is( scalar @$info, 2, 'We got information on two columns' ); - foreach my $item (qw( TYPE_NAME COLUMN_NAME )) { - my @info = map { uc $_->{$item} } (@$info); - is_deeply( \@info, $expected{$item}, "We got the right info in $item" ); - }; - } else { - skip( "The table didn't get created correctly or we can't get column information.", 3 ); + foreach my $item (qw( TYPE_NAME COLUMN_NAME )) { + my @info = map { $_->{$item} } (@$info); + is_deeply( \@info, $expected{$item}, "We got the right info in $item" ); } -}; + + $info = $dbh->column_info(undef, undef, 't%', '%a%')->fetchall_arrayref({}); + + # 10. Found 3 columns + is( scalar @$info, 3, 'We matched information from multiple databases' ); + + my @fields = qw( TABLE_SCHEM TYPE_NAME COLUMN_NAME COLUMN_SIZE NULLABLE ); + my @info = map [ @$_{@fields} ], @$info; + my $expected = [ + [ 'db3', 'CHAR', 'name', 64, 0 ], + [ 'main', 'VARCHAR', 'name', 255, 1 ], + [ 'temp', 'INTEGER', 'flag', undef, 1 ] # TODO: column_info should always return a valid COLUMN_SIZE + ]; + + # 11. Correct info retrieved + is_deeply( \@info, $expected, 'We got the right info from multiple databases' ); +} Index: lib/DBD/SQLite.pm =================================================================== --- lib/DBD/SQLite.pm (revision 9042) +++ lib/DBD/SQLite.pm (working copy) @@ -267,11 +267,11 @@ FROM ( SELECT 'main' TABLE_SCHEM, tbl_name, upper(type) TABLE_TYPE, sql FROM sqlite_master - WHERE type IN ( 'table','view') + WHERE type IN ('table','view') UNION ALL SELECT 'temp' TABLE_SCHEM, tbl_name, 'LOCAL TEMPORARY' TABLE_TYPE, sql FROM sqlite_temp_master - WHERE type IN ( 'table','view') + WHERE type IN ('table','view') END_SQL for my $db_name (_attached_database_list($dbh)) { @@ -306,8 +306,8 @@ if ($table_type !~ /^'.*'$/) { $table_type = "'" . $table_type . "'"; } - $table_type_list = join(", ", @ttype_list); } + $table_type_list = join(', ', @ttype_list); push @where, "TABLE_TYPE IN (\U$table_type_list)" if $table_type_list; } $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where; @@ -324,7 +324,7 @@ # This is a hack but much simpler than using pragma index_list etc # also the pragma doesn't list 'INTEGER PRIMARY KEY' autoinc PKs! my @pk_info; - my $sth_tables = $dbh->table_info($catalog, $schema, $table, ''); + my $sth_tables = $dbh->table_info($catalog, $schema, $table, undef); while ( my $row = $sth_tables->fetchrow_hashref ) { my $sql = $row->{sqlite_sql} or next; next unless $sql =~ /(.*?)\s*PRIMARY\s+KEY\s*(?:\(\s*(.*?)\s*\))?/si; @@ -423,60 +423,106 @@ IS_NULLABLE ); -# Taken from Fey::Loader::SQLite sub column_info { - my($dbh, $catalog, $schema, $table, $column) = @_; + my ($dbh, $cat_val, $sch_val, $tbl_val, $col_val) = @_; - if ( defined $column and $column eq '%' ) { - $column = undef; + if ( defined $col_val and $col_val eq '%' ) { + $col_val = undef; } - my @cols = (); - my $position = 0; - my $sth_columns = $dbh->prepare("PRAGMA table_info('$table')"); - $sth_columns->execute; - while ( my $col_info = $sth_columns->fetchrow_hashref ) { - $position++; - next if defined $column && $column ne $col_info->{name}; + # Get a list of all tables ordered by TABLE_SCHEM, TABLE_NAME + my $sql = <<'END_SQL'; +SELECT TABLE_SCHEM, tbl_name TABLE_NAME +FROM ( + SELECT 'main' TABLE_SCHEM, tbl_name + FROM sqlite_master + WHERE type IN ('table','view') +UNION ALL + SELECT 'temp' TABLE_SCHEM, tbl_name + FROM sqlite_temp_master + WHERE type IN ('table','view') +END_SQL - my %col = ( - TABLE_NAME => $table, - COLUMN_NAME => $col_info->{name}, - ORDINAL_POSITION => $position, - ); + for my $db_name (_attached_database_list($dbh)) { + $sql .= <<"END_SQL"; +UNION ALL + SELECT '$db_name' TABLE_SCHEM, tbl_name + FROM "$db_name".sqlite_master + WHERE type IN ('table','view') +END_SQL + } - my $type = $col_info->{type}; - if ( $type =~ s/(\w+)\((\d+)(?:,(\d+))?\)/$1/ ) { - $col{COLUMN_SIZE} = $2; - $col{DECIMAL_DIGITS} = $3; - } + $sql .= <<'END_SQL'; +UNION ALL + SELECT 'main' TABLE_SCHEM, 'sqlite_master' tbl_name +UNION ALL + SELECT 'temp' TABLE_SCHEM, 'sqlite_temp_master' tbl_name +) +END_SQL - $col{TYPE_NAME} = $type; + my @where; + if ( defined $sch_val ) { + push @where, "TABLE_SCHEM LIKE '$sch_val'"; + } + if ( defined $tbl_val ) { + push @where, "TABLE_NAME LIKE '$tbl_val'"; + } + $sql .= ' WHERE ' . join("\n AND ", @where ) . "\n" if @where; + $sql .= " ORDER BY TABLE_SCHEM, TABLE_NAME\n"; + my $sth_tables = $dbh->prepare($sql) or return undef; + $sth_tables->execute or return undef; - if ( defined $col_info->{dflt_value} ) { - $col{COLUMN_DEF} = $col_info->{dflt_value} - } + # Taken from Fey::Loader::SQLite + my @cols; + while ( my ($schema, $table) = $sth_tables->fetchrow_array ) { + my $sth_columns = $dbh->prepare(qq{PRAGMA "$schema".table_info("$table")}); + $sth_columns->execute; - if ( $col_info->{notnull} ) { - $col{NULLABLE} = 0; - $col{IS_NULLABLE} = 'NO'; - } else { - $col{NULLABLE} = 1; - $col{IS_NULLABLE} = 'YES'; - } + for ( my $position = 0; my $col_info = $sth_columns->fetchrow_hashref; $position++ ) { + if ( defined $col_val ) { + # This must do a LIKE comparison + my $sth = $dbh->prepare("SELECT '$col_info->{name}' LIKE '$col_val'") or return undef; + $sth->execute or return undef; + # Skip columns that don't match $col_val + next unless ($sth->fetchrow_array)[0]; + } - foreach my $key ( @COLUMN_INFO ) { - next if exists $col{$key}; - $col{$key} = undef; + my %col = ( + TABLE_SCHEM => $schema, + TABLE_NAME => $table, + COLUMN_NAME => $col_info->{name}, + ORDINAL_POSITION => $position, + ); + + my $type = $col_info->{type}; + if ( $type =~ s/(\w+) ?\((\d+)(?:,(\d+))?\)/$1/ ) { + $col{COLUMN_SIZE} = $2; + $col{DECIMAL_DIGITS} = $3; + } + + $col{TYPE_NAME} = $type; + + if ( defined $col_info->{dflt_value} ) { + $col{COLUMN_DEF} = $col_info->{dflt_value} + } + + if ( $col_info->{notnull} ) { + $col{NULLABLE} = 0; + $col{IS_NULLABLE} = 'NO'; + } else { + $col{NULLABLE} = 1; + $col{IS_NULLABLE} = 'YES'; + } + + push @cols, \%col; } - - push @cols, \%col; + $sth_columns->finish; } - $sth_columns->finish; + $sth_tables->finish; my $sponge = DBI->connect("DBI:Sponge:", '','') or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr"); - my $sth = $sponge->prepare( "column_info $table", { + $sponge->prepare( "column_info", { rows => [ map { [ @{$_}{@COLUMN_INFO} ] } @cols ], NUM_OF_FIELDS => scalar @COLUMN_INFO, NAME => [ @COLUMN_INFO ], @@ -484,8 +530,6 @@ $sponge->err, $sponge->errstr, ); - - return $sth; } Index: Changes =================================================================== --- Changes (revision 9042) +++ Changes (working copy) @@ -9,6 +9,8 @@ under a very, very slow (virtual) machine. (ISHIGAKI) - Added a code to look for a compiler from Module::Install::Can. (ISHIGAKI) + - Fixed $dbh->column_info to work according to the spec in DBI and + added support for attached daatabases. (VLYON) 1.26_03 Wed 12 Aug 2009 - Updated to SQLite 3.6.17 (ISHIGAKI)
Subject: Re: [rt.cpan.org #49716] column_info is broken
Date: Wed, 16 Sep 2009 11:29:32 +1000
To: bug-DBD-SQLite [...] rt.cpan.org
From: Adam Kennedy <adamkennedybackup [...] gmail.com>
If you let me know your CPAN author id, I can fix that. :) Adam K 2009/9/15 Vernon Lyon via RT <bug-DBD-SQLite@rt.cpan.org>: Show quoted text
>       Queue: DBD-SQLite >  Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=49716 > > > I would have checked it in, but I don't have access. ;) >
Subject: RE: [rt.cpan.org #49716] column_info is broken
Date: Wed, 16 Sep 2009 13:03:20 +0100
To: <bug-dbd-sqlite [...] rt.cpan.org>
From: Vernon Lyon <vernonlyon [...] hotmail.com>
My id is VLYON. Thanx! I'll also be adding some more columns to column_info(), and then work begins on primary_keys(). Show quoted text
> Subject: Re: [rt.cpan.org #49716] column_info is broken > From: bug-DBD-SQLite@rt.cpan.org > To: VLYON@cpan.org > Date: Tue, 15 Sep 2009 21:30:09 -0400 > > <URL: http://rt.cpan.org/Ticket/Display.html?id=49716 > > > If you let me know your CPAN author id, I can fix that. :) > > Adam K > > 2009/9/15 Vernon Lyon via RT <bug-DBD-SQLite@rt.cpan.org>:
> > Queue: DBD-SQLite > > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=49716 > > > > > I would have checked it in, but I don't have access. ;) > >
>
Show quoted text
_________________________________________________________________ Learn how to add other email accounts to Hotmail in 3 easy steps. http://clk.atdmt.com/UKM/go/167688463/direct/01/
VLYON, while you're at it, could you have a look at http://rt.cpan.org/ Public/Bug/Display.html?id=46873 ?
Marked as resolved since 1.26_04