[Bps-public-commit] dbix-searchbuilder branch master updated. 1.79-7-g6933ace

BPS Git Server git at git.bestpractical.com
Thu Jan 18 21:13:17 UTC 2024


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "dbix-searchbuilder".

The branch, master has been updated
       via  6933ace132487e9f8e0352e8a1cca6ed82901f8d (commit)
       via  2b68b2a0fa2695f141489ad24813727abfbe88c8 (commit)
       via  42bd0ecafd8d5d61c4b7686c86fac67cfaed1fb6 (commit)
       via  322873823390e26a4551d9b4346a85b1a829b291 (commit)
      from  72634d77e34c17a1dc153421a20711cb29df41ed (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 6933ace132487e9f8e0352e8a1cca6ed82901f8d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jan 18 15:54:08 2024 -0500

    Prep version 1.81

diff --git a/Changes b/Changes
index d2c24cf..4406ddd 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,8 @@
 Revision history for Perl extension DBIx::SearchBuilder.
 
+1.81 2024-01-18
+ - Add explicit support for MariaDB in addition to MySQL
+
 1.80 2023-12-13
  - Add CastAsDecimal helper method
 
diff --git a/MANIFEST b/MANIFEST
index 9a01c3e..7491774 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -18,6 +18,7 @@ inc/Module/Install/WriteAll.pm
 lib/DBIx/SearchBuilder.pm
 lib/DBIx/SearchBuilder/Handle.pm
 lib/DBIx/SearchBuilder/Handle/Informix.pm
+lib/DBIx/SearchBuilder/Handle/MariaDB.pm
 lib/DBIx/SearchBuilder/Handle/mysql.pm
 lib/DBIx/SearchBuilder/Handle/mysqlPP.pm
 lib/DBIx/SearchBuilder/Handle/ODBC.pm
diff --git a/META.yml b/META.yml
index f3d1407..2416040 100644
--- a/META.yml
+++ b/META.yml
@@ -36,4 +36,4 @@ requires:
   perl: 5.10.1
 resources:
   license: http://dev.perl.org/licenses/
-version: '1.80'
+version: '1.81'
diff --git a/README b/README
index cc262a5..d61b60e 100644
--- a/README
+++ b/README
@@ -614,7 +614,7 @@ BUGS
         L<rt.cpan.org|http://rt.cpan.org/Public/Dist/Display.html?Name=DBIx-SearchBuilder>.
 
 LICENSE AND COPYRIGHT
-    Copyright (C) 2001-2023, Best Practical Solutions LLC.
+    Copyright (C) 2001-2024, Best Practical Solutions LLC.
 
     This library is free software; you can redistribute it and/or modify it
     under the same terms as Perl itself.
diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 694f7c9..f0a2947 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -4,7 +4,7 @@ package DBIx::SearchBuilder;
 use strict;
 use warnings;
 
-our $VERSION = "1.80";
+our $VERSION = "1.81";
 
 use Clone qw();
 use Encode qw();
@@ -2115,7 +2115,7 @@ or via the web at
 
 =head1 LICENSE AND COPYRIGHT
 
-Copyright (C) 2001-2023, Best Practical Solutions LLC.
+Copyright (C) 2001-2024, Best Practical Solutions LLC.
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.

commit 2b68b2a0fa2695f141489ad24813727abfbe88c8
Merge: 72634d7 42bd0ec
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jan 18 15:50:31 2024 -0500

    Merge branch 'driver-mariadb'


commit 42bd0ecafd8d5d61c4b7686c86fac67cfaed1fb6
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jan 12 17:04:15 2024 -0500

    Support BLOBs with DBD::MariaDB

diff --git a/lib/DBIx/SearchBuilder/Handle.pm b/lib/DBIx/SearchBuilder/Handle.pm
index b9f9deb..6a2f474 100755
--- a/lib/DBIx/SearchBuilder/Handle.pm
+++ b/lib/DBIx/SearchBuilder/Handle.pm
@@ -602,13 +602,25 @@ sub SimpleQuery {
         }
     }
 
-    # Check @bind_values for HASH refs
+    # Check @bind_values for HASH refs. These can be sent as type settings
+    # or hits for the DBD module for dealing with certain data types,
+    # usually BLOBS.
     for ( my $bind_idx = 0 ; $bind_idx < scalar @bind_values ; $bind_idx++ ) {
         if ( ref( $bind_values[$bind_idx] ) eq "HASH" ) {
             my $bhash = $bind_values[$bind_idx];
             $bind_values[$bind_idx] = $bhash->{'value'};
             delete $bhash->{'value'};
-            $sth->bind_param( $bind_idx + 1, undef, $bhash );
+
+            # DBD::MariaDB only works using the string version of setting type,
+            # so convert the hash to the string for this case.
+            if ( $self->isa('DBIx::SearchBuilder::Handle::MariaDB')
+                 && $bhash->{'TYPE'}
+                 && $bhash->{'TYPE'} eq 'SQL_BLOB' ) {
+                $sth->bind_param( $bind_idx + 1, undef, DBI::SQL_BLOB );
+            }
+            else {
+                $sth->bind_param( $bind_idx + 1, undef, $bhash );
+            }
         }
     }
 
