[Bps-public-commit] dbix-searchbuilder branch, quote_table_names, created. 1.67-11-gaecd7b1

Aaron Trevena ast at bestpractical.com
Thu Aug 27 16:18:20 EDT 2020


The branch, quote_table_names has been created
        at  aecd7b18717fd587d829d6dd6562c563685b58e6 (commit)

- Log -----------------------------------------------------------------
commit c402dff9c509e5ddb724602c510e894aec05cac6
Author: Aaron Trevena <aaron at aarontrevena.co.uk>
Date:   Tue Aug 25 21:16:11 2020 +0100

    Add table name quoting option for mysql
    
    Add table name quoting option, initially for mysql

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 195669e..fa271b0 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1251,6 +1251,7 @@ sub NewAlias {
 
     my $alias = $self->_GetAlias($table);
 
+    $table = $self->_Handle->QuoteName($table) if ($self->_Handle->QuoteTableNames);
     unless ( $type ) {
         push @{ $self->{'aliases'} }, "$table $alias";
     } elsif ( lc $type eq 'left' ) {
diff --git a/lib/DBIx/SearchBuilder/Handle.pm b/lib/DBIx/SearchBuilder/Handle.pm
index d0f67fc..6e98b4e 100755
--- a/lib/DBIx/SearchBuilder/Handle.pm
+++ b/lib/DBIx/SearchBuilder/Handle.pm
@@ -27,7 +27,8 @@ DBIx::SearchBuilder::Handle - Perl extension which is a generic DBI handle
                     Database => 'dbname',
                     Host => 'hostname',
                     User => 'dbuser',
-                    Password => 'dbpassword');
+                    Password => 'dbpassword',
+                    QuoteTableNames => 1 );
   # now $handle isa DBIx::SearchBuilder::Handle::mysql                    
  
 =head1 DESCRIPTION
@@ -50,13 +51,17 @@ sub new  {
     my $self  = {};
     bless ($self, $class);
 
+    # Enable quotes table names
+    my %args = ( QuoteTableNames => 0, @_ );
+    $self->{'QuoteTableNames'} = $args{QuoteTableNames};
+
     @{$self->{'StatementLog'}} = ();
     return $self;
 }
 
 
 
-=head2 Connect PARAMHASH: Driver, Database, Host, User, Password
+=head2 Connect PARAMHASH: Driver, Database, Host, User, Password, QuoteTableNames
 
 Takes a paramhash and connects to your DBI datasource. 
 
@@ -71,6 +76,9 @@ If you created the handle with
 and there is a DBIx::SearchBuilder::Handle::(Driver) subclass for the driver you have chosen,
 the handle will be automatically "upgraded" into that subclass.
 
