[Bps-public-commit] dbix-searchbuilder branch search-support-bind created. 1.69-1-ge1c13a8

BPS Git Server git at git.bestpractical.com
Tue Aug 31 19:11:58 UTC 2021


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, search-support-bind has been created
        at  e1c13a8c7a1a69233094c81b990dd58147cf2d06 (commit)

- Log -----------------------------------------------------------------
commit e1c13a8c7a1a69233094c81b990dd58147cf2d06
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Aug 30 04:19:13 2021 +0800

    Support bind values for searches
    
    This is mainly to make database(especially Oracle) happy and cache
    plans, which improves performance.

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 2356f3b..f02b76d 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -10,6 +10,7 @@ use Clone qw();
 use Encode qw();
 use Scalar::Util qw(blessed);
 use DBIx::SearchBuilder::Util qw/ sorted_values /;
+our $PREFER_BIND = $ENV{SB_PREFER_BIND} // 1;
 
 =head1 NAME
 
@@ -151,6 +152,7 @@ sub CleanSlate {
         group_by
         columns
         query_hint
+        _bind_values
     );
 
     #we have no limit statements. DoSearch won't work.
@@ -238,7 +240,7 @@ sub _DoSearch {
     delete $self->{'items'};
     $self->{'itemscount'} = 0;
 
-    my $records = $self->_Handle->SimpleQuery($QueryString);
+    my $records = $self->_Handle->SimpleQuery( $QueryString, @{ $self->{_bind_values} || [] } );
     return 0 unless $records;
 
     while ( my $row = $records->fetchrow_hashref() ) {
@@ -294,7 +296,7 @@ sub _DoCount {
     my $all  = shift || 0;
 
     my $QueryString = $self->BuildSelectCountQuery();
-    my $records     = $self->_Handle->SimpleQuery($QueryString);
+    my $records     = $self->_Handle->SimpleQuery( $QueryString, @{ $self->{_bind_values} || [] } );
     return 0 unless $records;
 
     my @row = $records->fetchrow_array();
@@ -353,7 +355,7 @@ Build up all of the joins we need to perform this query.
 sub _BuildJoins {
     my $self = shift;
 
-        return ( $self->_Handle->_BuildJoins($self) );
+    return ( $self->_Handle->_BuildJoins($self, @_) );
 
 }
 
@@ -419,19 +421,25 @@ sub _isLimited {
 
 
 
-=head2 BuildSelectQuery
+=head2 BuildSelectQuery PreferBind => 1|0
 
 Builds a query string for a "SELECT rows from Tables" statement for this SearchBuilder object
 
+If C<PreferBind> is true, the generated query will use bind variables where
+possible. If C<PreferBind> is not passed, it defaults to package variable
+C<$DBIx::SearchBuilder::PREFER_BIND>, which defaults to
+C<$ENV{SB_PREFER_BIND}>(if it's defined) or true.
+
 =cut
 
 sub BuildSelectQuery {
     my $self = shift;
+    my %args = ( PreferBind => $PREFER_BIND, @_ );
 
     # The initial SELECT or SELECT DISTINCT is decided later
 
-    my $QueryString = $self->_BuildJoins . " ";
-    $QueryString .= $self->_WhereClause . " "
+    my $QueryString = $self->_BuildJoins(%args) . " ";
+    $QueryString .= $self->_WhereClause(%args) . " "
       if ( $self->_isLimited > 0 );
 
     my $QueryHint = $self->QueryHintFormatted;
@@ -461,7 +469,7 @@ sub BuildSelectQuery {
 
 
 
-=head2 BuildSelectCountQuery
+=head2 BuildSelectCountQuery PreferBind => 1|0
 
 Builds a SELECT statement to find the number of rows this SearchBuilder object would find.
 
@@ -469,12 +477,13 @@ Builds a SELECT statement to find the number of rows this SearchBuilder object w
 
 sub BuildSelectCountQuery {
     my $self = shift;
+    my %args = ( PreferBind => $PREFER_BIND, @_ );
 
     #TODO refactor DoSearch and DoCount such that we only have
     # one place where we build most of the querystring
-    my $QueryString = $self->_BuildJoins . " ";
+    my $QueryString = $self->_BuildJoins(%args) . " ";
 
-    $QueryString .= $self->_WhereClause . " "
+    $QueryString .= $self->_WhereClause(%args) . " "
       if ( $self->_isLimited > 0 );
 
 
@@ -640,7 +649,7 @@ sub DistinctFieldValues {
     }
 
     my $dbh = $self->_Handle->dbh;
-    my $list = $dbh->selectcol_arrayref( $query_string, { MaxRows => $args{'Max'} } );
+    my $list = $dbh->selectcol_arrayref( $query_string, { MaxRows => $args{'Max'} }, @{ $self->{_bind_values} || [] } );
     return $list? @$list : ();
 }
 
@@ -877,7 +886,7 @@ sub Limit {
                     warn "Collection in '$args{OPERATOR}' with more than one column selected, using first";
                     splice @{ $args{'VALUE'}{'columns'} }, 1;
                 }
-                $args{'VALUE'} = '('. $args{'VALUE'}->BuildSelectQuery .')';
+                $args{'VALUE'} = '('. $args{'VALUE'}->BuildSelectQuery(PreferBind => 0) .')';
                 $args{'QUOTEVALUE'} = 0;
             }
             elsif ( ref $args{'VALUE'} ) {
@@ -1061,7 +1070,7 @@ sub _WhereClause {
     #Go through all the generic restrictions and build up the "generic_restrictions" subclause
     # That's the only one that SearchBuilder builds itself.
     # Arguably, the abstraction should be better, but I don't really see where to put it.
-    $self->_CompileGenericRestrictions();
+    $self->_CompileGenericRestrictions(@_);
 
     #Go through all restriction types. Build the where clause from the
     #Various subclauses.
@@ -1081,6 +1090,7 @@ sub _WhereClause {
 
 sub _CompileGenericRestrictions {
     my $self = shift;
+    my %args = @_;
 
     my $result = '';
     #Go through all the restrictions of this type. Buld up the generic subclause
@@ -1092,6 +1102,14 @@ sub _CompileGenericRestrictions {
                 $result .= ' '. $entry . ' ';
             }
             else {
+                if ( $args{PreferBind} ) {
+                    my ( $placeholder, @bind_values ) = $self->_ExtractBindValues( $entry->{value} );
+                    if ( $placeholder ) {
+                        push @{ $self->{_bind_values} }, @bind_values;
+                        $result .= join ' ', @{$entry}{qw(field op)}, $placeholder;
+                        next;
+                    }
+                }
                 $result .= join ' ', @{$entry}{qw(field op value)};
             }
         }
@@ -1907,6 +1925,27 @@ DEPRECATED AND DOES NOTHING.
 
 sub ImportRestrictions { }
 
+
+sub _ExtractBindValues {
+    my $self  = shift;
+    my $value = shift;
+    my $placeholder;
+    my @bind_values;
+    if ( $value =~ /^\d+$/ ) {
+        $placeholder = '?';
+        push @bind_values, $value;
+    }
+    elsif ( $value =~ /^E?'.*'$/ ) {
+        $placeholder = '?';
+        push @bind_values, $self->_Handle->Dequote($value);
+    }
+    elsif ( $value =~ /^\s*\(\s*(?!SELECT)(.+)\)\s*$/is ) {
+        push @bind_values, $self->_Handle->SplitAndDequote($1);
+        $placeholder = '(' . join( ', ', ('?') x @bind_values ) . ')';
+    }
+    return ( $placeholder, @bind_values );
+}
+
 # not even documented
 sub DEBUG { warn "DEBUG is deprecated" }
 
diff --git a/lib/DBIx/SearchBuilder/Handle.pm b/lib/DBIx/SearchBuilder/Handle.pm
index ed49ff7..937cd15 100755
--- a/lib/DBIx/SearchBuilder/Handle.pm
+++ b/lib/DBIx/SearchBuilder/Handle.pm
@@ -1222,6 +1222,7 @@ sub _NormalJoin {
 sub _BuildJoins {
     my $self = shift;
     my $sb   = shift;
+    my %args = @_;
 
     $self->OptimizeJoins( SearchBuilder => $sb );
     my $table = $self->{'QuoteTableNames'} ? $self->QuoteName($sb->Table) : $sb->Table;
@@ -1230,6 +1231,7 @@ sub _BuildJoins {
     my %processed = map { /^\S+\s+(\S+)$/; $1 => 1 } @{ $sb->{'aliases'} };
     $processed{'main'} = 1;
 
+    $sb->{_bind_values} = [];
     # get a @list of joins that have not been processed yet, but depend on processed join
     my $joins = $sb->{'left_joins'};
     while ( my @list =
@@ -1245,9 +1247,24 @@ sub _BuildJoins {
 
             $join_clause .= $meta->{'alias_string'} . " ON ";
             my @tmp = map {
-                    ref($_)?
-                        $_->{'field'} .' '. $_->{'op'} .' '. $_->{'value'}:
+                    if ( ref $_ ) {
+                        if ( $args{PreferBind} ) {
+                            my ( $placeholder, @bind_values ) = $sb->_ExtractBindValues( $_->{'value'} );
+                            if ( $placeholder ) {
+                                push @{ $sb->{_bind_values} }, @bind_values;
+                                $_->{'field'} . ' ' . $_->{'op'} . ' ' . $placeholder;
+                            }
+                            else {
+                                $_->{'field'} .' '. $_->{'op'} .' '. $_->{'value'};
+                            }
+                        }
+                        else {
+                            $_->{'field'} .' '. $_->{'op'} .' '. $_->{'value'};
+                        }
+                    }
+                    else {
                         $_
+                    }
                 }
                 map { ('(', @$_, ')', $aggregator) } sorted_values($meta->{'criteria'});
             pop @tmp;
@@ -1813,6 +1830,98 @@ sub DequoteName {
     return $name;
 }
 
+=head2 Dequote
+
+Undo the quote effects for quoted values
+
+=cut
+
+sub Dequote {
+    my ( $self, $value ) = @_;
+    if ( $value && $value =~ /^(E?)'(.*)'$/ ) {
+        $value = $2;
+        if ($1) {
+            $value =~ s!\\(.)!$1!g;
+        }
+        else {
+            $value =~ s!''!'!g;
+        }
+    }
+    return $value;
+}
+
+=head2 SplitAndDequote
+
+Split comma separated quoted values and return a list of dequoted values.
+
+This is useful to parse value of C<IN> operator.
+
+=cut
+
+sub SplitAndDequote {
+    my $self                = shift;
+    my $string              = shift;
+    my $default_escape_char = shift || q{'};
+    return $string unless defined $string;
+
+    my @chars       = split //, $string;
+    my $value       = '';
+    my $escape_char = $default_escape_char;
+
+    my @values;
+    my $in = 0;    # keep state in the loop: is it in a value?
+    while ( defined( my $c = shift @chars ) ) {
+        my $escaped;
+        if ( $c eq $escape_char && $in ) {
+            if ( $escape_char eq q{'} ) {
+                if ( ( $chars[0] || '' ) eq q{'} ) {
+                    $c       = shift @chars;
+                    $escaped = 1;
+                }
+            }
+            else {
+                $c       = shift @chars;
+                $escaped = 1;
+            }
+        }
+
+        if ($in) {
+            if ( $c eq q{'} ) {
+                if ( !$escaped ) {
+                    push @values, $value;
+                    $in          = 0;
+                    $value       = '';
+                    $escape_char = $default_escape_char;
+                    next;
+                }
+            }
+            $value .= $c;
+        }
+        else {
+            if ( $c eq q{'} ) {
+                $in = 1;
+            }
+            elsif ( $c eq 'E' ) {
+                $escape_char = '\\';
+            }
+            elsif ( $c =~ /[-\d]/ ) {
+                $value .= $c;
+                while ( my $next = shift @chars ) {
+                    if ( $next =~ /\d/ ) {
+                        $value .= $c;
+                    }
+                    else {
+                        push @values, $value;
+                        $value = '';
+                        last;
+                    }
+                }
+            }
+        }
+    }
+    return @values;
+}
+
 sub _RequireQuotedTables { return 0 };
 
 =head2 DESTROY
diff --git a/lib/DBIx/SearchBuilder/Handle/mysql.pm b/lib/DBIx/SearchBuilder/Handle/mysql.pm
index 0e24364..b6de8b6 100755
--- a/lib/DBIx/SearchBuilder/Handle/mysql.pm
+++ b/lib/DBIx/SearchBuilder/Handle/mysql.pm
@@ -323,6 +323,22 @@ sub DequoteName {
     return $name;
 }
 
+sub Dequote {
+    my ($self, $value) = @_;
+    if ( $value && $value =~ /^'(.*)'$/ ) {
+        $value = $1;
+        $value =~ s!\\(.)!$1!g;
+    }
+    return $value;
+}
+
+sub SplitAndDequote {
+    my $self  = shift;
+    my $value = shift;
+    return $self->SUPER::SplitAndDequote( $value, '\\' );
+}
+
+
 sub _IsMariaDB {
     my $self = shift;
 

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


hooks/post-receive
-- 
dbix-searchbuilder


More information about the Bps-public-commit mailing list