prepared statements can be disabled

This commit is contained in:
Aaron Patterson
2012-02-21 15:08:54 -08:00
parent a566ee5308
commit 83e42d52e3
8 changed files with 56 additions and 30 deletions

View File

@@ -2,9 +2,11 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
# Converts an arel AST to SQL
def to_sql(arel)
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
visitor.accept(arel.ast)
visitor.accept(arel.ast) do
quote(*binds.shift.reverse)
end
else
arel
end
@@ -13,7 +15,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(arel, name = nil, binds = [])
select(to_sql(arel), name, binds)
select(to_sql(arel, binds), name, binds)
end
# Returns a record hash with the column names as keys and column values
@@ -33,7 +35,7 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
result = select_rows(to_sql(arel), name)
result = select_rows(to_sql(arel, []), name)
result.map { |v| v[0] }
end
@@ -84,19 +86,19 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
value = exec_insert(sql, name, binds)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel), name, binds)
exec_update(to_sql(arel, binds), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel), name, binds)
exec_delete(to_sql(arel, binds), name, binds)
end
# Checks whether there is currently no transaction active. This is done

View File

@@ -57,7 +57,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
sql = to_sql(arel)
sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
super

View File

@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
@@ -122,12 +123,21 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
include Arel::Visitors::BindVisitor
end
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = Arel::Visitors::MySQL.new self
if config.fetch(:prepared_statements) { true }
@visitor = Arel::Visitors::MySQL.new self
else
@visitor = BindSubstitution.new self
end
end
def adapter_name #:nodoc:

View File

@@ -32,6 +32,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
@visitor = BindSubstitution.new self
configure_connection
end
@@ -65,10 +66,6 @@ module ActiveRecord
@connection.escape(string)
end
def substitute_at(column, index)
Arel::Nodes::BindParam.new "\0"
end
# CONNECTION MANAGEMENT ====================================
def active?
@@ -98,7 +95,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel)}"
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
start = Time.now
result = exec_query(sql, 'EXPLAIN', binds)
elapsed = Time.now - start
@@ -224,8 +221,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
binds = binds.dup
exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
exec_query(sql, name).to_a
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -235,17 +231,11 @@ module ActiveRecord
alias :create :insert_sql
def exec_insert(sql, name, binds)
binds = binds.dup
# Pretend to support bind parameters
execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
execute to_sql(sql, binds), name
end
def exec_delete(sql, name, binds)
binds = binds.dup
# Pretend to support bind parameters
execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
execute to_sql(sql, binds), name
@connection.affected_rows
end
alias :exec_update :exec_delete

View File

@@ -1,6 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/object/blank'
require 'active_record/connection_adapters/statement_pool'
require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
gem 'pg', '~> 0.11'
@@ -303,11 +304,23 @@ module ActiveRecord
end
end
class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
include Arel::Visitors::BindVisitor
end
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
if config.fetch(:prepared_statements) { true }
@visitor = Arel::Visitors::PostgreSQL.new self
else
@visitor = BindSubstitution.new self
end
connection_parameters.delete :prepared_statements
@connection_parameters, @config = connection_parameters, config
@visitor = Arel::Visitors::PostgreSQL.new self
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@@ -520,7 +533,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel)}"
sql = "EXPLAIN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end

View File

@@ -1,6 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/string/encoding'
require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters #:nodoc:
@@ -69,12 +70,21 @@ module ActiveRecord
end
end
class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
include Arel::Visitors::BindVisitor
end
def initialize(connection, logger, config)
super(connection, logger)
@statements = StatementPool.new(@connection,
config.fetch(:statement_limit) { 1000 })
@config = config
@visitor = Arel::Visitors::SQLite.new self
if config.fetch(:prepared_statements) { true }
@visitor = Arel::Visitors::SQLite.new self
else
@visitor = BindSubstitution.new self
end
end
def adapter_name #:nodoc:
@@ -210,7 +220,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end

View File

@@ -77,6 +77,7 @@ module ActiveRecord
end
def initialize_copy(other)
@bind_values = @bind_values.dup
reset
end
@@ -453,7 +454,7 @@ module ActiveRecord
end
def to_sql
@to_sql ||= klass.connection.to_sql(arel)
@to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
end
def where_values_hash

View File

@@ -208,7 +208,7 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
rows = connection.select_all(relation, 'SQL', relation.bind_values)
rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
join_dependency.instantiate(rows)
rescue ThrowResult
[]