Re: retrieve() hits db even if obj present in %Live_Objects.

[prev] [thread] [next] [Date index for 2004/06/25]

From: Tim Bunce
Subject: Re: retrieve() hits db even if obj present in %Live_Objects.
Date: 10:16 on 25 Jun 2004
--jq0ap7NbKX2Kqbes
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Thu, Jun 24, 2004 at 12:05:42PM +0100, Tim Bunce wrote:
> On Thu, Jun 24, 2004 at 09:30:30AM +0100, Tim Bunce wrote:
> > On Thu, Jun 24, 2004 at 05:50:13AM +0100, trlorenz@xxxxxxx.xxx wrote:
> > > Hi, all.
> > > 
> > > Unless I'm mistaken, retrieve() makes a trip to the db even if the object being sought is already present in the CDBI object cache. (Apparently this is because retrieve() is implemented through _do_search(), which has no concept of an id-only lookup.) Only after the data is picked up from the db is the id used for a lookup into the cache (and any fresh data from the db lookup discarded).
> > > 
> > > Is this correct?
> > 
> > Yeap. Needs fixing.
> 
> I've attached a (tested) patch that fixes this and also fixes some
> other problems that were shown up by it.

I've attatched revised versions of the two patches.


> The first patch is an updated and tested version of my previous
> _live_object_key() patch. This one includes fixing remove_from_object_index(),
> and _live_object_key() now works correctly if called as an instance method.

This version also fixes a problem with _live_object_key() which would
autovivify PK fields in its argument hash. The only difference from last is:
-       return "" unless @primary_columns == grep defined, @{$data}{@primary_columns};
+       return "" unless @primary_columns == grep defined $data->{$_}, @primary_columns;


> The second patch assumes the first has been applied. It adds code to
> retrieve() to check the object cache first and avoid a database lookup.

This one adds the object cache check into _do_search instead so it works
in more situations. (Two more in the test suite.)

> It also:
> 
>  - Fixes delete() to call $self->remove_from_object_index;
> 
>  - Calls $class->clear_object_index when new relationships are defined
>    for a class. Otherwise cached objects may have columns that ought
>    to be inflated but aren't because the object was fetched before the
>    relationship was defined. Unlikely to be an issue in practice but
>    the test suite does that.
> 
>  - Simplifies the code path through retrieve()
> 
>  - Fixes the "parameters don't include values for all primary key
>    columns" test which was too simplistic (I wrote it originally :).

All that still applies. And there's one other benefit now:

   - search(foo=>..., bar=>...) and search(bar=>..., foo=>...) will now
     generate the same SQL so fewer statement handles will be created/cached.

All tests pass.

Tony, any opinions on these?

Tim.

--jq0ap7NbKX2Kqbes
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="Class-DBI-0.96-live_object_key.patch"

Only in Class-DBI-0.96-live_object_key: Makefile
Only in Class-DBI-0.96-live_object_key: blib
diff -u -r Class-DBI-0.96.orig/lib/Class/DBI.pm Class-DBI-0.96-live_object_key/lib/Class/DBI.pm
--- Class-DBI-0.96.orig/lib/Class/DBI.pm	Fri Apr 30 08:22:12 2004
+++ Class-DBI-0.96-live_object_key/lib/Class/DBI.pm	Fri Jun 25 11:08:50 2004
@@ -509,19 +509,23 @@
 my %Live_Objects;
 my $Init_Count = 0;
 