+QuoteTableNames option will force all table names to be quoted if the driver subclass has a method
+for quoting implemented.
+
 =cut
 
 sub Connect  {
@@ -85,6 +93,7 @@ sub Connect  {
         Password => undef,
         RequireSSL => undef,
         DisconnectHandleOnDestroy => undef,
+        QuoteTableNames => undef,
         @_
     );
 
@@ -96,6 +105,9 @@ sub Connect  {
     # So we need to explicitly call it
     $self->{'DisconnectHandleOnDestroy'} = $args{'DisconnectHandleOnDestroy'};
 
+    # Enable quotes table names
+    $self->{'QuoteTableNames'} = $args{QuoteTableNames} if (defined $args{QuoteTableNames});
+
     my $old_dsn = $self->DSN || '';
     my $new_dsn = $self->BuildDSN( %args );
 
@@ -390,6 +402,7 @@ sub InsertQueryString {
         push @bind, shift @pairs;
     }
 
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     my $QueryString = "INSERT INTO $table";
     $QueryString .= " (". join(", ", @cols) .")";
     $QueryString .= " VALUES (". join(", ", @vals). ")";
@@ -415,6 +428,7 @@ sub InsertFromSelect {
     $columns = join ', ', @$columns
         if $columns;
 
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     my $full_query = "INSERT INTO $table";
     $full_query .= " ($columns)" if $columns;
     $full_query .= ' '. $query;
@@ -446,6 +460,7 @@ sub UpdateRecordValue {
                  @_ );
 
     my @bind  = ();
+    $args{Table} = $self->QuoteName($args{Table}) if ($self->{'QuoteTableNames'});
     my $query = 'UPDATE ' . $args{'Table'} . ' ';
      $query .= 'SET '    . $args{'Column'} . '=';
 
@@ -518,6 +533,7 @@ sub SimpleUpdateFromSelect {
         push @binds, $values->{$k};
     }
 
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     my $full_query = "UPDATE $table SET ";
     $full_query .= join ', ', map "$_ = ?", @columns;
     $full_query .= ' WHERE id IN ('. $query .')';
@@ -541,6 +557,7 @@ select query, eg:
 
 sub DeleteFromSelect {
     my ($self, $table, $query, @binds) = @_;
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     my $sth = $self->SimpleQuery(
         "DELETE FROM $table WHERE id IN ($query)",
         @binds
@@ -753,6 +770,16 @@ sub CaseSensitive {
     return(1);
 }
 
+=head2 QuoteTableNames
+
+Returns 1 if table names will be quoted in queries, otherwise 0
+
+=cut
+
+sub QuoteTableNames  {
+    return shift->{'QuoteTableNames'}
+}
+
 
 
 
@@ -1192,8 +1219,9 @@ sub _BuildJoins {
     my $sb   = shift;
 
     $self->OptimizeJoins( SearchBuilder => $sb );
+    my $table = ($self->{'QuoteTableNames'}) ? $self->QuoteName($sb->Table) : $sb->Table;
 
-    my $join_clause = join " CROSS JOIN ", ($sb->Table ." main"), @{ $sb->{'aliases'} };
+    my $join_clause = join " CROSS JOIN ", ("$table main"), @{ $sb->{'aliases'} };
     my %processed = map { /^\S+\s+(\S+)$/; $1 => 1 } @{ $sb->{'aliases'} };
     $processed{'main'} = 1;
 
@@ -1738,6 +1766,21 @@ sub HasSupportForNullsOrder {
 }
 
 
+=head2 QuoteName
+
+Quote table or column name to avoid reserved word errors.
+
+Returns same value passed unless over-ridden in database-specific subclass.
+
+=cut
+
+# over-ride in subclass
+sub QuoteName {
+    my $self = shift;
+    return shift;
+}
+
+
 =head2 DESTROY
 
 When we get rid of the Searchbuilder::Handle, we need to disconnect from the database
diff --git a/lib/DBIx/SearchBuilder/Handle/mysql.pm b/lib/DBIx/SearchBuilder/Handle/mysql.pm
index a0302aa..76b994c 100755
--- a/lib/DBIx/SearchBuilder/Handle/mysql.pm
+++ b/lib/DBIx/SearchBuilder/Handle/mysql.pm
@@ -74,6 +74,7 @@ sub SimpleUpdateFromSelect {
         push @binds, $values->{$k};
     }
 
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     my $update_query = "UPDATE $table SET "
         . join( ', ', map "$_ = ?", @columns )
         .' WHERE ID IN ';
@@ -92,6 +93,7 @@ sub DeleteFromSelect {
         $table, $query, @query_binds
     ) unless $query =~ /\b\Q$table\E\b/i;
 
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     return $self->SimpleMassChangeFromSelect(
         "DELETE FROM $table WHERE id IN ", [],
         $query, @query_binds
@@ -206,6 +208,7 @@ sub Fields {
     my $table = shift;
 
     my $cache = \%DBIx::SearchBuilder::Handle::FIELDS_IN_TABLE;
+    $table = $self->QuoteName($table) if ($self->{'QuoteTableNames'});
     unless ( $cache->{ lc $table } ) {
         my $sth = $self->dbh->column_info( undef, undef, $table, '%' )
             or return ();
@@ -289,6 +292,20 @@ sub _DateTimeIntervalFunction {
     return "TIMESTAMPDIFF(SECOND, $args{'From'}, $args{'To'})";
 }
 
+
+=head2 QuoteName
+
+Quote table or column name to avoid reserved word errors.
+
+=cut
+
+# over-rides inherited method
+sub QuoteName {
+    my ($self, $name) = @_;
+    return sprintf('`%s`', $name);
+}
+
+
 1;
 
 __END__
diff --git a/lib/DBIx/SearchBuilder/Record.pm b/lib/DBIx/SearchBuilder/Record.pm
index c1428d0..413de87 100755
--- a/lib/DBIx/SearchBuilder/Record.pm
+++ b/lib/DBIx/SearchBuilder/Record.pm
@@ -709,7 +709,7 @@ sub __Value {
     my %pk = $self->PrimaryKeys;
     return undef if grep !defined, values %pk;
 
-    my $query = "SELECT $field FROM ". $self->Table
+    my $query = "SELECT $field FROM ". $self->QuotedTableName
         ." WHERE ". join " AND ", map "$_ = ?", sort keys %pk;
     my $sth = $self->_Handle->SimpleQuery( $query, sorted_values(%pk) ) or return undef;
     return $self->{'values'}{$field} = ($sth->fetchrow_array)[0];
@@ -1147,8 +1147,8 @@ sub LoadByCols  {
 
 	}
     }
-    
-    my $QueryString = "SELECT  * FROM ".$self->Table." WHERE ". 
+
+    my $QueryString = "SELECT  * FROM ".$self->QuotedTableName." WHERE ".
     join(' AND ', @phrases) ;
     return ($self->_LoadFromSQL($QueryString, @bind));
 }
@@ -1349,7 +1349,7 @@ sub __Delete {
     }
 
     $where =~ s/AND\s$//;
-    my $QueryString = "DELETE FROM ". $self->Table . ' ' . $where;
+    my $QueryString = "DELETE FROM ". $self->QuotedTableName . ' ' . $where;
    my $return = $self->_Handle->SimpleQuery($QueryString, @bind);
 
     if (UNIVERSAL::isa($return, 'Class::ReturnValue')) {
@@ -1369,8 +1369,6 @@ Returns or sets the name of the current Table
 
 =cut
 
-
-
 sub Table {
     my $self = shift;
     if (@_) {
@@ -1379,7 +1377,19 @@ sub Table {
     return ($self->{'table'});
 }
 
+=head2 QuotedTableName
+
+Returns the name of current Table, including any quoting
 
+=cut
+
+sub QuotedTableName {
+    my $self = shift;
+    return $self->{'_quoted_table'} if (defined $self->{'_quoted_table'});
+    $self->{'_quoted_table'} = ( $self->_Handle->QuoteTableNames ) ?
+        $self->_Handle->QuoteName($self->Table) : $self->Table ;
+    return $self->{'_quoted_table'}
+}
 
 =head2 _Handle
 

commit 074b6ed5c822b5503cb8559485b4e8e2bb8e0acc
Author: Aaron Trevena <aaron at aarontrevena.co.uk>
Date:   Thu Aug 27 21:06:37 2020 +0100

    Add tests for tablename quoting in mysql
    
    Add tests for with/without tablename quoting flag set
    Add host/port ENV var options for testing database

diff --git a/t/11schema_records.t b/t/11schema_records.t
index 4fc4dc4..aa1d209 100644
--- a/t/11schema_records.t
+++ b/t/11schema_records.t
@@ -8,11 +8,13 @@ use Test::More;
 BEGIN { require "t/utils.pl" }
 our (@AvailableDrivers);
 
-use constant TESTS_PER_DRIVER => 63;
+use constant TESTS_PER_DRIVER => 66 * 4;
 
 my $total = scalar(@AvailableDrivers) * TESTS_PER_DRIVER;
 plan tests => $total;
 
+foreach my $quote_tablenames (0,1) {
+
 foreach my $d ( @AvailableDrivers ) {
 SKIP: {
 	unless( has_schema( 'TestApp', $d ) ) {
@@ -22,7 +24,7 @@ SKIP: {
 		skip "ENV is not defined for driver '$d'", TESTS_PER_DRIVER;
 	}
 
-	my $handle = get_handle( $d );
+	my $handle = get_handle( $d, QuoteTableNames => $quote_tablenames );
 	connect_handle( $handle );
 	isa_ok($handle->dbh, 'DBI::db', "Got handle for $d");
 
@@ -180,11 +182,25 @@ SKIP: {
 	    is($phone_collection->Next, undef);
 	}
 	
-	
-
+	ok($phone3->Delete, "Deleted phone $p3_id");
+
+        # check mysql version
+        my $version = $handle->DatabaseVersion(Short=>1);
+        if ($quote_tablenames == 0 and lc($d) eq 'mysql' and $version =~ m/^8\./) {
+            ok(1, 'skipping quoted reserved word tests');
+            ok(1, 'skipping quoted reserved word tests');
+        }
+        else {
+            my $group = TestApp::Group->new($handle);
+            my $g_id = $group->Create( Name => 'Employees' );
+            ok($g_id, "Got an id for the new group: $g_id");
+            $group->Load($g_id);
+            is($group->id, $g_id, "loaded group ok");
+        }
 	cleanup_schema( 'TestApp', $handle );
 }} # SKIP, foreach blocks
 
+}
 1;
 
 
@@ -201,6 +217,10 @@ CREATE TABLE Phones (
 	id integer primary key,
 	Employee integer NOT NULL,
 	Phone varchar(18)
+) },
+q{CREATE TABLE Groups (
+	id integer primary key,
+	Name varchar(42)
 ) }
 ]
 }
@@ -217,7 +237,12 @@ CREATE TEMPORARY TABLE Phones (
 	Employee integer NOT NULL,
 	Phone varchar(18)
 )
-} ]
+},
+q{CREATE TEMPORARY TABLE `Groups` (
+	id integer AUTO_INCREMENT primary key,
+	Name varchar(42)
+) }
+]
 }
 
 sub schema_pg {
@@ -232,7 +257,12 @@ CREATE TEMPORARY TABLE Phones (
 	Employee integer references Employees(id),
 	Phone varchar
 )
-} ]
+},
+q{CREATE TEMPORARY TABLE Groups (
+	id serial primary key,
+	Name varchar
+) }
+]
 }
 
 package TestApp::Employee;
