Stephen
2006-Jul-24 16:15 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
Skipped content of type multipart/alternative-------------- next part
--------------
Index: connection_adapters/abstract/connection_specification.rb
==================================================================---
connection_adapters/abstract/connection_specification.rb (revision 4617)
+++ connection_adapters/abstract/connection_specification.rb (working copy)
@@ -1,10 +1,21 @@
require ''set''
+require ''pp''
module ActiveRecord
class Base
class ConnectionSpecification #:nodoc:
attr_reader :config, :adapter_method
- def initialize (config, adapter_method)
+ def initialize (config, adapter_method = nil)
+ # If no adapter is found, try to devine it from the config.
+ # This was moved here from establish_connection since it''s now
+ # useful in other places.
+ if adapter_method.nil?
+ config = config.symbolize_keys
+ unless config.key?(:adapter) then raise AdapterNotSpecified,
"database configuration does not specify adapter" end
+ adapter_method = "#{config[:adapter]}_connection"
+ unless ActiveRecord::Base.respond_to?(adapter_method) then
+ raise AdapterNotFound, "database configuration specifies
nonexistent #{config[:adapter]} adapter" end
+ end
@config, @adapter_method = config, adapter_method
end
end
@@ -20,6 +31,16 @@
# The class -> thread id -> adapter cache. (class -> adapter if
not allow_concurrency)
@@active_connections = {}
+ # Define a connection pool for doing database reads.
+ @@read_connection_pool = {}
+
+ # Define a connection pool for doing database reads.
+ @@write_connection_pool = {}
+
+ # Used in looping the connection pools.
+ @@last_read_connection = {}
+ @@last_write_connection = {}
+
class << self
# Retrieve the connection cache.
def thread_safe_active_connections #:nodoc:
@@ -29,16 +50,32 @@
def single_threaded_active_connections #:nodoc:
@@active_connections
end
-
+ def thread_safe_read_pool
+ @@read_connection_pool[Thread.current.object_id] ||= {}
+ end
+ def thread_safe_write_pool
+ @@write_connection_pool[Thread.current.object_id] ||= {}
+ end
+ def single_threaded_read_pool
+ @@read_connection_pool
+ end
+ def single_threaded_write_pool
+ @@write_connection_pool
+ end
+
# pick up the right active_connection method from @@allow_concurrency
if @@allow_concurrency
- alias_method :active_connections, :thread_safe_active_connections
+ alias_method :active_connections, :thread_safe_active_connections
+ alias_method :read_connection_pool, :thread_safe_read_pool
+ alias_method :write_connection_pool, :thread_safe_write_pool
else
- alias_method :active_connections, :single_threaded_active_connections
+ alias_method :active_connections,
:single_threaded_active_connections
+ alias_method :read_connection_pool, :single_threaded_read_pool
+ alias_method :write_connection_pool, :single_threaded_write_pool
end
# set concurrency support flag (not thread safe, like most of the methods
in this file)
- def allow_concurrency=(threaded) #:nodoc:
+ def allow_concurrency=(threaded) #:nodoc
logger.debug "allow_concurrency=#{threaded}" if logger
return if @@allow_concurrency == threaded
clear_all_cached_connections!
@@ -52,8 +89,9 @@
end
def active_connection_name #:nodoc:
+ # This must use name, not active_connection_name
@active_connection_name ||- if active_connections[name] ||
@@defined_connections[name]
+ if write_connection_pool[name] || read_connection_pool[name]
name
elsif self == ActiveRecord::Base
nil
@@ -70,13 +108,24 @@
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work
unrelated
# to any of the specific Active Records.
- def connection
- if @active_connection_name && (conn =
active_connections[@active_connection_name])
+ def connection(type = nil)
+ case type
+ when nil, :write
+ # Provides write connection on nil for backwards compatibility.
+ id = round_robin_write_connection.object_id
+ when :read
+ id = round_robin_read_connection.object_id
+ else
+ # If someone passes garbage, raise error.
+ # they could have misspelt "read" and that could bork their
database.
+ raise ActiveRecordError, "Connection type can only be read or
write."
+ end
+ if conn = active_connections[id]
conn
else
# retrieve_connection sets the cache key.
- conn = retrieve_connection
- active_connections[@active_connection_name] = conn
+ conn = retrieve_connection(id)
+ active_connections[id] = conn
end
end
@@ -100,6 +149,28 @@
end
end
+ protected
+ def round_robin_read_connection
+ # pp "ActiveConnectionName in round_robin_read is
#{active_connection_name}"
+ # It''s very important to use active_connection_name instead of
@active_connection_name
+ if read_connection_pool[active_connection_name].nil? ||
read_connection_pool[active_connection_name].size == 0
+ raise ConnectionNotEstablished, "No Connection is
available"
+ else
+ @@last_read_connection[active_connection_name] =
(@@last_read_connection[active_connection_name] + 1) %
read_connection_pool[active_connection_name].size
+
read_connection_pool[active_connection_name].to_a[@@last_read_connection[active_connection_name]][1]
+ end
+ end
+
+ def round_robin_write_connection
+ # See comments for round_robin_read_connection.
+ if write_connection_pool[active_connection_name].nil? ||
write_connection_pool[active_connection_name].size == 0
+ raise ConnectionNotEstablished, "No Connection is
available"
+ else
+ @@last_write_connection[active_connection_name] =
(@@last_write_connection[active_connection_name] + 1) %
write_connection_pool[active_connection_name].size
+
write_connection_pool[active_connection_name].to_a[@@last_write_connection[active_connection_name]][1]
+ end
+ end
+
private
def clear_cache!(cache, thread_id = nil, &block)
if cache
@@ -108,7 +179,6 @@
thread_cache, cache = cache, cache[thread_id]
return unless cache
end
-
cache.each(&block) if block_given?
cache.clear
end
@@ -121,11 +191,9 @@
# Remove stale threads from the cache.
def remove_stale_cached_threads!(cache, &block)
stale = Set.new(cache.keys)
-
Thread.list.each do |thread|
stale.delete(thread.object_id) if thread.alive?
end
-
stale.each do |thread_id|
clear_cache!(cache, thread_id, &block)
end
@@ -147,10 +215,70 @@
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work
that isn''t
# easily done without going straight to SQL.
- def connection
- self.class.connection
+ def connection(type = nil)
+ self.class.connection(type)
end
+
+ # Add specs to various connection pools.
+ def self.append_spec_to_connection_pools spec
+ # The default behavior allows a connection to be used for both reading
and writing.
+ # Otherwise, we just load both pools.
+
+ write_connection_pool[active_connection_name] ||= {}
+ read_connection_pool[active_connection_name] ||= {}
+ @@last_read_connection[active_connection_name] = 0
+ @@last_write_connection[active_connection_name] = 0
+ # Check out what were were passed if its not a ConnectionSpec, try to make
one.
+ if !spec.kind_of?(ActiveRecord::Base::ConnectionSpecification)
+ spec = ConnectionSpecification.new(spec)
+ end
+
+ if !spec.config.key?(:read_only) && !spec.config[:read_only] !=
''true''
+ write_connection_pool[active_connection_name][spec.object_id] = spec if
!write_connection_pool[active_connection_name][spec.object_id]
+ end
+ if !spec.config.key?(:write_only) && !spec.config[:write_only] !=
''true''
+ read_connection_pool[active_connection_name][spec.object_id] = spec if
!read_connection_pool[active_connection_name][spec.object_id]
+ end
+ @@defined_connections[spec.object_id] = spec
+ end
+
+ def self.clear_connection_pools(klass=self)
+ if write_connection_pool[klass.name].kind_of?(Hash)
+ write_connection_pool[klass.name].each_value {|conn|
+ active_connections[conn.object_id].disconnect! if
active_connections[conn.object_id] }
+ write_connection_pool[klass.name].clear
+ end
+ if read_connection_pool[klass.name].kind_of?(Hash)
+ read_connection_pool[klass.name].each_value {|conn|
+ active_connections[conn.object_id].disconnect! if
active_connections[conn.object_id] }
+ read_connection_pool[klass.name].clear
+ end
+ read_connection_pool.delete(klass.name)
+ write_connection_pool.delete(klass.name)
+ clear_all_cached_connections!
+ end
+
+ def self.establish_connection_pool(environment)
+ # Connections that are specifically in a pool override the read/write
settings.
+ @active_connection_name = name
+ if @read = configurations["#{environment}_read_pool"]
+ @read.split(",").each do |connection|
+ spec = configurations[connection].clone
+ spec[:read_only] = true
+ append_spec_to_connection_pools(spec)
+ end
+ end
+ if @write = configurations["#{environment}_write_pool"]
+ @write.split(",").each do |connection|
+ spec = configurations[connection].clone
+ spec[:write_only] = true
+ append_spec_to_connection_pools(spec)
+ end
+ end
+ (write_connection_pool[name].size > 0 &&
read_connection_pool[name].size > 0) ? true : false
+ end
+
# Establishes the connection to the database. Accepts a hash as input where
# the :adapter key must be specified with the name of a database adapter
(in lower-case)
# example for regular databases (MySQL, Postgresql, etc):
@@ -182,11 +310,13 @@
case spec
when nil
raise AdapterNotSpecified unless defined? RAILS_ENV
- establish_connection(RAILS_ENV)
+ if !establish_connection_pool(RAILS_ENV)
+ establish_connection(RAILS_ENV)
+ end
when ConnectionSpecification
- clear_active_connection_name
+ clear_active_connection_name
@active_connection_name = name
- @@defined_connections[name] = spec
+ append_spec_to_connection_pools(spec)
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
@@ -194,12 +324,7 @@
raise AdapterNotSpecified, "#{spec} database is not
configured"
end
else
- spec = spec.symbolize_keys
- unless spec.key?(:adapter) then raise AdapterNotSpecified,
"database configuration does not specify adapter" end
- adapter_method = "#{spec[:adapter]}_connection"
- unless respond_to?(adapter_method) then raise AdapterNotFound,
"database configuration specifies nonexistent #{spec[:adapter]}
adapter" end
- remove_connection
- establish_connection(ConnectionSpecification.new(spec,
adapter_method))
+ establish_connection(ConnectionSpecification.new(spec))
end
end
@@ -207,27 +332,29 @@
# active or defined connections: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
# for (not necessarily the current class).
- def self.retrieve_connection #:nodoc:
+ def self.retrieve_connection(id = nil) #:nodoc:
# Name is nil if establish_connection hasn''t been called for
# some class along the inheritance chain up to AR::Base yet.
- if name = active_connection_name
- if conn = active_connections[name]
- # Verify the connection.
- conn.verify!(@@verification_timeout)
- elsif spec = @@defined_connections[name]
- # Activate this connection specification.
- klass = name.constantize
- klass.connection = spec
- conn = active_connections[name]
- end
+ if id.nil?
+ id = round_robin_write_connection.object_id
end
-
+ if conn = active_connections[id]
+ # Verify the connection.
+ conn.verify!(@@verification_timeout)
+ elsif spec = @@defined_connections[id]
+ # Activate this connection specification.
+ # Note this no longer calls connection= which causes problems
+ # with read/write pools being clobbered.
+ config = spec.config.reverse_merge(:allow_concurrency =>
@@allow_concurrency)
+ active_connections[id] = self.send(spec.adapter_method, config)
+ conn = active_connections[id]
+ end
conn or raise ConnectionNotEstablished
end
# Returns true if a connection that''s accessible to this class
have already been opened.
def self.connected?
- active_connections[active_connection_name] ? true : false
+
active_connections[active_connection_name][round_robin_write_connection.object_id]
? true : false
end
# Remove the connection for this class. This will close the active
@@ -235,21 +362,20 @@
# can be used as argument for establish_connection, for easy
# re-establishing of the connection.
def self.remove_connection(klass=self)
- spec = @@defined_connections[klass.name]
- konn = active_connections[klass.name]
- @@defined_connections.delete_if { |key, value| value == spec }
- active_connections.delete_if { |key, value| value == konn }
- konn.disconnect! if konn
- spec.config if spec
+ # Arbitarily choosing the first spec off the write pool to send back.
+ # This is here to stay backwards compatible
+ spec = @@defined_connections[round_robin_write_connection.object_id] if
!@@defined_connections[round_robin_write_connection.object_id].nil?
+ clear_connection_pools(klass)
+ spec if spec
end
# Set the connection for the class.
def self.connection=(spec) #:nodoc:
if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
- active_connections[name] = spec
+ active_connections[active_connection_name] = spec
elsif spec.kind_of?(ConnectionSpecification)
config = spec.config.reverse_merge(:allow_concurrency =>
@@allow_concurrency)
- self.connection = self.send(spec.adapter_method, config)
+ establish_connection(spec.adapter_method, config)
elsif spec.nil?
raise ConnectionNotEstablished
else
@@ -265,5 +391,5 @@
logger.info "Active connection name:
#{@active_connection_name}"
end
end
- end
+ end_pool
end
Index: base.rb
==================================================================--- base.rb
(revision 4617)
+++ base.rb (working copy)
@@ -419,7 +419,7 @@
# Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c
WHERE p.id = c.post_id"
# Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND
created > ?", author_id, start_date]
def find_by_sql(sql)
- connection.select_all(sanitize_sql(sql), "#{name}
Load").collect! { |record| instantiate(record) }
+ connection(:read).select_all(sanitize_sql(sql), "#{name}
Load").collect! { |record| instantiate(record) }
end
# Returns true if the given +id+ represents the primary key of a record
in the database, false otherwise.
@@ -506,7 +506,7 @@
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c
WHERE s.customer_id = c.id"
def count_by_sql(sql)
sql = sanitize_conditions(sql)
- connection.select_value(sql, "#{name} Count").to_i
+ connection(:read).select_value(sql, "#{name} Count").to_i
end
# Increments the specified counter by one. So
<tt>DiscussionBoard.increment_counter("post_count",
@@ -725,7 +725,7 @@
# Returns an array of column objects for the table associated with this
class.
def columns
unless @columns
- @columns = connection.columns(table_name, "#{name}
Columns")
+ @columns = connection(:read).columns(table_name, "#{name}
Columns")
@columns.each {|column| column.primary = column.name == primary_key}
end
@columns
Tom Mornini
2006-Jul-24 17:41 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
On Jul 24, 2006, at 9:13 AM, Stephen wrote:> Defining a database pool is very easy and follows a common sense > convention. I will assume you are running with > RAILS_ENV=development for this example. You first define your > connections as you always have in database.yml . Your are then > able to able to define > development_read_pool: db1, db2, db3 > development_write_pool: db4, db5, db6 (In general, the name is > RAILS_ENV_write_pool so you can test your clusters in development > and production with no config changes)This is very cool, Stephen. Couldn''t this be read_pool, and write_pool, within the current block of YAML? -- -- Tom Mornini
Stephen
2006-Jul-24 17:48 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
Here is an example. I considered the merits of allow an attribute like pools: p1,p2,p3 but I think this makes more sense from a configuration standpoint. It would look something like this; development: adapter: mysql database: project_development username: someuser password: happyland host: 10.0.0.1 port: 3306 development2: adapter: mysql database: project_development username: root password: somethingelse host: 10.0.0.2 port: 3306 development_write_pool: development development_read_pool: development,development2 On 7/24/06, Tom Mornini <tmornini@infomania.com> wrote:> > On Jul 24, 2006, at 9:13 AM, Stephen wrote: > > > Defining a database pool is very easy and follows a common sense > > convention. I will assume you are running with > > RAILS_ENV=development for this example. You first define your > > connections as you always have in database.yml . Your are then > > able to able to define > > development_read_pool: db1, db2, db3 > > development_write_pool: db4, db5, db6 (In general, the name is > > RAILS_ENV_write_pool so you can test your clusters in development > > and production with no config changes) > > This is very cool, Stephen. > > Couldn''t this be read_pool, and write_pool, within the current block > of YAML? > > -- > -- Tom Mornini > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060724/0114edff/attachment.html
Hi,
I''ve tried creating a new ticket for this issue, but I''m just
getting
500 server errors.
There appears to be a problem with the ActiveRecord SqlServer adapter
that results in illegal SQL being generated...
ActiveRecord::StatementInvalid in ArticlesController#index
DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server Driver][SQL
Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM
(SELECT TOP 1
*
FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND
contents.published_at < ''2006-07-25 11:59:31'' AND blog_id
= 1) AND (
(contents.[type] = ''Article'' ) ) ORDER BY
contents.published_at DESC )
AS
tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY
contents\.\[published_at\] DESC
This is due to messed up column quoting. I''ve tracked it down to the
''add_limit_offset!'' method in sqlserver_adapter.rb, but I
can''t decipher
enough of what is going on to suggest the fix just yet.
Full stack trace also included below.
Regards,
Stu
RAILS_ROOT: ./script/../config/..
Application Trace | Framework Trace | Full Trace
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada
pters/abstract_adapter.rb:120:in
`log''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada
pters/sqlserver_adapter.rb:485:in
`select''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada
pters/sqlserver_adapter.rb:233:in
`select_all''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:1166:in
`select_limited_ids_list''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:1158:in
`add_limited_ids_condition!''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:1148:in
`construct_finder_sql_with_included_associations''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:1111:in
`select_all_rows''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:975:in
`find_with_associations''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r
b:973:in
`find_with_associations''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in
`find_every''
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in
`find''
#{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910:
in
`perform_action_without_filters''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3
68:in
`perform_action_without_benchmark''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking
.rb:69:in
`perform_action_without_rescue''
c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking
.rb:69:in
`perform_action_without_rescue''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82
:in
`perform_action''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381:
in
`process_without_filters''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3
77:in
`process_without_session_management_support''
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana
gement.rb:117:in
`process''
#{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in `dispatch''
#{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in
`handle_dispatch''
#{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in
`service''
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service''
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run''
c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread''
c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread''
c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start''
c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start''
c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start''
c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start''
#{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in
`dispatch''
#{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
`require''
#{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies
.rb:147:in
`require''
#{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
`require''
script/server:3
Request
Parameters: None
Show session dump
---
Response
Headers: {"cookie"=>[],
"Cache-Control"=>"no-cache"}
--
Ticket URL: <http://www.typosphere.org/trac/ticket/1040>
typo <http://typosphere.org/trac/>
web2.0 weblogging engine
<body>
<blockquote>
<font FACE="Arial,Arial" SIZE="1"><p
ALIGN="JUSTIFY">This Email may contain confidential and privileged
information and is intended for the use of the addressee(s) only. If you are
not the intended recipient please notify the sender and delete the Email from
your system. It should not be transmitted to any other person without the
consent of the sender. Additional important notifications regarding Email
transmissions from and to members of Baring Asset Management can be accessed at
<a
href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body>
</p>
</font>
</blockquote>
<p> </p>
</body>
</html>
Interesting. I''ll take a look this evening. Please let me know if you find out any more. Tom On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> Hi, > > > I''ve tried creating a new ticket for this issue, but I''m just getting > 500 server errors. > > There appears to be a problem with the ActiveRecord SqlServer adapter > that results in illegal SQL being generated... > > > ActiveRecord::StatementInvalid in ArticlesController#index > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server Driver][SQL > Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP 1 > * > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC ) > AS > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > contents\.\[published_at\] DESC > > This is due to messed up column quoting. I''ve tracked it down to the > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''t decipher > enough of what is going on to suggest the fix just yet. > > Full stack trace also included below. > > Regards, > Stu > > RAILS_ROOT: ./script/../config/.. > Application Trace | Framework Trace | Full Trace > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/abstract_adapter.rb:120:in > `log'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/sqlserver_adapter.rb:485:in > `select'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/sqlserver_adapter.rb:233:in > `select_all'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1166:in > `select_limited_ids_list'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1158:in > `add_limited_ids_condition!'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1148:in > `construct_finder_sql_with_included_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1111:in > `select_all_rows'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:975:in > `find_with_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:973:in > `find_with_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in > `find_every'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in > `find'' > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910: > in > `perform_action_without_filters'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > 68:in > `perform_action_without_benchmark'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > .rb:69:in > `perform_action_without_rescue'' > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > .rb:69:in > `perform_action_without_rescue'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82 > :in > `perform_action'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381: > in > `process_without_filters'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > 77:in > `process_without_session_management_support'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana > gement.rb:117:in > `process'' > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in `dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > `handle_dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > `dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > > #{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies > .rb:147:in > `require'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > script/server:3 > Request > > Parameters: None > > Show session dump > --- > Response > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > -- > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > typo <http://typosphere.org/trac/> > web2.0 weblogging engine > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> > </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
I think I''ve managed to fix the column quoting issue by making three
small changes to the add_limit_offset! method in the
sqlserver_adapter.rb file:
def add_limit_offset!(sql, options)
if options[:limit] and options[:offset]
total_rows = @connection.select_all("SELECT count(*) as
TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP
1000000000")}) tally")[0][:TotalRows].to_i
if (options[:limit] + options[:offset]) >= total_rows
options[:limit] = (total_rows - options[:offset] >= 0) ?
(total_rows - options[:offset]) : 0
end
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT
TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] +
options[:offset]} ")
sql << ") AS tmp1"
if options[:order]
options[:order] = options[:order].split('','').map
do |field|
parts = field.split(" ")
tc = parts[0]
if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used
in query
####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['')
####-- ORIGINAL : tc << ''\\]''
tc.gsub!(/\./, ''.['') #NEW: -- do not escape
column quotes
tc << '']'' #NEW: -- do not
escape column quotes
end
if sql =~ /#{tc} AS (t\d_r\d\d?)/
parts[0] = $1
####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/
elsif parts[0] =~ /\w+\.([\[\]\w]+)/
#NEW: ensure that column quotes are picked up when matching to strip the
tablename
parts[0] = $1
end
parts.join('' '')
end.join('', '')
sql << " ORDER BY
#{change_order_direction(options[:order])}) AS tmp2 ORDER BY
#{options[:order]}"
else
sql << " ) AS tmp2"
end
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
"SELECT#{$1} TOP #{options[:limit]}"
end unless options[:limit].nil?
end
end
This has fixed the column quoting issue, but there are still some
problems with the SQL that ActiveRecord is generating. The original
issue of selecting by offset now works...
Article.find( :all,
:offset => 0,
:limit => 1,
:order => "contents.published_at DESC",
:conditions => [''published = ? AND contents.published_at <
? AND blog_id = ?'',true, Time.now, 1])
[#<Article:0x32acb34 @attributes={... (snip)
And selecting with the :include tag works...
Article.find( :all,
:order => "contents.published_at DESC",
:include => [:categories, :tags, :user, :blog],
:conditions => [''published = ? AND contents.published_at <
? AND blog_id = ?'',true, Time.now, 1])
[#<Article:0x3848110 @user=#<User:0x38476fc @attributes={...
(snip)
But a find that combines both the :offset and :include will fail due to
bad SQL...
Article.find( :all,
:offset => 0,
:limit => 1,
:order => "contents.published_at DESC",
:include => [:categories, :tags, :user, :blog],
:conditions => [''published = ? AND contents.published_at <
? AND
blog_id = ?'',true, Time.now, 1])
ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207)
[Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name
''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT
TOP 1 id
FROM contents WHERE (published = 1 AND contents.published_at <
''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type]
''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1
ORDER BY
[published_at] ASC) AS tmp2 ORDER BY [published_at] DESC
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/abstract_adapter.rb:120:in `log''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/sqlserver_adapter.rb:521:in `select''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1166:in `select_limited_ids_list''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1158:in `add_limited_ids_condition!''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1148:in
`construct_finder_sql_with_included_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1111:in `select_all_rows''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:975:in `find_with_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:973:in `find_with_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/base.rb:923:in `find_every''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/base.rb:381:in `find''
from (irb):104
from :0
This one is caused by just selecting the [id] in the inner select, and
not exposing the the [published_at] column to be sorted on in the outer
selects. Sadly I haven''t been able to get anywhere with debugging this
one. Also, please note that this is being run against the Typo 4
database, and the test are run via the script/console util. The issues
do also occur through the web frontend.
Regards,
Stu
-----Original Message-----
From: rails-bounces@lists.rubyonrails.org
[mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward
Sent: 25 July 2006 16:18
To: rails@lists.rubyonrails.org
Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug...
Interesting. I''ll take a look this evening. Please let me know if
you find out any more.
Tom
On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com>
wrote:> Hi,
>
>
> I''ve tried creating a new ticket for this issue, but I''m
just getting
> 500 server errors.
>
> There appears to be a problem with the ActiveRecord SqlServer adapter
> that results in illegal SQL being generated...
>
>
> ActiveRecord::StatementInvalid in ArticlesController#index
> DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server
Driver][SQL> Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM
(SELECT TOP
1> *
> FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND
> contents.published_at < ''2006-07-25 11:59:31'' AND
blog_id = 1) AND (
> (contents.[type] = ''Article'' ) ) ORDER BY
contents.published_at DESC
)> AS
> tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY
> contents\.\[published_at\] DESC
>
> This is due to messed up column quoting. I''ve tracked it down to
the
> ''add_limit_offset!'' method in sqlserver_adapter.rb, but I
can''t
decipher> enough of what is going on to suggest the fix just yet.
>
> Full stack trace also included below.
>
> Regards,
> Stu
>
> RAILS_ROOT: ./script/../config/..
> Application Trace | Framework Trace | Full Trace
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/abstract_adapter.rb:120:in
> `log''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:485:in
> `select''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:233:in
> `select_all''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1166:in
> `select_limited_ids_list''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1158:in
> `add_limited_ids_condition!''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1148:in
> `construct_finder_sql_with_included_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1111:in
> `select_all_rows''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:975:in
> `find_with_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:973:in
> `find_with_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in> `find_every''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in> `find''
> #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in
`index''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910:> in
> `perform_action_without_filters''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 68:in
> `perform_action_without_benchmark''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in
> `perform_action_without_rescue''
> c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in
> `perform_action_without_rescue''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82> :in
> `perform_action''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381:> in
> `process_without_filters''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 77:in
> `process_without_session_management_support''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana> gement.rb:117:in
> `process''
> #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in
`dispatch''> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in
> `handle_dispatch''
> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in
> `service''
> c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service''
> c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start''
> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in
> `dispatch''
>
#{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
> `require''
>
>
#{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies> .rb:147:in
> `require''
> #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30
> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
> `require''
> script/server:3
> Request
>
> Parameters: None
>
> Show session dump
> ---
> Response
> Headers: {"cookie"=>[],
"Cache-Control"=>"no-cache"}
>
> --
> Ticket URL: <http://www.typosphere.org/trac/ticket/1040>
> typo <http://typosphere.org/trac/>
> web2.0 weblogging engine
>
>
> <body>
>
> <blockquote>
> <font FACE="Arial,Arial" SIZE="1"><p
ALIGN="JUSTIFY">This Email may
contain confidential and privileged information and is intended for the
use of the addressee(s) only. If you are not the intended recipient
please notify the sender and delete the Email from your system. It
should not be transmitted to any other person without the consent of the
sender. Additional important notifications regarding Email transmissions
from and to members of Baring Asset Management can be accessed at <a
href="http://www.barings.com/email/index.hcst">http://www.barings.com/em
ail/index.hcst</a></font></p></body>> </p>
> </font>
> </blockquote>
>
> <p> </p>
> </body>
> </html>
>
> _______________________________________________
> Rails mailing list
> Rails@lists.rubyonrails.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
>
_______________________________________________
Rails mailing list
Rails@lists.rubyonrails.org
http://lists.rubyonrails.org/mailman/listinfo/rails
<body>
<blockquote>
<font FACE="Arial,Arial" SIZE="1"><p
ALIGN="JUSTIFY">This Email may contain confidential and privileged
information and is intended for the use of the addressee(s) only. If you are
not the intended recipient please notify the sender and delete the Email from
your system. It should not be transmitted to any other person without the
consent of the sender. Additional important notifications regarding Email
transmissions from and to members of Baring Asset Management can be accessed at
<a
href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body>
</p>
</font>
</blockquote>
<p> </p>
</body>
</html>
-----Original Message-----
From: Stuart Holden
Sent: 26 July 2006 12:38
To: ''rails@lists.rubyonrails.org''
Subject: RE: [Rails] ActiveRecord SQL Server Adapter Bug...
I think I''ve managed to fix the column quoting issue by making three
small changes to the add_limit_offset! method in the
sqlserver_adapter.rb file:
def add_limit_offset!(sql, options)
if options[:limit] and options[:offset]
total_rows = @connection.select_all("SELECT count(*) as
TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP
1000000000")}) tally")[0][:TotalRows].to_i
if (options[:limit] + options[:offset]) >= total_rows
options[:limit] = (total_rows - options[:offset] >= 0) ?
(total_rows - options[:offset]) : 0
end
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT
TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] +
options[:offset]} ")
sql << ") AS tmp1"
if options[:order]
options[:order] = options[:order].split('','').map
do |field|
parts = field.split(" ")
tc = parts[0]
if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used
in query
####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['')
####-- ORIGINAL : tc << ''\\]''
tc.gsub!(/\./, ''.['') #NEW: -- do not escape
column quotes
tc << '']'' #NEW: -- do not
escape column quotes
end
if sql =~ /#{tc} AS (t\d_r\d\d?)/
parts[0] = $1
####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/
elsif parts[0] =~ /\w+\.([\[\]\w]+)/
#NEW: ensure that column quotes are picked up when matching to strip the
tablename
parts[0] = $1
end
parts.join('' '')
end.join('', '')
sql << " ORDER BY
#{change_order_direction(options[:order])}) AS tmp2 ORDER BY
#{options[:order]}"
else
sql << " ) AS tmp2"
end
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
"SELECT#{$1} TOP #{options[:limit]}"
end unless options[:limit].nil?
end
end
This has fixed the column quoting issue, but there are still some
problems with the SQL that ActiveRecord is generating. The original
issue of selecting by offset now works...
Article.find( :all,
:offset => 0,
:limit => 1,
:order => "contents.published_at DESC",
:conditions => [''published = ? AND contents.published_at <
? AND blog_id = ?'',true, Time.now, 1])
[#<Article:0x32acb34 @attributes={... (snip)
And selecting with the :include tag works...
Article.find( :all,
:order => "contents.published_at DESC",
:include => [:categories, :tags, :user, :blog],
:conditions => [''published = ? AND contents.published_at <
? AND blog_id = ?'',true, Time.now, 1])
[#<Article:0x3848110 @user=#<User:0x38476fc @attributes={...
(snip)
But a find that combines both the :offset and :include will fail due to
bad SQL...
Article.find( :all,
:offset => 0,
:limit => 1,
:order => "contents.published_at DESC",
:include => [:categories, :tags, :user, :blog],
:conditions => [''published = ? AND contents.published_at <
? AND
blog_id = ?'',true, Time.now, 1])
ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207)
[Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name
''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT
TOP 1 id
FROM contents WHERE (published = 1 AND contents.published_at <
''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type]
''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1
ORDER BY
[published_at] ASC) AS tmp2 ORDER BY [published_at] DESC
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/abstract_adapter.rb:120:in `log''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/sqlserver_adapter.rb:521:in `select''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1166:in `select_limited_ids_list''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1158:in `add_limited_ids_condition!''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1148:in
`construct_finder_sql_with_included_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:1111:in `select_all_rows''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:975:in `find_with_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/associations.rb:973:in `find_with_associations''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/base.rb:923:in `find_every''
from
./script/../config/../config/../vendor/rails/activerecord/lib/active_rec
ord/base.rb:381:in `find''
from (irb):104
from :0
This one is caused by just selecting the [id] in the inner select, and
not exposing the the [published_at] column to be sorted on in the outer
selects. Sadly I haven''t been able to get anywhere with debugging this
one. Also, please note that this is being run against the Typo 4
database, and the test are run via the script/console util. The issues
do also occur through the web frontend.
Regards,
Stu
-----Original Message-----
From: rails-bounces@lists.rubyonrails.org
[mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward
Sent: 25 July 2006 16:18
To: rails@lists.rubyonrails.org
Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug...
Interesting. I''ll take a look this evening. Please let me know if
you find out any more.
Tom
On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com>
wrote:> Hi,
>
>
> I''ve tried creating a new ticket for this issue, but I''m
just getting
> 500 server errors.
>
> There appears to be a problem with the ActiveRecord SqlServer adapter
> that results in illegal SQL being generated...
>
>
> ActiveRecord::StatementInvalid in ArticlesController#index
> DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server
Driver][SQL> Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM
(SELECT TOP
1> *
> FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND
> contents.published_at < ''2006-07-25 11:59:31'' AND
blog_id = 1) AND (
> (contents.[type] = ''Article'' ) ) ORDER BY
contents.published_at DESC
)> AS
> tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY
> contents\.\[published_at\] DESC
>
> This is due to messed up column quoting. I''ve tracked it down to
the
> ''add_limit_offset!'' method in sqlserver_adapter.rb, but I
can''t
decipher> enough of what is going on to suggest the fix just yet.
>
> Full stack trace also included below.
>
> Regards,
> Stu
>
> RAILS_ROOT: ./script/../config/..
> Application Trace | Framework Trace | Full Trace
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/abstract_adapter.rb:120:in
> `log''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:485:in
> `select''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:233:in
> `select_all''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1166:in
> `select_limited_ids_list''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1158:in
> `add_limited_ids_condition!''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1148:in
> `construct_finder_sql_with_included_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1111:in
> `select_all_rows''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:975:in
> `find_with_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:973:in
> `find_with_associations''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in> `find_every''
>
>
#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in> `find''
> #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in
`index''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910:> in
> `perform_action_without_filters''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 68:in
> `perform_action_without_benchmark''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in
> `perform_action_without_rescue''
> c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in
> `perform_action_without_rescue''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82> :in
> `perform_action''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381:> in
> `process_without_filters''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 77:in
> `process_without_session_management_support''
>
>
#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana> gement.rb:117:in
> `process''
> #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in
`dispatch''> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in
> `handle_dispatch''
> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in
> `service''
> c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service''
> c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start''
> c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start''
> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in
> `dispatch''
>
#{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
> `require''
>
>
#{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies> .rb:147:in
> `require''
> #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30
> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in
> `require''
> script/server:3
> Request
>
> Parameters: None
>
> Show session dump
> ---
> Response
> Headers: {"cookie"=>[],
"Cache-Control"=>"no-cache"}
>
> --
> Ticket URL: <http://www.typosphere.org/trac/ticket/1040>
> typo <http://typosphere.org/trac/>
> web2.0 weblogging engine
>
>
> <body>
>
> <blockquote>
> <font FACE="Arial,Arial" SIZE="1"><p
ALIGN="JUSTIFY">This Email may
contain confidential and privileged information and is intended for the
use of the addressee(s) only. If you are not the intended recipient
please notify the sender and delete the Email from your system. It
should not be transmitted to any other person without the consent of the
sender. Additional important notifications regarding Email transmissions
from and to members of Baring Asset Management can be accessed at <a
href="http://www.barings.com/email/index.hcst">http://www.barings.com/em
ail/index.hcst</a></font></p></body>> </p>
> </font>
> </blockquote>
>
> <p> </p>
> </body>
> </html>
>
> _______________________________________________
> Rails mailing list
> Rails@lists.rubyonrails.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
>
_______________________________________________
Rails mailing list
Rails@lists.rubyonrails.org
http://lists.rubyonrails.org/mailman/listinfo/rails
<body>
<blockquote>
<font FACE="Arial,Arial" SIZE="1"><p
ALIGN="JUSTIFY">This Email may contain confidential and privileged
information and is intended for the use of the addressee(s) only. If you are
not the intended recipient please notify the sender and delete the Email from
your system. It should not be transmitted to any other person without the
consent of the sender. Additional important notifications regarding Email
transmissions from and to members of Baring Asset Management can be accessed at
<a
href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body>
</p>
</font>
</blockquote>
<p> </p>
</body>
</html>
Thanks for the extra info. I hate that limit/offset thing, cause of all my sorrow. Tom On 26/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> I think I''ve managed to fix the column quoting issue by making three > small changes to the add_limit_offset! method in the > sqlserver_adapter.rb file: > > def add_limit_offset!(sql, options) > > if options[:limit] and options[:offset] > total_rows = @connection.select_all("SELECT count(*) as > TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP > 1000000000")}) tally")[0][:TotalRows].to_i > if (options[:limit] + options[:offset]) >= total_rows > options[:limit] = (total_rows - options[:offset] >= 0) ? > (total_rows - options[:offset]) : 0 > end > sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT > TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + > options[:offset]} ") > sql << ") AS tmp1" > if options[:order] > options[:order] = options[:order].split('','').map do |field| > parts = field.split(" ") > tc = parts[0] > if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used > in query > > > ####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['') > ####-- ORIGINAL : tc << ''\\]'' > > tc.gsub!(/\./, ''.['') #NEW: -- do not escape > column quotes > tc << '']'' #NEW: -- do not > escape column quotes > > > end > if sql =~ /#{tc} AS (t\d_r\d\d?)/ > parts[0] = $1 > > ####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/ > elsif parts[0] =~ /\w+\.([\[\]\w]+)/ > #NEW: ensure that column quotes are picked up when matching to strip the > tablename > > parts[0] = $1 > end > parts.join('' '') > end.join('', '') > sql << " ORDER BY > #{change_order_direction(options[:order])}) AS tmp2 ORDER BY > #{options[:order]}" > else > sql << " ) AS tmp2" > end > elsif sql !~ /^\s*SELECT (@@|COUNT\()/i > sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do > "SELECT#{$1} TOP #{options[:limit]}" > end unless options[:limit].nil? > end > end > > > This has fixed the column quoting issue, but there are still some > problems with the SQL that ActiveRecord is generating. The original > issue of selecting by offset now works... > > Article.find( :all, > :offset => 0, > :limit => 1, > :order => "contents.published_at DESC", > :conditions => [''published = ? AND contents.published_at < > ? AND blog_id = ?'',true, Time.now, 1]) > > [#<Article:0x32acb34 @attributes={... (snip) > > And selecting with the :include tag works... > > Article.find( :all, > :order => "contents.published_at DESC", > :include => [:categories, :tags, :user, :blog], > :conditions => [''published = ? AND contents.published_at < > ? AND blog_id = ?'',true, Time.now, 1]) > > [#<Article:0x3848110 @user=#<User:0x38476fc @attributes={... > (snip) > > But a find that combines both the :offset and :include will fail due to > bad SQL... > > Article.find( :all, > :offset => 0, > :limit => 1, > :order => "contents.published_at DESC", > :include => [:categories, :tags, :user, :blog], > :conditions => [''published = ? AND contents.published_at < ? AND > blog_id = ?'',true, Time.now, 1]) > > ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207) > [Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name > ''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT TOP 1 id > FROM contents WHERE (published = 1 AND contents.published_at < > ''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type] > ''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1 ORDER BY > [published_at] ASC) AS tmp2 ORDER BY [published_at] DESC > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/abstract_adapter.rb:120:in `log'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/sqlserver_adapter.rb:521:in `select'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1166:in `select_limited_ids_list'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1158:in `add_limited_ids_condition!'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1148:in > `construct_finder_sql_with_included_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1111:in `select_all_rows'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:975:in `find_with_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:973:in `find_with_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/base.rb:923:in `find_every'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/base.rb:381:in `find'' > from (irb):104 > from :0 > > This one is caused by just selecting the [id] in the inner select, and > not exposing the the [published_at] column to be sorted on in the outer > selects. Sadly I haven''t been able to get anywhere with debugging this > one. Also, please note that this is being run against the Typo 4 > database, and the test are run via the script/console util. The issues > do also occur through the web frontend. > > Regards, > Stu > > > -----Original Message----- > From: rails-bounces@lists.rubyonrails.org > [mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward > Sent: 25 July 2006 16:18 > To: rails@lists.rubyonrails.org > Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug... > > Interesting. I''ll take a look this evening. Please let me know if > you find out any more. > > Tom > > On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote: > > Hi, > > > > > > I''ve tried creating a new ticket for this issue, but I''m just getting > > 500 server errors. > > > > There appears to be a problem with the ActiveRecord SqlServer adapter > > that results in illegal SQL being generated... > > > > > > ActiveRecord::StatementInvalid in ArticlesController#index > > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server > Driver][SQL > > Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP > 1 > > * > > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC > ) > > AS > > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > > contents\.\[published_at\] DESC > > > > This is due to messed up column quoting. I''ve tracked it down to the > > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''t > decipher > > enough of what is going on to suggest the fix just yet. > > > > Full stack trace also included below. > > > > Regards, > > Stu > > > > RAILS_ROOT: ./script/../config/.. > > Application Trace | Framework Trace | Full Trace > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/abstract_adapter.rb:120:in > > `log'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/sqlserver_adapter.rb:485:in > > `select'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/sqlserver_adapter.rb:233:in > > `select_all'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1166:in > > `select_limited_ids_list'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1158:in > > `add_limited_ids_condition!'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1148:in > > `construct_finder_sql_with_included_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1111:in > > `select_all_rows'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:975:in > > `find_with_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:973:in > > `find_with_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in > > `find_every'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in > > `find'' > > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910: > > in > > `perform_action_without_filters'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > > 68:in > > `perform_action_without_benchmark'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > > .rb:69:in > > `perform_action_without_rescue'' > > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > > .rb:69:in > > `perform_action_without_rescue'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82 > > :in > > `perform_action'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381: > > in > > `process_without_filters'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > > 77:in > > `process_without_session_management_support'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana > > gement.rb:117:in > > `process'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in > `dispatch'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > > `handle_dispatch'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > > `service'' > > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > > `dispatch'' > > > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59 > > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > > `require'' > > > > > #{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies > > .rb:147:in > > `require'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > > `require'' > > script/server:3 > > Request > > > > Parameters: None > > > > Show session dump > > --- > > Response > > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > > > -- > > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > > typo <http://typosphere.org/trac/> > > web2.0 weblogging engine > > > > > > <body> > > > > <blockquote> > > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may > contain confidential and privileged information and is intended for the > use of the addressee(s) only. If you are not the intended recipient > please notify the sender and delete the Email from your system. It > should not be transmitted to any other person without the consent of the > sender. Additional important notifications regarding Email transmissions > from and to members of Baring Asset Management can be accessed at <a > href="http://www.barings.com/email/index.hcst">http://www.barings.com/em > ail/index.hcst</a></font></p></body> > > </p> > > </font> > > </blockquote> > > > > <p> </p> > > </body> > > </html> > > > > _______________________________________________ > > Rails mailing list > > Rails@lists.rubyonrails.org > > http://lists.rubyonrails.org/mailman/listinfo/rails > > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> > </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Maybe Matching Threads
- establish_connection method
- Rails talking to MySql (cygwin on Windows XP)
- Problem with mongrel_rails, but works with script/server
- what this line means exactly? (@@active_connections ||= {})
- [PATCH] Access to more Arel predicate types from where condition hash