Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

reset prepared statement when schema changes imapact statement result…

…s. fixes #3335
  • Loading branch information...
commit 818d285305502cc6191a98400b43633f44394f6e 1 parent dd27e2e
Aaron Patterson authored October 18, 2011
67  activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -279,6 +279,11 @@ def clear
279 279
           cache.clear
280 280
         end
281 281
 
  282
+        def delete(sql_key)
  283
+          dealloc cache[sql_key]
  284
+          cache.delete sql_key
  285
+        end
  286
+
282 287
         private
283 288
         def cache
284 289
           @cache[$$]
@@ -999,27 +1004,55 @@ def translate_exception(exception, message)
999 1004
         end
1000 1005
 
1001 1006
       private
1002  
-      def exec_no_cache(sql, binds)
1003  
-        @connection.async_exec(sql)
1004  
-      end
  1007
+        FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1005 1008
 
1006  
-      def exec_cache(sql, binds)
1007  
-        unless @statements.key? sql
1008  
-          nextkey = @statements.next_key
1009  
-          @connection.prepare nextkey, sql
1010  
-          @statements[sql] = nextkey
  1009
+        def exec_no_cache(sql, binds)
  1010
+          @connection.async_exec(sql)
1011 1011
         end
1012 1012
 
1013  
-        key = @statements[sql]
  1013
+        def exec_cache(sql, binds)
  1014
+          begin
  1015
+            stmt_key = prepare_statement sql
  1016
+
  1017
+            # Clear the queue
  1018
+            @connection.get_last_result
  1019
+            @connection.send_query_prepared(stmt_key, binds.map { |col, val|
  1020
+              type_cast(val, col)
  1021
+            })
  1022
+            @connection.block
  1023
+            @connection.get_last_result
  1024
+          rescue PGError => e
  1025
+            # Get the PG code for the failure.  Annoyingly, the code for
  1026
+            # prepared statements whose return value may have changed is
  1027
+            # FEATURE_NOT_SUPPORTED.  Check here for more details:
  1028
+            # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
  1029
+            code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
  1030
+            if FEATURE_NOT_SUPPORTED == code
  1031
+              @statements.delete sql_key(sql)
  1032
+              retry
  1033
+            else
  1034
+              raise e
  1035
+            end
  1036
+          end
  1037
+        end
1014 1038
 
1015  
-        # Clear the queue
1016  
-        @connection.get_last_result
1017  
-        @connection.send_query_prepared(key, binds.map { |col, val|
1018  
-          type_cast(val, col)
1019  
-        })
1020  
-        @connection.block
1021  
-        @connection.get_last_result
1022  
-      end
  1039
+        # Returns the statement identifier for the client side cache
  1040
+        # of statements
  1041
+        def sql_key(sql)
  1042
+          "#{schema_search_path}-#{sql}"
  1043
+        end
  1044
+
  1045
+        # Prepare the statement if it hasn't been prepared, return
  1046
+        # the statement key.
  1047
+        def prepare_statement(sql)
  1048
+          sql_key = sql_key(sql)
  1049
+          unless @statements.key? sql_key
  1050
+            nextkey = @statements.next_key
  1051
+            @connection.prepare nextkey, sql
  1052
+            @statements[sql_key] = nextkey
  1053
+          end
  1054
+          @statements[sql_key]
  1055
+        end
1023 1056
 
1024 1057
         # The internal PostgreSQL identifier of the money data type.
1025 1058
         MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
8  activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -56,6 +56,14 @@ def teardown
56 56
     @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
57 57
   end
58 58
 
  59
+  def test_schema_change_with_prepared_stmt
  60
+    @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
  61
+    @connection.exec_query "alter table developers add column zomg int", 'sql', []
  62
+    @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
  63
+  ensure
  64
+    @connection.exec_query "alter table developers drop column if exists zomg", 'sql', []
  65
+  end
  66
+
59 67
   def test_table_exists?
60 68
     [Thing1, Thing2, Thing3, Thing4].each do |klass|
61 69
       name = klass.table_name

0 notes on commit 818d285

Please sign in to comment.
Something went wrong with that request. Please try again.