reset prepared statement when schema changes imapact statement results. fixes #3335

This commit is contained in:
Aaron Patterson
2011-10-18 11:12:18 -07:00
parent d44702c830
commit 6a28c512e3
2 changed files with 51 additions and 11 deletions

View File

@@ -278,6 +278,11 @@ module ActiveRecord
cache.clear
end
def delete(sql_key)
dealloc cache[sql_key]
cache.delete sql_key
end
private
def cache
@cache[$$]
@@ -1030,27 +1035,54 @@ module ActiveRecord
end
private
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
def exec_no_cache(sql, binds)
@connection.async_exec(sql)
end
def exec_cache(sql, binds)
sql_key = "#{schema_search_path}-#{sql}"
begin
stmt_key = prepare_statement sql
# Clear the queue
@connection.get_last_result
@connection.send_query_prepared(stmt_key, binds.map { |col, val|
type_cast(val, col)
})
@connection.block
@connection.get_last_result
rescue PGError => e
# Get the PG code for the failure. Annoyingly, the code for
# prepared statements whose return value may have changed is
# FEATURE_NOT_SUPPORTED. Check here for more details:
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
if FEATURE_NOT_SUPPORTED == code
@statements.delete sql_key(sql)
retry
else
raise e
end
end
end
# Returns the statement identifier for the client side cache
# of statements
def sql_key(sql)
"#{schema_search_path}-#{sql}"
end
# Prepare the statement if it hasn't been prepared, return
# the statement key.
def prepare_statement(sql)
sql_key = sql_key(sql)
unless @statements.key? sql_key
nextkey = @statements.next_key
@connection.prepare nextkey, sql
@statements[sql_key] = nextkey
end
key = @statements[sql_key]
# Clear the queue
@connection.get_last_result
@connection.send_query_prepared(key, binds.map { |col, val|
type_cast(val, col)
})
@connection.block
@connection.get_last_result
@statements[sql_key]
end
# The internal PostgreSQL identifier of the money data type.

View File

@@ -62,6 +62,14 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
def test_schema_change_with_prepared_stmt
@connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
@connection.exec_query "alter table developers add column zomg int", 'sql', []
@connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
ensure
@connection.exec_query "alter table developers drop column if exists zomg", 'sql', []
end
def test_table_exists?
[Thing1, Thing2, Thing3, Thing4].each do |klass|
name = klass.table_name