@@ -292,4 +322,35 @@ sub NewItem {
 }
 
 
+package TestApp::Group;
+
+use base $ENV{SB_TEST_CACHABLE}?
+    qw/DBIx::SearchBuilder::Record::Cachable/:
+    qw/DBIx::SearchBuilder::Record/;
+
+sub Table { 'Groups' }
+
+sub Schema {
+    return {
+        Name => { TYPE => 'varchar' },
+    }
+}
+
+package TestApp::GroupCollection;
+
+use base qw/DBIx::SearchBuilder/;
+
+sub Table {
+    my $self = shift;
+    my $tab = $self->NewItem->Table();
+    return $tab;
+}
+
+sub NewItem {
+    my $self = shift;
+    my $class = 'TestApp::Group';
+    return $class->new( $self->_Handle );
+
+}
+
 1;
diff --git a/t/utils.pl b/t/utils.pl
index b662034..7f99ba2 100644
--- a/t/utils.pl
+++ b/t/utils.pl
@@ -110,6 +110,8 @@ sub connect_mysql
 	return $handle->Connect(
 		Driver => 'mysql',
 		Database => $ENV{'SB_TEST_MYSQL'},
+                Host => $ENV{'SB_TEST_MYSQL_HOST'},
+                Port => $ENV{'SB_TEST_MYSQL_PORT'},
 		User => $ENV{'SB_TEST_MYSQL_USER'} || 'root',
 		Password => $ENV{'SB_TEST_MYSQL_PASS'} || '',
 	);

commit aecd7b18717fd587d829d6dd6562c563685b58e6
Author: Aaron Trevena <aaron at aarontrevena.co.uk>
Date:   Thu Aug 27 21:16:03 2020 +0100

    Update Changes with quoted table names update

diff --git a/Changes b/Changes
index bed91f1..a230dd9 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
 Revision history for Perl extension DBIx::SearchBuilder.
 
+ - New option to quote tablenames, initially for MySQL
+
 1.68 2020-07-06
  - Avoid segmentation faults on disconnect on MariaDB 10.2+
 

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list