mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
reset prepared statement when schema changes imapact statement results. fixes #3335
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user