+sub _live_object_key {
+	my ($class, $data) = @_;
+	my @primary_columns = $class->primary_columns;
+
+	# no key unless all PK columns are defined
+	return "" unless @primary_columns == grep defined $data->{$_}, @primary_columns;
+
+	# create single unique key for this object
+	return join "\030", ref($class)||$class, map { $_ . "\032" . $data->{$_} }
+		       sort @primary_columns;
+}
+
 sub _init {
 	my $class = shift;
 	my $data = shift || {};
 	my $obj;
-	my $obj_key = "";
-
-	my @primary_columns = $class->primary_columns;
-	if (@primary_columns == grep defined, @{$data}{@primary_columns}) {
-
-		# create single unique key for this object
-		$obj_key = join "|", $class, map { $_ . '=' . $data->{$_} }
-			sort @primary_columns;
-	}
+	my $obj_key = $class->_live_object_key($data);
 
 	unless (defined($obj = $Live_Objects{$obj_key})) {
 
@@ -548,11 +552,7 @@
 
 sub remove_from_object_index {
 	my $self            = shift;
-	my @primary_columns = $self->primary_columns;
-	my %data;
-	@data{@primary_columns} = $self->get(@primary_columns);
-	my $obj_key = join "|", ref $self, map $_ . '=' . $data{$_},
-		sort @primary_columns;
+	my $obj_key = $self->_live_object_key($self);
 	delete $Live_Objects{$obj_key};
 }
 
Only in Class-DBI-0.96-live_object_key: pm_to_blib

--jq0ap7NbKX2Kqbes
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="Class-DBI-0.96-search-from-cache.patch"

Only in Class-DBI-0.96-live_object_key: Makefile
Only in Class-DBI-0.96: Makefile.old
Only in Class-DBI-0.96-live_object_key: blib
diff -u -r Class-DBI-0.96-live_object_key/lib/Class/DBI/Relationship.pm Class-DBI-0.96/lib/Class/DBI/Relationship.pm
--- Class-DBI-0.96-live_object_key/lib/Class/DBI/Relationship.pm	Sun Apr 25 16:33:36 2004
+++ Class-DBI-0.96/lib/Class/DBI/Relationship.pm	Thu Jun 24 11:24:34 2004
@@ -20,6 +20,7 @@
 	my $proto = shift;
 	my $name  = shift;
 	my ($class, $accessor, $foreign_class, $args) = $proto->remap_arguments(@_);
+	$class->clear_object_index;
 	return $proto->new({
 			name          => $name,
 			class         => $class,
diff -u -r Class-DBI-0.96-live_object_key/lib/Class/DBI.pm Class-DBI-0.96/lib/Class/DBI.pm
--- Class-DBI-0.96-live_object_key/lib/Class/DBI.pm	Fri Jun 25 11:08:50 2004
+++ Class-DBI-0.96/lib/Class/DBI.pm	Fri Jun 25 11:07:54 2004
@@ -661,23 +661,29 @@
 }
 
 sub retrieve {
-	my $class           = shift;
+	my $class = shift;
+	my @args  = @_;
 	my @primary_columns = $class->primary_columns
 		or return $class->_croak(
 		"Can't retrieve unless primary columns are defined");
-	my %key_value;
-	if (@_ == 1 && @primary_columns == 1) {
-		my $id = shift;
-		return unless defined $id;
-		return $class->_croak("Can't retrieve a reference") if ref($id);
-		$key_value{ $primary_columns[0] } = $id;
-	} else {
-		%key_value = @_;
-		$class->_croak(
-			"$class->retrieve(@_) parameters don't include values for all primary key columns (@primary_columns)"
-			)
-			if keys %key_value < @primary_columns;
+
+	if (@args == 1 && @primary_columns == 1) {
+		my $id = shift @args;
+		return $class->_croak("Can't retrieve a reference")
+			if ref $id and !UNIVERSAL::isa($id => 'Class::DBI');
+		@args = ($primary_columns[0] => $id)
+	}
+
+	my %key_value = @args;
+	while (my ($col, $val) = splice @args, 0, 2) { # as per _do_search:
+		my $column = $class->find_column($col)
+			|| (List::Util::first { $_->accessor eq $col } $class->columns)
+			|| $class->_croak("$col is not a column of $class");
+		$key_value{$column} = $val; # search() deflate for us
 	}
+	$class->_croak("$class->retrieve(@_) parameters don't include defined values for all primary key columns (@primary_columns)")
+		unless @primary_columns == grep defined, @key_value{@primary_columns};
+
 	my @rows = $class->search(%key_value);
 	$class->_carp("$class->retrieve(@_) selected " . @rows . " rows")
 		if @rows > 1;
@@ -737,6 +743,7 @@
 sub delete {
 	my $self = shift;
 	return $self->_search_delete(@_) if not ref $self;
+	$self->remove_from_object_index;
 	$self->call_trigger('before_delete');
 
 	eval { $self->sql_DeleteMe->execute($self->id) };
@@ -1075,23 +1082,43 @@
 	my $class = ref $proto || $proto;
 
 	@args = %{ $args[0] } if ref $args[0] eq "HASH";
-	my (@cols, @vals);
 	my $search_opts = @args % 2 ? pop @args : {};
+	my %search_for;
 	while (my ($col, $val) = splice @args, 0, 2) {
 		my $column = $class->find_column($col)
 			|| (List::Util::first { $_->accessor eq $col } $class->columns)
 			|| $class->_croak("$col is not a column of $class");
-		push @cols, $column;
-		push @vals, $class->_deflated_column($column, $val);
+		$search_for{$column} = $class->_deflated_column($column, $val);
 	}
 
-	my $frag = join " AND ",
-		map defined($vals[$_]) ? "$cols[$_] $search_type ?" : "$cols[$_] IS NULL",
-		0 .. $#cols;
+	# if we're searching using "=" and we have all the PK fields
+	# then we can bypass the db search if the object is already cached
+	# and all the searched-for field values match. If any don't match,
+	# or don't exist in the cached object, then we fall through and db search.
+	if ($search_type eq "=" and my $obj_key = $class->_live_object_key(\%search_for)) {
+		my $obj = $Live_Objects{$obj_key};
+		# do we have a cached object that also matches the search criteria?
+		# for now we use the cached object only if the PK was the only search criteria
+		# (wouldn't be hard to add more through check of %search_for values)
+		my @primary_columns = $class->primary_columns;
+		return $obj if defined $obj and keys %search_for == @primary_columns;
+	}
+
+	my (@qual, @bind);
+	for my $column (sort keys %search_for) { # canonical order (for prepare_cached)
+	    if (defined(my $value = $search_for{$column})) {
+		push @qual, "$column $search_type ?";
+		push @bind, $value;
+	    }
+	    else {
+		# perhaps _carp if $search_type ne "="
+		push @qual, "$column IS NULL";
+	    }
+	}
+	my $frag = join " AND ", @qual;
 	$frag .= " ORDER BY $search_opts->{order_by}"
 		if $search_opts->{order_by};
-	return $class->sth_to_objects($class->sql_Retrieve($frag),
-		[ grep defined, @vals ]);
+	return $class->sth_to_objects($class->sql_Retrieve($frag), \@bind);
 
 }
 
Only in Class-DBI-0.96-live_object_key: pm_to_blib
Only in Class-DBI-0.96: x

--jq0ap7NbKX2Kqbes--

Re: retrieve() hits db even if obj present in %Live_Objects.
Tim Bunce 10:16 on 25 Jun 2004

Re: retrieve() hits db even if obj present in %Live_Objects.
Takes Tea at Half Past Three 21:40 on 24 Jun 2004

Generated at 11:34 on 01 Dec 2004 by mariachi v0.52