[ruby-dbi-users] dbd_pg and deallocating prepared statements

KUBO Takehiro kubo at jiubao.org
Tue Sep 2 01:39:09 EDT 2008


Hi,

On Tue, Sep 2, 2008 at 6:11 AM, Erik Hollensbe <erik at hollensbe.org> wrote:
> On Tuesday 26 August 2008 18:56:40 KUBO Takehiro wrote:
>> Pardon me if I'm not correct. I have been interested in how various
>> DBMSs implement prepared statements. But I have not used postgresql
>> for 9 years.
>>
>> dbd_pg uses prepared statements for all queries. But who deallocate the
>> prepared statements? "@stmt.clear if @stmt" doesn't do it.
>> IMO, if a ruby process uses one database session for all SQL statements
>> and issues them periodically, server side (postmaster) memory will be
>> increased as time goes.
>>
>> See: http://www.postgresql.org/docs/8.3/static/libpq-exec.html#AEN30965
>>      http://www.postgresql.org/docs/8.3/static/sql-deallocate.html
>
> This bug (and a type handling bug in mysql) have been fixed in the recent git
> push. If anyone has some spare time and would just like to run the test suite
> which should reveal any issues in either of these changes, it'll ease my mind
> to release it today.
>
> Thanks again for the help in solving this problem.

The following patch will release prepared statements in GC even though they are
not finished explicitly.
   sth = dbh.prepare('select * from names')
   sth.execute
     ...
   sth = nil # forget to finish the statement handle.
   GC.start # <= sth's prepared statement is deallocated by GC.

Sorry, I have not test it. I have no working postgresql database...

diff --git a/lib/dbd/pg/statement.rb b/lib/dbd/pg/statement.rb
index 0271a10..7273e77 100644
--- a/lib/dbd/pg/statement.rb
+++ b/lib/dbd/pg/statement.rb
@@ -10,6 +10,16 @@ class DBI::DBD::Pg::Statement < DBI::BaseStatement

     PG_STMT_NAME_PREFIX = 'ruby-dbi:Pg:'

+    @@id_map = {} # prepared statement's id => db
+
+    @@finalizer_proc = proc do |id|
+        db = @@id_map[id]
+        if db
+            @@id_map.delete(id)
+            db._exec("DEALLOCATE \"#{PG_STMT_NAME_PREFIX + id.to_s}\"")
+        end
+    end
+
     def initialize(db, sql)
         super(db)
         @db  = db
@@ -17,7 +27,7 @@ class DBI::DBD::Pg::Statement < DBI::BaseStatement
         @stmt_name = PG_STMT_NAME_PREFIX + self.object_id.to_s
         @result = nil
         @bindvars = []
-        @prepared = false
+        ObjectSpace.define_finalizer(self, @@finalizer_proc)
     rescue PGError => err
         raise DBI::ProgrammingError.new(err.message)
     end
@@ -125,8 +135,8 @@ class DBI::DBD::Pg::Statement < DBI::BaseStatement
     # finish the statement at a lower level
     def internal_finish
         @result.finish if @result
-        statement_exists = @db._exec("select * from
pg_prepared_statements where name='#{@stmt_name}'")
-        if statement_exists.num_tuples > 0
+        if @@id_map[self.object_id]
+            @@id_map.delete(self.object_id)
             @db._exec("DEALLOCATE \"#{@stmt_name}\"")
         end
     end
@@ -134,14 +144,14 @@ class DBI::DBD::Pg::Statement < DBI::BaseStatement
     # prepare the statement at a lower level.
     def internal_prepare
         if @db["pg_native_binding"]
-            unless @prepared
+            unless @@id_map[self.object_id]
                 @stmt = @db._prepare(@stmt_name, translate_param_markers(@sql))
             end
         else
             internal_finish
             @stmt = @db._prepare(@stmt_name,
DBI::SQL::PreparedStatement.new(DBI::DBD::Pg, @sql).bind(@bindvars))
         end
-        @prepared = true
+        @@id_map[self.object_id] = @db
     end

     # Prepare the given SQL statement, returning its PostgreSQL string


More information about the ruby-dbi-users mailing list