diff --git a/lib/DBIx/SearchBuilder/Handle/MariaDB.pm b/lib/DBIx/SearchBuilder/Handle/MariaDB.pm
index bd76d5a..ea8ab6e 100755
--- a/lib/DBIx/SearchBuilder/Handle/MariaDB.pm
+++ b/lib/DBIx/SearchBuilder/Handle/MariaDB.pm
@@ -53,6 +53,41 @@ sub Insert  {
     return( $self->{'id'}); #Add Succeded. return the id
 }
 
+=head2 KnowsBLOBs
+
+Returns 1 if the current database supports inserts of BLOBs automatically.
+Returns undef if the current database must be informed of BLOBs for inserts.
+
+=cut
+
+sub KnowsBLOBs {
+    my $self = shift;
+    return(undef);
+}
+
+=head2 BLOBParams FIELD_NAME FIELD_TYPE
+
+Returns a hash ref for the bind_param call to identify BLOB types used by
+the current database for a particular column type.
+
+=cut
+
+sub BLOBParams {
+    my $self = shift;
+    my $field = shift;
+    my $type = shift;
+
+    if ( $type =~ /^(blob|longblob)$/i ) {
+        # Don't assign to key 'value' as it is defined later.
+        return ( { TYPE => 'SQL_BLOB', } );
+    }
+    else {
+        # Normal handling for these, so no hashref
+        return;
+    }
+}
+
+
 =head2 SimpleUpdateFromSelect
 
 Customization of L<DBIx::SearchBuilder::Handle/SimpleUpdateFromSelect>.
diff --git a/lib/DBIx/SearchBuilder/Record.pm b/lib/DBIx/SearchBuilder/Record.pm
index 8bd4c05..25ba6b0 100755
--- a/lib/DBIx/SearchBuilder/Record.pm
+++ b/lib/DBIx/SearchBuilder/Record.pm
@@ -853,7 +853,7 @@ sub __Set {
         # Support for databases which don't deal with LOBs automatically
         my $ca = $self->_ClassAccessible();
         my $key = $args{'Column'};
-        if ( $ca->{$key}->{'type'} =~ /^(text|longtext|clob|blob|lob)$/i ) {
+        if ( ( $ca->{$key}->{'type'} // '' ) =~ /^(text|longtext|clob|longblob|blob|lob)$/i ) {
             my $bhash = $self->_Handle->BLOBParams( $key, $ca->{$key}->{'type'} );
             $bhash->{'value'} = $args{'Value'};
             $args{'Value'} = $bhash;
@@ -1302,11 +1302,13 @@ sub Create {
         my $ca = $self->_ClassAccessible();
         foreach $key ( keys %attribs ) {
             my $type = $ca->{$key}->{'type'};
-            next unless $type && $type =~ /^(text|longtext|clob|blob|lob)$/i;
+            next unless $type && $type =~ /^(text|longtext|clob|blob|lob|longblob)$/i;
 
             my $bhash = $self->_Handle->BLOBParams( $key, $type );
-            $bhash->{'value'} = $attribs{$key};
-            $attribs{$key} = $bhash;
+            if ( ref($bhash) eq 'HASH' ) {
+                $bhash->{'value'} = $attribs{$key};
+                $attribs{$key} = $bhash;
+            }
         }
     }
     return ( $self->_Handle->Insert( $self->Table, %attribs ) );
diff --git a/t/01records.t b/t/01records.t
index d4954ec..e0d55ea 100644
--- a/t/01records.t
+++ b/t/01records.t
@@ -7,13 +7,14 @@ use Test::More;
 BEGIN { require "./t/utils.pl" }
 our (@AvailableDrivers);
 
-use constant TESTS_PER_DRIVER => 69;
+use constant TESTS_PER_DRIVER => 75;
 
 my $total = scalar(@AvailableDrivers) * TESTS_PER_DRIVER;
 plan tests => $total;
 
 foreach my $d ( @AvailableDrivers ) {
 SKIP: {
+    diag ("Running tests for $d");
 	unless( has_schema( 'TestApp::Address', $d ) ) {
 		skip "No schema for '$d' driver", TESTS_PER_DRIVER;
 	}
@@ -34,7 +35,7 @@ SKIP: {
 # Handle->Fields
         is_deeply(
             [$handle->Fields('Address')],
-            [qw(id name phone employeeid)],
+            [qw(id name phone employeeid content)],
             "listed all columns in the table"
         );
         is_deeply(
@@ -48,23 +49,34 @@ SKIP: {
 	is( $rec->_Accessible('id' => 'write'), undef, 'id is not accessible for write' );
 	is( $rec->_Accessible('id'), undef, "any field is not accessible in undefined mode" );
 	is( $rec->_Accessible('unexpected_field' => 'read'), undef, "field doesn't exist and can't be accessible for read" );
-	is_deeply( [sort($rec->ReadableAttributes)], [qw(EmployeeId Name Phone id)], 'readable attributes' );
-	is_deeply( [sort($rec->WritableAttributes)], [qw(EmployeeId Name Phone)], 'writable attributes' );
+	is_deeply( [sort($rec->ReadableAttributes)], [qw(Content EmployeeId Name Phone id)], 'readable attributes' );
+	is_deeply( [sort($rec->WritableAttributes)], [qw(Content EmployeeId Name Phone)], 'writable attributes' );
 
 	can_ok($rec,'Create');
 
-	my ($id) = $rec->Create( Name => 'Jesse', Phone => '617 124 567');
+	my ($id) = $rec->Create( Name => 'Jesse', Phone => '617 124 567', Content => 'Håvard');
 	ok($id,"Created record ". $id);
 	ok($rec->Load($id), "Loaded the record");
 
 
 	is($rec->id, $id, "The record has its id");
 	is ($rec->Name, 'Jesse', "The record's name is Jesse");
+    is( $rec->Content, 'Håvard', "The record's Content is Håvard");
 
 	my ($val, $msg) = $rec->SetName('Obra');
 	ok($val, $msg) ;
 	is($rec->Name, 'Obra', "We did actually change the name");
 
+    my $rec2 = TestApp::Address->new($handle);
+    isa_ok($rec2, 'DBIx::SearchBuilder::Record');
+
+    my ($id2) = $rec2->Create( Name => 'Håvard', Phone => '617 124 567', Content => 'Foo');
+    ok($id2,"Created record ". $id2);
+    ok($rec2->Load($id2), "Loaded the record");
+
+    is($rec2->id, $id2, "The record has its id");
+    is ($rec2->Name, 'Håvard', "The record's name is Håvard");
+
 # Validate immutability of the field id
 	($val, $msg) = $rec->Setid( $rec->id + 1 );
 	ok(!$val, $msg);
@@ -253,7 +265,8 @@ sub _ClassAccessible {
         {read => 1, write => 1, type => 'varchar(18)', length => 18, default => ''},
         EmployeeId => 
         {read => 1, write => 1, type => 'int(8)', default => ''},
-
+        Content =>
+        {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longblob', default => ''},
 }
 
 }
@@ -265,6 +278,7 @@ CREATE TEMPORARY TABLE Address (
         Name varchar(36),
         Phone varchar(18),
         EmployeeId int(8),
+        Content LONGBLOB,
   	PRIMARY KEY (id)) CHARACTER SET utf8mb4
 EOF
 
@@ -277,18 +291,22 @@ CREATE TEMPORARY TABLE Address (
     Name varchar(36),
     Phone varchar(18),
     EmployeeId int(8),
+    Content LONGBLOB,
     PRIMARY KEY (id)) CHARACTER SET utf8mb4
 EOF
 
 }
 
+# TODO: Change this test to use bytes for Content
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Address (
         id serial PRIMARY KEY,
         Name varchar,
         Phone varchar,
-        EmployeeId integer
+        EmployeeId integer,
+        Content varchar
 )
 EOF
 
@@ -301,7 +319,8 @@ CREATE TABLE Address (
         id  integer primary key,
         Name varchar(36),
         Phone varchar(18),
-        EmployeeId int(8))
+        EmployeeId int(8),
+        Content BLOB )
 EOF
 
 }
@@ -312,7 +331,8 @@ sub schema_oracle { [
         id integer CONSTRAINT Address_Key PRIMARY KEY,
         Name varchar(36),
         Phone varchar(18),
-        EmployeeId integer
+        EmployeeId integer,
+        Content CLOB
     )",
 ] }
 

commit 322873823390e26a4551d9b4346a85b1a829b291
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jan 5 17:03:38 2024 -0500

    Add explicit support for MariaDB in addition to MySQL
    
    Starting with version 5.001, DBD::mysql will no longer support
    MariaDB. Add explicit support in DBIx::SearchBuilder for
    MariaDB and DBD::MariaDB independent of mysql and DBD::mysql.

diff --git a/lib/DBIx/SearchBuilder/Handle/MariaDB.pm b/lib/DBIx/SearchBuilder/Handle/MariaDB.pm
new file mode 100755
index 0000000..bd76d5a
--- /dev/null
+++ b/lib/DBIx/SearchBuilder/Handle/MariaDB.pm
@@ -0,0 +1,376 @@
+package DBIx::SearchBuilder::Handle::MariaDB;
+
+use strict;
+use warnings;
+use version;
+
+use base qw(DBIx::SearchBuilder::Handle);
+
+=head1 NAME
+
+  DBIx::SearchBuilder::Handle::MariaDB - A MariaDB specific Handle object
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+This module provides a subclass of DBIx::SearchBuilder::Handle that
+compensates for some of the idiosyncrasies of MySQL.
+
+=head1 METHODS
+
+=head2 Insert
+
+Takes a table name as the first argument and assumes that the rest of the arguments are an array of key-value pairs to be inserted.
+
+If the insert succeeds, returns the id of the insert, otherwise, returns
+a Class::ReturnValue object with the error reported.
+
+=cut
+
+sub Insert  {
+    my $self = shift;
+
+    my $sth = $self->SUPER::Insert(@_);
+    if (!$sth) {
+        return ($sth);
+    }
+
+    # Follow the advice in the docs and use last_insert_id rather than
+    # {'mariadb_insertid'}.
+    #
+    # https://metacpan.org/dist/DBD-MariaDB/view/lib/DBD/MariaDB.pod#mariadb_insertid
+
+    $self->{'id'} = $self->dbh->last_insert_id();
+
+    # Docs say last_insert_id could still return undef, so keeping this code
+    unless ( $self->{'id'} ) {
+        $self->{'id'} = $self->FetchResult('SELECT LAST_INSERT_ID()');
+    }
+    warn "$self no row id returned on row creation" unless ($self->{'id'});
+
+    return( $self->{'id'}); #Add Succeded. return the id
+}
+
+=head2 SimpleUpdateFromSelect
+
+Customization of L<DBIx::SearchBuilder::Handle/SimpleUpdateFromSelect>.
+Mysql doesn't support update with subqueries when those fetch data from
+the table that is updated.
+
+=cut
+
+sub SimpleUpdateFromSelect {
+    my ($self, $table, $values, $query, @query_binds) = @_;
+
+    return $self->SUPER::SimpleUpdateFromSelect(
+        $table, $values, $query, @query_binds
+    ) unless $query =~ /\b\Q$table\E\b/i;
+
+    my $sth = $self->SimpleQuery( $query, @query_binds );
+    return $sth unless $sth;
+
+    my (@binds, @columns);
+    for my $k (sort keys %$values) {
+        push @columns, $k;
+        push @binds, $values->{$k};
+    }
+
+    $table = $self->QuoteName($table) if $self->{'QuoteTableNames'};
+    my $update_query = "UPDATE $table SET "
+        . join( ', ', map "$_ = ?", @columns )
+        .' WHERE ID IN ';
+
+    return $self->SimpleMassChangeFromSelect(
+        $update_query, \@binds,
+        $query, @query_binds
+    );
+}
+
+
+sub DeleteFromSelect {
+    my ($self, $table, $query, @query_binds) = @_;
+
+    return $self->SUPER::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
+    );
+}
+
+sub SimpleMassChangeFromSelect {
+    my ($self, $update_query, $update_binds, $search, @search_binds) = @_;
+
+    my $sth = $self->SimpleQuery( $search, @search_binds );
+    return $sth unless $sth;
+
+
+    # tried TEMPORARY tables, much slower than fetching and delete
+    # also size of ENGINE=MEMORY is limitted by option, on disk
+    # tables more slower than in memory
+    my $res = 0;
+
+    my @ids;
+    while ( my $id = ($sth->fetchrow_array)[0] ) {
+        push @ids, $id;
+        next if @ids < 1000;
+
+        my $q = $update_query .'('. join( ',', ('?')x at ids ) .')';
+        my $sth = $self->SimpleQuery( $q, @$update_binds, splice @ids );
+        return $sth unless $sth;
+
+        $res += $sth->rows;
+    }
+    if ( @ids ) {
+        my $q = $update_query .'('. join( ',', ('?')x at ids ) .')';
+        my $sth = $self->SimpleQuery( $q, @$update_binds, splice @ids );
+        return $sth unless $sth;
+
+        $res += $sth->rows;
+    }
+    return $res == 0? '0E0': $res;
+}
+
+=head2 DatabaseVersion
+
+Returns the MariaDB version, trimming off any -foo identifier
+
+=cut
+
+sub DatabaseVersion {
+    my $self = shift;
+    my $v = $self->SUPER::DatabaseVersion();
+
+    $v =~ s/\-.*$//;
+    return ($v);
+}
+
+=head2 CaseSensitive
+
+Returns undef, since MariaDB's searches are not case sensitive by default
+
+=cut
+
+sub CaseSensitive {
+    my $self = shift;
+    return(undef);
+}
+
+sub DistinctQuery {
+    my $self = shift;
+    my $statementref = shift;
+    my $sb = shift;
+
+    return $self->SUPER::DistinctQuery( $statementref, $sb, @_ )
+        if $sb->_OrderClause !~ /(?<!main)\./;
+
+    if ( substr($self->DatabaseVersion, 0, 1) == 4 ) {
+        local $sb->{'group_by'} = [{FIELD => 'id'}];
+
+        my ($idx, @tmp, @specials) = (0, ());
+        foreach ( @{$sb->{'order_by'}} ) {
+            if ( !exists $_->{'ALIAS'} || ($_->{'ALIAS'}||'') eq "main" ) {
+                push @tmp, $_; next;
+            }
+
+            push @specials,
+                ((($_->{'ORDER'}||'') =~ /^des/i)?'MAX':'MIN')
+                ."(". $_->{'ALIAS'} .".". $_->{'FIELD'} .")"
+                ." __special_sort_$idx";
+            push @tmp, { ALIAS => '', FIELD => "__special_sort_$idx", ORDER => $_->{'ORDER'} };
+            $idx++;
+        }
+
+        local $sb->{'order_by'} = \@tmp;
+        $$statementref = "SELECT ". join( ", ", 'main.*', @specials ) ." FROM $$statementref";
+        $$statementref .= $sb->_GroupClause;
+        $$statementref .= $sb->_OrderClause;
+    } else {
+        local $sb->{'group_by'} = [{FIELD => 'id'}];
+        local $sb->{'order_by'} = [
+            map {
+                ($_->{'ALIAS'}||'') ne "main"
+                ? { %{$_}, FIELD => ((($_->{'ORDER'}||'') =~ /^des/i)?'MAX':'MIN') ."(".$_->{FIELD}.")" }
+                : $_
+            }
+            @{$sb->{'order_by'}}
+        ];
+        $$statementref = "SELECT main.* FROM $$statementref";
+        $$statementref .= $sb->_GroupClause;
+        $$statementref .= $sb->_OrderClause;
+    }
+}
+
+sub Fields {
+    my $self  = shift;
+    my $table = shift;
+
+    my $cache = \%DBIx::SearchBuilder::Handle::FIELDS_IN_TABLE;
+    unless ( $cache->{ lc $table } ) {
+        my $sth = $self->dbh->column_info( undef, undef, $table, '%' )
+            or return ();
+        my $info = $sth->fetchall_arrayref({});
+        foreach my $e ( sort {$a->{'ORDINAL_POSITION'} <=> $b->{'ORDINAL_POSITION'}} @$info ) {
+            push @{ $cache->{ lc $e->{'TABLE_NAME'} } ||= [] }, lc $e->{'COLUMN_NAME'};
+        }
+    }
+    return @{ $cache->{ lc $table } || [] };
+}
+
+=head2 SimpleDateTimeFunctions
+
+Returns hash reference with specific date time functions of this
+database for L<DBIx::SearchBuilder::Handle/DateTimeFunction>.
+
+=cut
+
+sub SimpleDateTimeFunctions {
+    my $self = shift;
+    return $self->{'_simple_date_time_functions'} ||= {
+        %{ $self->SUPER::SimpleDateTimeFunctions(@_) },
+        datetime   => '?',
+        time       => 'TIME(?)',
+
+        hourly     => "DATE_FORMAT(?, '%Y-%m-%d %H')",
+        hour       => 'HOUR(?)',
+
+        date       => 'DATE(?)',
+        daily      => 'DATE(?)',
+
+        day        => 'DAYOFMONTH(?)',
+        dayofmonth => 'DAYOFMONTH(?)',
+
+        monthly    => "DATE_FORMAT(?, '%Y-%m')",
+        month      => 'MONTH(?)',
+
+        annually   => 'YEAR(?)',
+        year       => 'YEAR(?)',
+
+        dayofweek  => "DAYOFWEEK(?) - 1", # 1-7, 1 - Sunday
+        dayofyear  => "DAYOFYEAR(?)", # 1-366
+        weekofyear => "WEEK(?)", # skip mode argument, so it can be controlled in MariaDB config
+    };
+}
+
+
+=head2 ConvertTimezoneFunction
+
+Custom implementation of L<DBIx::SearchBuilder::Handle/ConvertTimezoneFunction>.
+
+Use the following query to get list of timezones:
+
+    SELECT Name FROM mysql.time_zone_name;
+
+See also details on how MariaDB works with mysql timezone tables
+
+    https://mariadb.com/kb/en/time-zones/
+
+=cut
+
+sub ConvertTimezoneFunction {
+    my $self = shift;
+    my %args = (
+        From  => 'UTC',
+        To    => undef,
+        Field => '',
+        @_
+    );
+    return $args{'Field'} unless $args{From} && $args{'To'};
+    return $args{'Field'} if lc $args{From} eq lc $args{'To'};
+    my $dbh = $self->dbh;
+    $_ = $dbh->quote( $_ ) foreach @args{'From', 'To'};
+    return "CONVERT_TZ( $args{'Field'}, $args{'From'}, $args{'To'} )";
+}
+
+sub _DateTimeIntervalFunction {
+    my $self = shift;
+    my %args = ( From => undef, To => undef, @_ );
+
+    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) = @_;
+    # use dbi built in quoting if we have a connection,
+    if ($self->dbh) {
+        return $self->SUPER::QuoteName($name);
+    }
+
+    return sprintf('`%s`', $name);
+}
+
+sub DequoteName {
+    my ($self, $name) = @_;
+
+    # If we have a handle, the base class can do it for us
+    if ($self->dbh) {
+        return $self->SUPER::DequoteName($name);
+    }
+
+    if ($name =~ /^`(.*)`$/) {
+        return $1;
+    }
+    return $name;
+}
+
+sub _ExtractBindValues {
+    my $self  = shift;
+    my $value = shift;
+    return $self->SUPER::_ExtractBindValues( $value, '\\' );
+}
+
+sub _IsMariaDB {
+    my $self = shift;
+
+    # We override DatabaseVersion to chop off "-MariaDB-whatever", so
+    # call super here to get the original version
+    my $v = $self->SUPER::DatabaseVersion();
+
+    return ($v =~ /mariadb/i);
+}
+
+sub _RequireQuotedTables {
+    my $self = shift;
+
+    # MariaDB version does not match mysql, and hasn't added new reserved words
+    # like "groups".
+    return 0;
+}
+
+=head2 HasSupportForCombineSearchAndCount
+
+MariaDB 10.2+ supports this.
+
+=cut
+
+sub HasSupportForCombineSearchAndCount {
+    my $self = shift;
+    my ($version) = $self->DatabaseVersion =~ /^(\d+\.\d+)/;
+
+    return (version->parse('v'.$version) >= version->parse('v10.2')) ? 1 : 0;
+}
+
+sub CastAsDecimal {
+    my $self  = shift;
+    my $field = shift or return;
+
+    # CAST($field AS DECIMAL) rounds values to integers by default. It supports
+    # specific precisions like CAST($field AS DECIMAL(5,2)), but we don't know
+    # the precisions in advance. +0 works like other dbs.
+    return "($field+0)";
+}
+
+1;
diff --git a/t/00.load.t b/t/00.load.t
index cfc6dcb..c03ba27 100644
--- a/t/00.load.t
+++ b/t/00.load.t
@@ -1,4 +1,4 @@
-use Test::More tests => 12;
+use Test::More tests => 13;
 
 BEGIN { use_ok("DBIx::SearchBuilder"); }
 BEGIN { use_ok("DBIx::SearchBuilder::Handle"); }
@@ -19,4 +19,4 @@ BEGIN { use_ok("DBIx::SearchBuilder::Handle::Sybase"); }
 BEGIN { use_ok("DBIx::SearchBuilder::Handle::SQLite"); }
 BEGIN { use_ok("DBIx::SearchBuilder::Record"); }
 BEGIN { use_ok("DBIx::SearchBuilder::Record::Cachable"); }
-
+BEGIN { use_ok("DBIx::SearchBuilder::Handle::MariaDB"); }
diff --git a/t/01records.t b/t/01records.t
index dd143ca..d4954ec 100644
--- a/t/01records.t
+++ b/t/01records.t
@@ -270,6 +270,18 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Address (
+    id integer AUTO_INCREMENT,
+    Name varchar(36),
+    Phone varchar(18),
+    EmployeeId int(8),
+    PRIMARY KEY (id)) CHARACTER SET utf8mb4
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Address (
diff --git a/t/01searches.t b/t/01searches.t
index 3320061..7ff3822 100644
--- a/t/01searches.t
+++ b/t/01searches.t
@@ -479,6 +479,21 @@ sub cleanup_schema_mysql { [
     "DROP TABLE Users", 
 ] }
 
+sub schema_mariadb {[
+	"DROP TABLE IF EXISTS Users",
+<<EOF
+CREATE TABLE Users (
+    id integer AUTO_INCREMENT,
+    Login varchar(18) NOT NULL,
+    Name varchar(36),
+    Phone varchar(18),
+    PRIMARY KEY (id))
+EOF
+]}
+sub cleanup_schema_mariadb { [
+    "DROP TABLE Users",
+] }
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Users (
diff --git a/t/02distinct_values.t b/t/02distinct_values.t
index 826db21..0908de5 100644
--- a/t/02distinct_values.t
+++ b/t/02distinct_values.t
@@ -88,6 +88,17 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Users (
+    id integer AUTO_INCREMENT,
+    Login varchar(18) NOT NULL,
+    GroupName varchar(36),
+    PRIMARY KEY (id))
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Users (
diff --git a/t/02null_order.t b/t/02null_order.t
index fab111d..099e5dc 100644
--- a/t/02null_order.t
+++ b/t/02null_order.t
@@ -110,6 +110,20 @@ sub cleanup_schema_mysql { [
     "DROP TABLE Users", 
 ] }
 
+sub schema_mariadb {[
+    "DROP TABLE IF EXISTS Users",
+<<EOF
+CREATE TABLE Users (
+    id integer AUTO_INCREMENT,
+    Value integer,
+    PRIMARY KEY (id)
+)
+EOF
+]}
+sub cleanup_schema_mariadb { [
+    "DROP TABLE Users",
+] }
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Users (
diff --git a/t/02order.t b/t/02order.t
index e1eff21..14e19b1 100644
--- a/t/02order.t
+++ b/t/02order.t
@@ -57,7 +57,7 @@ diag "generate data" if $ENV{TEST_VERBOSE};
 
 my @fields = (
     'Name',
-    $d eq 'Oracle' ? 'TO_CHAR(Name)' : $d eq 'mysql' ? 'BINARY(Name)' : 'CAST(Name AS TEXT)'
+    $d eq 'Oracle' ? 'TO_CHAR(Name)' : ($d eq 'mysql' || $d eq 'MariaDB') ? 'BINARY(Name)' : 'CAST(Name AS TEXT)'
 );
 
 diag "test ordering objects by fields on Tags table" if $ENV{TEST_VERBOSE};
@@ -172,6 +172,21 @@ sub schema_mysql { [
     "CREATE INDEX Tags1 ON Tags (Name)"
 ] }
 
+sub schema_mariadb { [
+    "CREATE TEMPORARY TABLE Objects (
+        id integer AUTO_INCREMENT,
+        Name varchar(36),
+        PRIMARY KEY (id)
+    )",
+    "CREATE TEMPORARY TABLE Tags (
+        id integer AUTO_INCREMENT,
+        Object integer NOT NULL,
+        Name varchar(36),
+        PRIMARY KEY (id)
+    )",
+    "CREATE INDEX Tags1 ON Tags (Name)"
+] }
+
 sub schema_pg { [
     "CREATE TEMPORARY TABLE Objects (
         id serial PRIMARY KEY,
diff --git a/t/02records_cachable.t b/t/02records_cachable.t
index 9418990..410888c 100644
--- a/t/02records_cachable.t
+++ b/t/02records_cachable.t
@@ -106,6 +106,18 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Address (
+    id integer AUTO_INCREMENT,
+    Name varchar(36),
+    Phone varchar(18),
+    EmployeeId int(8),
+    PRIMARY KEY (id))
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Address (
diff --git a/t/02records_datetime.t b/t/02records_datetime.t
index 6882729..ddbae31 100644
--- a/t/02records_datetime.t
+++ b/t/02records_datetime.t
@@ -49,7 +49,7 @@ SKIP: {
         my ($got) = $handle->dbh->selectrow_array("SELECT datetime(?,'localtime')", undef, $check);
         $skip_tz_tests = 1 if $got eq $check;
     }
-    elsif ($d eq 'mysql') {
+    elsif ( $d eq 'mysql' || $d eq 'MariaDB' ) {
         my $check = '2013-04-01 16:00:00';
         my ($got) = $handle->dbh->selectrow_array(
             "SELECT CONVERT_TZ(?, ?, ?)", undef, $check, 'UTC', 'Europe/Moscow'
@@ -289,6 +289,17 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Users (
+    id integer AUTO_INCREMENT,
+    Expires DATETIME NULL,
+    PRIMARY KEY (id)
+)
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Users (
diff --git a/t/02records_dt_interval.t b/t/02records_dt_interval.t
index fa5abbf..f08ea50 100644
--- a/t/02records_dt_interval.t
+++ b/t/02records_dt_interval.t
@@ -94,6 +94,19 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Users (
+    id integer AUTO_INCREMENT,
+    Created DATETIME NULL,
+    Resolved DATETIME NULL,
+    Result integer NULL,
+    PRIMARY KEY (id)
+)
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Users (
diff --git a/t/02records_integers.t b/t/02records_integers.t
index 95b8504..9081e50 100644
--- a/t/02records_integers.t
+++ b/t/02records_integers.t
@@ -148,6 +148,17 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE MyTable (
+    id integer PRIMARY KEY AUTO_INCREMENT,
+    Optional integer NULL,
+    Mandatory integer NOT NULL DEFAULT 1
+)
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE MyTable (
diff --git a/t/02records_object.t b/t/02records_object.t
index 34576db..4799fe8 100644
--- a/t/02records_object.t
+++ b/t/02records_object.t
@@ -88,6 +88,21 @@ CREATE TEMPORARY TABLE Phones (
 } ]
 }
 
+sub schema_mariadb {
+[ q{
+CREATE TEMPORARY TABLE Employees (
+	id integer AUTO_INCREMENT primary key,
+	Name varchar(36)
+)
+}, q{
+CREATE TEMPORARY TABLE Phones (
+	id integer AUTO_INCREMENT primary key,
+	Employee integer NOT NULL,
+	Phone varchar(18)
+)
+} ]
+}
+
 sub schema_pg {
 [ q{
 CREATE TEMPORARY TABLE Employees (
diff --git a/t/02searches_function.t b/t/02searches_function.t
index e9a25a9..94f5d4c 100644
--- a/t/02searches_function.t
+++ b/t/02searches_function.t
@@ -190,6 +190,28 @@ CREATE TEMPORARY TABLE `Groups` (
 ]
 }
 
+sub schema_mariadb {
+[
+q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36),
+    DeptNumber varchar(36)
+) },
+q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+q{
+CREATE TEMPORARY TABLE `Groups` (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+]
+}
+
 sub schema_pg {
 [
 q{
diff --git a/t/02searches_joins.t b/t/02searches_joins.t
index cbec237..64b86cc 100644
--- a/t/02searches_joins.t
+++ b/t/02searches_joins.t
@@ -343,6 +343,27 @@ CREATE TEMPORARY TABLE `Groups` (
 ]
 }
 
+sub schema_mariadb {
+[
+q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36)
+) },
+q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+q{
+CREATE TEMPORARY TABLE `Groups` (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+]
+}
+
 sub schema_pg {
 [
 q{
diff --git a/t/03cud_from_select.t b/t/03cud_from_select.t
index 9f1aed4..902ca92 100644
--- a/t/03cud_from_select.t
+++ b/t/03cud_from_select.t
@@ -184,6 +184,33 @@ sub cleanup_schema_mysql { [
     "DROP TABLE UsersToGroups", 
 ] }
 
+# TEMPORARY tables can not be referenced more than once
+# in the same query, use real table for UsersToGroups
+sub schema_mariadb {
+[
+q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36)
+) },
+q{
+CREATE TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+q{
+CREATE TEMPORARY TABLE `Groups` (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+]
+}
+
+sub cleanup_schema_mariadb { [
+    "DROP TABLE UsersToGroups",
+] }
+
 sub schema_pg {
 [
 q{
diff --git a/t/03searches_bind.t b/t/03searches_bind.t
index 30d4739..05de4e6 100644
--- a/t/03searches_bind.t
+++ b/t/03searches_bind.t
@@ -189,6 +189,26 @@ CREATE TEMPORARY TABLE `Groups` (
     ]
 }
 
+sub schema_mariadb {
+    [   q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36)
+) },
+        q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+        q{
+CREATE TEMPORARY TABLE `Groups` (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+    ]
+}
+
 sub schema_pg {
     [   q{
 CREATE TEMPORARY TABLE Users (
diff --git a/t/03searches_combine.t b/t/03searches_combine.t
index 3b28c5d..8250abe 100644
--- a/t/03searches_combine.t
+++ b/t/03searches_combine.t
@@ -141,6 +141,26 @@ CREATE TEMPORARY TABLE `Groups` (
     ]
 }
 
+sub schema_mariadb {
+    [   q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36)
+) },
+        q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+        q{
+CREATE TEMPORARY TABLE `Groups` (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+    ]
+}
+
 sub schema_pg {
     [   q{
 CREATE TEMPORARY TABLE Users (
diff --git a/t/03transactions.t b/t/03transactions.t
index ef0f973..07ad8e6 100644
--- a/t/03transactions.t
+++ b/t/03transactions.t
@@ -170,6 +170,18 @@ EOF
 
 }
 
+sub schema_mariadb {
+<<EOF;
+CREATE TEMPORARY TABLE Address (
+    id integer AUTO_INCREMENT,
+    Name varchar(36),
+    Phone varchar(18),
+    EmployeeId int(8),
+    PRIMARY KEY (id)) ENGINE='InnoDB'
+EOF
+
+}
+
 sub schema_pg {
 <<EOF;
 CREATE TEMPORARY TABLE Address (
diff --git a/t/11schema_records.t b/t/11schema_records.t
index 313f127..5d83a78 100644
--- a/t/11schema_records.t
+++ b/t/11schema_records.t
@@ -235,6 +235,26 @@ q{CREATE TEMPORARY TABLE `Groups` (
 ]
 }
 
+sub schema_mariadb {
+[ q{
+CREATE TEMPORARY TABLE Employees (
+	id integer AUTO_INCREMENT primary key,
+	Name varchar(36)
+)
+}, q{
+CREATE TEMPORARY TABLE Phones (
+	id integer AUTO_INCREMENT primary key,
+	Employee integer NOT NULL,
+	Phone varchar(18)
+)
+},
+q{CREATE TEMPORARY TABLE `Groups` (
+	id integer AUTO_INCREMENT primary key,
+	Name varchar(36)
+) }
+]
+}
+
 sub schema_pg {
 [ q{
 CREATE TEMPORARY TABLE Employees (
diff --git a/t/20set_edge_cases.t b/t/20set_edge_cases.t
index d22b1ee..e7765af 100644
--- a/t/20set_edge_cases.t
+++ b/t/20set_edge_cases.t
@@ -104,6 +104,17 @@ EOF
 
 }
 
+sub schema_mariadb {
+    <<EOF;
+CREATE TEMPORARY TABLE Address (
+    id integer AUTO_INCREMENT,
+    Name varchar(36) NOT NULL,
+    Counter int(8) NOT NULL,
+    PRIMARY KEY (id))
+EOF
+
+}
+
 sub schema_pg {
     <<EOF;
 CREATE TEMPORARY TABLE Address (
diff --git a/t/utils.pl b/t/utils.pl
index e21e3f9..fc1a9bf 100644
--- a/t/utils.pl
+++ b/t/utils.pl
@@ -12,14 +12,15 @@ Array of all supported DBD drivers.
 =cut
 
 our @SupportedDrivers = qw(
-	Informix
-	mysql
-	mysqlPP
-	ODBC
-	Oracle
-	Pg
-	SQLite
-	Sybase
+    Informix
+    MariaDB
+    mysql
+    mysqlPP
+    ODBC
+    Oracle
+    Pg
+    SQLite
+    Sybase
 );
 
 =head2 @AvailableDrivers
@@ -117,6 +118,19 @@ sub connect_mysql
 	);
 }
 
+sub connect_mariadb
+{
+    my $handle = shift;
+    return $handle->Connect(
+        Driver => 'MariaDB',
+        Database => $ENV{'SB_TEST_MARIADB'},
+        Host => $ENV{'SB_TEST_MARIADB_HOST'},
+        Port => $ENV{'SB_TEST_MARIADB_PORT'},
+        User => $ENV{'SB_TEST_MARIADB_USER'} || 'root',
+        Password => $ENV{'SB_TEST_MARIADB_PASS'} || '',
+    );
+}
+
 sub connect_pg
 {
 	my $handle = shift;

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

Summary of changes:
 Changes                                            |  3 +
 MANIFEST                                           |  1 +
 META.yml                                           |  2 +-
 README                                             |  2 +-
 lib/DBIx/SearchBuilder.pm                          |  4 +-
 lib/DBIx/SearchBuilder/Handle.pm                   | 16 ++++-
 .../SearchBuilder/Handle/{mysql.pm => MariaDB.pm}  | 82 ++++++++++++++--------
 lib/DBIx/SearchBuilder/Record.pm                   | 10 +--
 t/00.load.t                                        |  4 +-
 t/01records.t                                      | 50 ++++++++++---
 t/01searches.t                                     | 15 ++++
 t/02distinct_values.t                              | 11 +++
 t/02null_order.t                                   | 14 ++++
 t/02order.t                                        | 17 ++++-
 t/02records_cachable.t                             | 12 ++++
 t/02records_datetime.t                             | 13 +++-
 t/02records_dt_interval.t                          | 13 ++++
 t/02records_integers.t                             | 11 +++
 t/02records_object.t                               | 15 ++++
 t/02searches_function.t                            | 22 ++++++
 t/02searches_joins.t                               | 21 ++++++
 t/03cud_from_select.t                              | 27 +++++++
 t/03searches_bind.t                                | 20 ++++++
 t/03searches_combine.t                             | 20 ++++++
 t/03transactions.t                                 | 12 ++++
 t/11schema_records.t                               | 20 ++++++
 t/20set_edge_cases.t                               | 11 +++
 t/utils.pl                                         | 30 +++++---
 28 files changed, 419 insertions(+), 59 deletions(-)
 copy lib/DBIx/SearchBuilder/Handle/{mysql.pm => MariaDB.pm} (84%)


hooks/post-receive
-- 
dbix-searchbuilder


More information about the Bps-public-commit mailing list